@hasna/todos 0.11.38 → 0.11.39
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/README.md +1 -11
- package/dist/capabilities.d.ts +32 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/cli/commands/cloud-commands.d.ts.map +1 -1
- package/dist/cli/commands/remote-commands.d.ts +3 -0
- package/dist/cli/commands/remote-commands.d.ts.map +1 -0
- package/dist/cli/index.js +16830 -29235
- package/dist/cli/remote-index.d.ts +3 -0
- package/dist/cli/remote-index.d.ts.map +1 -0
- package/dist/contracts.d.ts +58 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +823 -0
- package/dist/db/builtin-templates.d.ts.map +1 -1
- package/dist/index.d.ts +14 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4712 -13636
- package/dist/json-contracts.d.ts +56 -0
- package/dist/json-contracts.d.ts.map +1 -0
- package/dist/lib/cloud-migration.d.ts +53 -0
- package/dist/lib/cloud-migration.d.ts.map +1 -0
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/logger.d.ts +6 -12
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +4435 -16629
- package/dist/mcp/token-utils.d.ts.map +1 -1
- package/dist/mcp/tools/code-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-meta-tools.d.ts.map +1 -1
- package/dist/mcp.d.ts +42 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +359 -0
- package/dist/registry.d.ts +33 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +1382 -0
- package/dist/remote-cli/remote-index.js +3054 -0
- package/dist/remote.d.ts +5 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +770 -0
- package/dist/sdk/client.d.ts +1 -1
- package/dist/sdk/client.d.ts.map +1 -1
- package/dist/sdk/index.js +635 -0
- package/dist/server/index.js +4 -4
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/interfaces.d.ts +185 -0
- package/dist/storage/interfaces.d.ts.map +1 -0
- package/dist/storage/local-sqlite.d.ts +7 -0
- package/dist/storage/local-sqlite.d.ts.map +1 -0
- package/dist/storage.d.ts +4 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +4984 -0
- package/package.json +22 -4
package/dist/remote.js
ADDED
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
function __accessProp(key) {
|
|
7
|
+
return this[key];
|
|
8
|
+
}
|
|
9
|
+
var __toCommonJS = (from) => {
|
|
10
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
11
|
+
if (entry)
|
|
12
|
+
return entry;
|
|
13
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (var key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(entry, key))
|
|
17
|
+
__defProp(entry, key, {
|
|
18
|
+
get: __accessProp.bind(from, key),
|
|
19
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
__moduleCache.set(from, entry);
|
|
23
|
+
return entry;
|
|
24
|
+
};
|
|
25
|
+
var __moduleCache;
|
|
26
|
+
var __returnValue = (v) => v;
|
|
27
|
+
function __exportSetter(name, newValue) {
|
|
28
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
29
|
+
}
|
|
30
|
+
var __export = (target, all) => {
|
|
31
|
+
for (var name in all)
|
|
32
|
+
__defProp(target, name, {
|
|
33
|
+
get: all[name],
|
|
34
|
+
enumerable: true,
|
|
35
|
+
configurable: true,
|
|
36
|
+
set: __exportSetter.bind(all, name)
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
40
|
+
|
|
41
|
+
// src/sdk/types.ts
|
|
42
|
+
class TodosAPIError extends Error {
|
|
43
|
+
status;
|
|
44
|
+
statusText;
|
|
45
|
+
body;
|
|
46
|
+
constructor(message, status, statusText, body) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.status = status;
|
|
49
|
+
this.statusText = statusText;
|
|
50
|
+
this.body = body;
|
|
51
|
+
this.name = "TodosAPIError";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class TodosNotFoundError extends TodosAPIError {
|
|
56
|
+
constructor(message, body) {
|
|
57
|
+
super(message, 404, "Not Found", body);
|
|
58
|
+
this.name = "TodosNotFoundError";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
class TodosConflictError extends TodosAPIError {
|
|
63
|
+
constructor(message, body) {
|
|
64
|
+
super(message, 409, "Conflict", body);
|
|
65
|
+
this.name = "TodosConflictError";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class TodosUnauthorizedError extends TodosAPIError {
|
|
70
|
+
constructor(message, body) {
|
|
71
|
+
super(message, 401, "Unauthorized", body);
|
|
72
|
+
this.name = "TodosUnauthorizedError";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class TodosRateLimitError extends TodosAPIError {
|
|
77
|
+
retryAfter;
|
|
78
|
+
constructor(message, retryAfter, body) {
|
|
79
|
+
super(message, 429, "Too Many Requests", body);
|
|
80
|
+
this.retryAfter = retryAfter;
|
|
81
|
+
this.name = "TodosRateLimitError";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
class TodosTimeoutError extends Error {
|
|
86
|
+
ms;
|
|
87
|
+
constructor(ms) {
|
|
88
|
+
super(`Request timed out after ${ms}ms`);
|
|
89
|
+
this.ms = ms;
|
|
90
|
+
this.name = "TodosTimeoutError";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/lib/config.ts
|
|
95
|
+
import { existsSync as existsSync2 } from "fs";
|
|
96
|
+
import { dirname, join as join2 } from "path";
|
|
97
|
+
|
|
98
|
+
// src/lib/sync-utils.ts
|
|
99
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
|
|
100
|
+
import { join } from "path";
|
|
101
|
+
var HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
102
|
+
function ensureDir(dir) {
|
|
103
|
+
if (!existsSync(dir))
|
|
104
|
+
mkdirSync(dir, { recursive: true });
|
|
105
|
+
}
|
|
106
|
+
function listJsonFiles(dir) {
|
|
107
|
+
if (!existsSync(dir))
|
|
108
|
+
return [];
|
|
109
|
+
return readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
110
|
+
}
|
|
111
|
+
function readJsonFile(path) {
|
|
112
|
+
try {
|
|
113
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function writeJsonFile(path, data) {
|
|
119
|
+
writeFileSync(path, JSON.stringify(data, null, 2) + `
|
|
120
|
+
`);
|
|
121
|
+
}
|
|
122
|
+
function readHighWaterMark(dir) {
|
|
123
|
+
const path = join(dir, ".highwatermark");
|
|
124
|
+
if (!existsSync(path))
|
|
125
|
+
return 1;
|
|
126
|
+
const val = parseInt(readFileSync(path, "utf-8").trim(), 10);
|
|
127
|
+
return isNaN(val) ? 1 : val;
|
|
128
|
+
}
|
|
129
|
+
function writeHighWaterMark(dir, value) {
|
|
130
|
+
writeFileSync(join(dir, ".highwatermark"), String(value));
|
|
131
|
+
}
|
|
132
|
+
function getFileMtimeMs(path) {
|
|
133
|
+
try {
|
|
134
|
+
return statSync(path).mtimeMs;
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function parseTimestamp(value) {
|
|
140
|
+
if (typeof value !== "string")
|
|
141
|
+
return null;
|
|
142
|
+
const parsed = Date.parse(value);
|
|
143
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
144
|
+
}
|
|
145
|
+
function appendSyncConflict(metadata, conflict, limit = 5) {
|
|
146
|
+
const current = Array.isArray(metadata["sync_conflicts"]) ? metadata["sync_conflicts"] : [];
|
|
147
|
+
const next = [conflict, ...current].slice(0, limit);
|
|
148
|
+
return { ...metadata, sync_conflicts: next };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/lib/config.ts
|
|
152
|
+
function getTodosGlobalDir() {
|
|
153
|
+
const home = process.env["HOME"] || HOME;
|
|
154
|
+
const newDir = join2(home, ".hasna", "todos");
|
|
155
|
+
const legacyDir = join2(home, ".todos");
|
|
156
|
+
const newConfig = join2(newDir, "config.json");
|
|
157
|
+
const legacyConfig = join2(legacyDir, "config.json");
|
|
158
|
+
if (!existsSync2(newConfig) && existsSync2(legacyConfig))
|
|
159
|
+
return legacyDir;
|
|
160
|
+
return newDir;
|
|
161
|
+
}
|
|
162
|
+
function getConfigPath() {
|
|
163
|
+
return join2(getTodosGlobalDir(), "config.json");
|
|
164
|
+
}
|
|
165
|
+
var cached = null;
|
|
166
|
+
function normalizeAgent(agent) {
|
|
167
|
+
return agent.trim().toLowerCase();
|
|
168
|
+
}
|
|
169
|
+
function loadConfig() {
|
|
170
|
+
if (cached)
|
|
171
|
+
return cached;
|
|
172
|
+
if (!existsSync2(getConfigPath())) {
|
|
173
|
+
cached = {};
|
|
174
|
+
return cached;
|
|
175
|
+
}
|
|
176
|
+
const config = readJsonFile(getConfigPath()) || {};
|
|
177
|
+
if (typeof config.sync_agents === "string") {
|
|
178
|
+
config.sync_agents = config.sync_agents.split(",").map((a) => a.trim()).filter(Boolean);
|
|
179
|
+
}
|
|
180
|
+
cached = config;
|
|
181
|
+
return cached;
|
|
182
|
+
}
|
|
183
|
+
function saveConfig(config) {
|
|
184
|
+
const configPath = getConfigPath();
|
|
185
|
+
ensureDir(dirname(configPath));
|
|
186
|
+
writeJsonFile(configPath, config);
|
|
187
|
+
cached = config;
|
|
188
|
+
return config;
|
|
189
|
+
}
|
|
190
|
+
function updateConfig(patch) {
|
|
191
|
+
return saveConfig({ ...loadConfig(), ...patch });
|
|
192
|
+
}
|
|
193
|
+
function normalizeApiUrl(value) {
|
|
194
|
+
const trimmed = value?.trim();
|
|
195
|
+
if (!trimmed)
|
|
196
|
+
return null;
|
|
197
|
+
return trimmed.replace(/\/+$/, "");
|
|
198
|
+
}
|
|
199
|
+
function normalizeMode(value) {
|
|
200
|
+
return value === "local" || value === "remote" ? value : null;
|
|
201
|
+
}
|
|
202
|
+
function getRemoteApiConfig(env = process.env) {
|
|
203
|
+
const config = loadConfig();
|
|
204
|
+
const envApiUrl = normalizeApiUrl(env["TODOS_API_URL"]);
|
|
205
|
+
const legacyEnvUrl = normalizeApiUrl(env["TODOS_URL"]);
|
|
206
|
+
const configApiUrl = normalizeApiUrl(config.apiUrl);
|
|
207
|
+
const apiUrl = envApiUrl ?? legacyEnvUrl ?? configApiUrl;
|
|
208
|
+
const envMode = normalizeMode(env["TODOS_MODE"]);
|
|
209
|
+
const configMode = normalizeMode(config.mode);
|
|
210
|
+
const mode = envMode ?? configMode ?? (apiUrl ? "remote" : "local");
|
|
211
|
+
const apiKey = env["TODOS_API_KEY"] || config.apiKey || null;
|
|
212
|
+
return {
|
|
213
|
+
mode,
|
|
214
|
+
apiUrl: mode === "remote" ? apiUrl : null,
|
|
215
|
+
apiKey,
|
|
216
|
+
source: {
|
|
217
|
+
mode: envMode ? "env" : configMode ? "config" : "derived",
|
|
218
|
+
apiUrl: envApiUrl ? "TODOS_API_URL" : legacyEnvUrl ? "TODOS_URL" : configApiUrl ? "config" : "none",
|
|
219
|
+
apiKey: env["TODOS_API_KEY"] ? "TODOS_API_KEY" : config.apiKey ? "config" : "none"
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function isRemoteMode(env = process.env) {
|
|
224
|
+
return getRemoteApiConfig(env).mode === "remote";
|
|
225
|
+
}
|
|
226
|
+
function getSyncAgentsFromConfig() {
|
|
227
|
+
const config = loadConfig();
|
|
228
|
+
const agents = config.sync_agents;
|
|
229
|
+
if (Array.isArray(agents) && agents.length > 0)
|
|
230
|
+
return agents.map(normalizeAgent);
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
function getAgentTasksDir(agent) {
|
|
234
|
+
const config = loadConfig();
|
|
235
|
+
const key = normalizeAgent(agent);
|
|
236
|
+
return config.agents?.[key]?.tasks_dir || config.agent_tasks_dir || null;
|
|
237
|
+
}
|
|
238
|
+
function getTaskPrefixConfig() {
|
|
239
|
+
const config = loadConfig();
|
|
240
|
+
return config.task_prefix || null;
|
|
241
|
+
}
|
|
242
|
+
var GUARD_DEFAULTS = {
|
|
243
|
+
enabled: false,
|
|
244
|
+
min_work_seconds: 30,
|
|
245
|
+
max_completions_per_window: 5,
|
|
246
|
+
window_minutes: 10,
|
|
247
|
+
cooldown_seconds: 60
|
|
248
|
+
};
|
|
249
|
+
function getCompletionGuardConfig(projectPath) {
|
|
250
|
+
const config = loadConfig();
|
|
251
|
+
const global = { ...GUARD_DEFAULTS, ...config.completion_guard };
|
|
252
|
+
if (projectPath && config.project_overrides?.[projectPath]?.completion_guard) {
|
|
253
|
+
return { ...global, ...config.project_overrides[projectPath].completion_guard };
|
|
254
|
+
}
|
|
255
|
+
return global;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/sdk/client.ts
|
|
259
|
+
function buildQuery(params) {
|
|
260
|
+
const search = new URLSearchParams;
|
|
261
|
+
for (const [k, v] of Object.entries(params)) {
|
|
262
|
+
if (v !== undefined)
|
|
263
|
+
search.set(k, Array.isArray(v) ? v.join(",") : String(v));
|
|
264
|
+
}
|
|
265
|
+
const s = search.toString();
|
|
266
|
+
return s ? `?${s}` : "";
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
class TasksResource {
|
|
270
|
+
client;
|
|
271
|
+
constructor(client) {
|
|
272
|
+
this.client = client;
|
|
273
|
+
}
|
|
274
|
+
async list(options) {
|
|
275
|
+
return this.client._get("/api/tasks", buildQuery(options || {}));
|
|
276
|
+
}
|
|
277
|
+
async get(id, options) {
|
|
278
|
+
return this.client._get(`/api/tasks/${id}`, buildQuery(options || {}));
|
|
279
|
+
}
|
|
280
|
+
async getWithRelations(id) {
|
|
281
|
+
return this.client._get(`/api/tasks/${id}`, buildQuery({ format: "full" }));
|
|
282
|
+
}
|
|
283
|
+
async create(data) {
|
|
284
|
+
return this.client._post("/api/tasks", data);
|
|
285
|
+
}
|
|
286
|
+
async update(id, data) {
|
|
287
|
+
return this.client._patch(`/api/tasks/${id}`, data);
|
|
288
|
+
}
|
|
289
|
+
async delete(id) {
|
|
290
|
+
return this.client._delete(`/api/tasks/${id}`);
|
|
291
|
+
}
|
|
292
|
+
async start(id, agentId) {
|
|
293
|
+
return this.client._post(`/api/tasks/${id}/start`, { agent_id: agentId });
|
|
294
|
+
}
|
|
295
|
+
async complete(id, agentId) {
|
|
296
|
+
return this.client._post(`/api/tasks/${id}/complete`, { agent_id: agentId });
|
|
297
|
+
}
|
|
298
|
+
async fail(id, options) {
|
|
299
|
+
return this.client._post(`/api/tasks/${id}/fail`, options || {});
|
|
300
|
+
}
|
|
301
|
+
async logProgress(taskId, message, pctComplete, agentId) {
|
|
302
|
+
return this.client._post(`/api/tasks/${taskId}/progress`, {
|
|
303
|
+
message,
|
|
304
|
+
pct_complete: pctComplete,
|
|
305
|
+
agent_id: agentId
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
async getProgress(id, options) {
|
|
309
|
+
return this.client._get(`/api/tasks/${id}/progress`, buildQuery(options || {}));
|
|
310
|
+
}
|
|
311
|
+
async getHistory(id, options) {
|
|
312
|
+
return this.client._get(`/api/tasks/${id}/history`, buildQuery(options || {}));
|
|
313
|
+
}
|
|
314
|
+
async getAttachments(id) {
|
|
315
|
+
return this.client._get(`/api/tasks/${id}/attachments`);
|
|
316
|
+
}
|
|
317
|
+
async status(options) {
|
|
318
|
+
return this.client._get("/api/tasks/status", buildQuery(options || {}));
|
|
319
|
+
}
|
|
320
|
+
async next(options) {
|
|
321
|
+
return this.client._get("/api/tasks/next", buildQuery(options || {}));
|
|
322
|
+
}
|
|
323
|
+
async active(projectId) {
|
|
324
|
+
return this.client._get("/api/tasks/active", projectId ? `?project_id=${projectId}` : "");
|
|
325
|
+
}
|
|
326
|
+
async stale(options) {
|
|
327
|
+
return this.client._get("/api/tasks/stale", buildQuery(options || {}));
|
|
328
|
+
}
|
|
329
|
+
async changedSince(since, projectId, options) {
|
|
330
|
+
return this.client._get("/api/tasks/changed", buildQuery({ since, project_id: projectId, fields: options?.fields }));
|
|
331
|
+
}
|
|
332
|
+
async context(options) {
|
|
333
|
+
const q = buildQuery({
|
|
334
|
+
agent_id: options?.agentId,
|
|
335
|
+
project_id: options?.projectId,
|
|
336
|
+
format: options?.format,
|
|
337
|
+
fields: options?.fields
|
|
338
|
+
});
|
|
339
|
+
const url = `${this.client.baseUrl}/api/tasks/context${q}`;
|
|
340
|
+
const res = await this.client._fetchRaw(url);
|
|
341
|
+
if (!res.ok)
|
|
342
|
+
return options?.format === "json" ? {} : "";
|
|
343
|
+
if (options?.format === "json")
|
|
344
|
+
return res.json();
|
|
345
|
+
return res.text();
|
|
346
|
+
}
|
|
347
|
+
async export(options) {
|
|
348
|
+
const q = buildQuery(options || {});
|
|
349
|
+
const url = `${this.client.baseUrl}/api/tasks/export${q}`;
|
|
350
|
+
const res = await this.client._fetchRaw(url);
|
|
351
|
+
if (!res.ok)
|
|
352
|
+
throw new TodosAPIError("Export failed", res.status, res.statusText, null);
|
|
353
|
+
if (options?.format === "csv")
|
|
354
|
+
return res.text();
|
|
355
|
+
return res.json();
|
|
356
|
+
}
|
|
357
|
+
async bulk(ids, action) {
|
|
358
|
+
return this.client._post("/api/tasks/bulk", { ids, action });
|
|
359
|
+
}
|
|
360
|
+
async claim(agentId, projectId) {
|
|
361
|
+
return this.client._post("/api/tasks/claim", { agent_id: agentId, project_id: projectId });
|
|
362
|
+
}
|
|
363
|
+
async* subscribe(options = {}) {
|
|
364
|
+
const q = buildQuery({
|
|
365
|
+
agent_id: options.agentId,
|
|
366
|
+
project_id: options.projectId,
|
|
367
|
+
events: options.events?.join(",")
|
|
368
|
+
});
|
|
369
|
+
const url = `${this.client.baseUrl}/api/tasks/stream${q}`;
|
|
370
|
+
const resp = await fetch(url);
|
|
371
|
+
if (!resp.ok || !resp.body)
|
|
372
|
+
throw new Error(`SSE connection failed: ${resp.status}`);
|
|
373
|
+
const reader = resp.body.getReader();
|
|
374
|
+
const decoder = new TextDecoder;
|
|
375
|
+
let buffer = "";
|
|
376
|
+
try {
|
|
377
|
+
while (true) {
|
|
378
|
+
const { done, value } = await reader.read();
|
|
379
|
+
if (done)
|
|
380
|
+
break;
|
|
381
|
+
buffer += decoder.decode(value, { stream: true });
|
|
382
|
+
const lines = buffer.split(`
|
|
383
|
+
`);
|
|
384
|
+
buffer = lines.pop() || "";
|
|
385
|
+
for (const line of lines) {
|
|
386
|
+
if (line.startsWith("data: ")) {
|
|
387
|
+
try {
|
|
388
|
+
const data = JSON.parse(line.slice(6));
|
|
389
|
+
if (data.type !== "connected")
|
|
390
|
+
yield data;
|
|
391
|
+
} catch {}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
} finally {
|
|
396
|
+
reader.releaseLock();
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
class AgentsResource {
|
|
402
|
+
client;
|
|
403
|
+
constructor(client) {
|
|
404
|
+
this.client = client;
|
|
405
|
+
}
|
|
406
|
+
async list(options) {
|
|
407
|
+
return this.client._get("/api/agents", buildQuery(options || {}));
|
|
408
|
+
}
|
|
409
|
+
async register(data) {
|
|
410
|
+
return this.client._post("/api/agents", data);
|
|
411
|
+
}
|
|
412
|
+
async fullRegister(data) {
|
|
413
|
+
return this.client._post("/api/agents", data);
|
|
414
|
+
}
|
|
415
|
+
async update(id, data) {
|
|
416
|
+
return this.client._patch(`/api/agents/${id}`, data);
|
|
417
|
+
}
|
|
418
|
+
async delete(id) {
|
|
419
|
+
return this.client._delete(`/api/agents/${id}`);
|
|
420
|
+
}
|
|
421
|
+
async bulkDelete(ids) {
|
|
422
|
+
return this.client._post("/api/agents/bulk", { ids, action: "delete" });
|
|
423
|
+
}
|
|
424
|
+
async me(name) {
|
|
425
|
+
return this.client._get("/api/agents/me", `?name=${encodeURIComponent(name)}`);
|
|
426
|
+
}
|
|
427
|
+
async queue(agentId) {
|
|
428
|
+
return this.client._get(`/api/agents/${encodeURIComponent(agentId)}/queue`);
|
|
429
|
+
}
|
|
430
|
+
async team(agentId) {
|
|
431
|
+
return this.client._get(`/api/agents/${encodeURIComponent(agentId)}/team`);
|
|
432
|
+
}
|
|
433
|
+
async orgChart() {
|
|
434
|
+
return this.client._get("/api/org");
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
class ProjectsResource {
|
|
439
|
+
client;
|
|
440
|
+
constructor(client) {
|
|
441
|
+
this.client = client;
|
|
442
|
+
}
|
|
443
|
+
async list(options) {
|
|
444
|
+
return this.client._get("/api/projects", buildQuery(options || {}));
|
|
445
|
+
}
|
|
446
|
+
async create(data) {
|
|
447
|
+
return this.client._post("/api/projects", data);
|
|
448
|
+
}
|
|
449
|
+
async delete(id) {
|
|
450
|
+
return this.client._delete(`/api/projects/${id}`);
|
|
451
|
+
}
|
|
452
|
+
async bulkDelete(ids) {
|
|
453
|
+
return this.client._post("/api/projects/bulk", { ids, action: "delete" });
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
class PlansResource {
|
|
458
|
+
client;
|
|
459
|
+
constructor(client) {
|
|
460
|
+
this.client = client;
|
|
461
|
+
}
|
|
462
|
+
async list(projectId) {
|
|
463
|
+
return this.client._get("/api/plans", projectId ? `?project_id=${projectId}` : "");
|
|
464
|
+
}
|
|
465
|
+
async create(data) {
|
|
466
|
+
return this.client._post("/api/plans", data);
|
|
467
|
+
}
|
|
468
|
+
async get(id) {
|
|
469
|
+
return this.client._get(`/api/plans/${id}`);
|
|
470
|
+
}
|
|
471
|
+
async update(id, data) {
|
|
472
|
+
return this.client._patch(`/api/plans/${id}`, data);
|
|
473
|
+
}
|
|
474
|
+
async delete(id) {
|
|
475
|
+
return this.client._delete(`/api/plans/${id}`);
|
|
476
|
+
}
|
|
477
|
+
async bulkDelete(ids) {
|
|
478
|
+
return this.client._post("/api/plans/bulk", { ids, action: "delete" });
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
class OrgsResource {
|
|
483
|
+
client;
|
|
484
|
+
constructor(client) {
|
|
485
|
+
this.client = client;
|
|
486
|
+
}
|
|
487
|
+
async list() {
|
|
488
|
+
return this.client._get("/api/orgs");
|
|
489
|
+
}
|
|
490
|
+
async create(data) {
|
|
491
|
+
return this.client._post("/api/orgs", data);
|
|
492
|
+
}
|
|
493
|
+
async update(id, data) {
|
|
494
|
+
return this.client._patch(`/api/orgs/${id}`, data);
|
|
495
|
+
}
|
|
496
|
+
async delete(id) {
|
|
497
|
+
return this.client._delete(`/api/orgs/${id}`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
class WebhooksResource {
|
|
502
|
+
client;
|
|
503
|
+
constructor(client) {
|
|
504
|
+
this.client = client;
|
|
505
|
+
}
|
|
506
|
+
async list() {
|
|
507
|
+
return this.client._get("/api/webhooks");
|
|
508
|
+
}
|
|
509
|
+
async create(data) {
|
|
510
|
+
return this.client._post("/api/webhooks", data);
|
|
511
|
+
}
|
|
512
|
+
async delete(id) {
|
|
513
|
+
return this.client._delete(`/api/webhooks/${id}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
class TemplatesResource {
|
|
518
|
+
client;
|
|
519
|
+
constructor(client) {
|
|
520
|
+
this.client = client;
|
|
521
|
+
}
|
|
522
|
+
async list() {
|
|
523
|
+
return this.client._get("/api/templates");
|
|
524
|
+
}
|
|
525
|
+
async create(data) {
|
|
526
|
+
return this.client._post("/api/templates", data);
|
|
527
|
+
}
|
|
528
|
+
async delete(id) {
|
|
529
|
+
return this.client._delete(`/api/templates/${id}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
class TodosClient {
|
|
534
|
+
baseUrl;
|
|
535
|
+
timeout;
|
|
536
|
+
apiKey;
|
|
537
|
+
maxRetries;
|
|
538
|
+
retryDelay;
|
|
539
|
+
tasks;
|
|
540
|
+
agents;
|
|
541
|
+
projects;
|
|
542
|
+
plans;
|
|
543
|
+
orgs;
|
|
544
|
+
webhooks;
|
|
545
|
+
templates;
|
|
546
|
+
constructor(options = {}) {
|
|
547
|
+
const remoteConfig = getRemoteApiConfig();
|
|
548
|
+
this.baseUrl = normalizeApiUrl(options.baseUrl) || remoteConfig.apiUrl || normalizeApiUrl(process.env["TODOS_URL"]) || "http://localhost:19427";
|
|
549
|
+
this.timeout = options.timeout ?? 1e4;
|
|
550
|
+
this.apiKey = options.apiKey || remoteConfig.apiKey;
|
|
551
|
+
this.maxRetries = options.maxRetries ?? 0;
|
|
552
|
+
this.retryDelay = options.retryDelay ?? 1000;
|
|
553
|
+
this.tasks = new TasksResource(this);
|
|
554
|
+
this.agents = new AgentsResource(this);
|
|
555
|
+
this.projects = new ProjectsResource(this);
|
|
556
|
+
this.plans = new PlansResource(this);
|
|
557
|
+
this.orgs = new OrgsResource(this);
|
|
558
|
+
this.webhooks = new WebhooksResource(this);
|
|
559
|
+
this.templates = new TemplatesResource(this);
|
|
560
|
+
}
|
|
561
|
+
static fromEnv(apiKey) {
|
|
562
|
+
return new TodosClient({ apiKey });
|
|
563
|
+
}
|
|
564
|
+
async _fetchRaw(url, init) {
|
|
565
|
+
const controller = new AbortController;
|
|
566
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
567
|
+
try {
|
|
568
|
+
const headers = this._buildHeaders(init?.headers);
|
|
569
|
+
return await fetch(url, { ...init, headers, signal: controller.signal });
|
|
570
|
+
} finally {
|
|
571
|
+
clearTimeout(timer);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
_buildHeaders(existing) {
|
|
575
|
+
const headers = { "Content-Type": "application/json" };
|
|
576
|
+
if (this.apiKey)
|
|
577
|
+
headers["x-api-key"] = this.apiKey;
|
|
578
|
+
if (existing) {
|
|
579
|
+
if (existing instanceof Headers) {
|
|
580
|
+
existing.forEach((v, k) => {
|
|
581
|
+
headers[k] = v;
|
|
582
|
+
});
|
|
583
|
+
} else if (Array.isArray(existing)) {
|
|
584
|
+
for (const [k, v] of existing)
|
|
585
|
+
headers[k] = v;
|
|
586
|
+
} else {
|
|
587
|
+
Object.assign(headers, existing);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return headers;
|
|
591
|
+
}
|
|
592
|
+
async _fetchWithRetry(path, init) {
|
|
593
|
+
let lastError = null;
|
|
594
|
+
const maxAttempts = this.maxRetries + 1;
|
|
595
|
+
for (let attempt = 0;attempt < maxAttempts; attempt++) {
|
|
596
|
+
try {
|
|
597
|
+
return await this._fetch(path, init);
|
|
598
|
+
} catch (e) {
|
|
599
|
+
lastError = e;
|
|
600
|
+
if (e instanceof TodosAPIError && e.status < 500 && e.status !== 429)
|
|
601
|
+
throw e;
|
|
602
|
+
if (e instanceof TodosUnauthorizedError || e instanceof TodosNotFoundError || e instanceof TodosConflictError)
|
|
603
|
+
throw e;
|
|
604
|
+
if (attempt < maxAttempts - 1) {
|
|
605
|
+
const delay = this.retryDelay * Math.pow(2, attempt);
|
|
606
|
+
if (e instanceof TodosRateLimitError) {
|
|
607
|
+
await this._sleep(e.retryAfter * 1000);
|
|
608
|
+
} else {
|
|
609
|
+
await this._sleep(delay);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
throw lastError || new Error("Request failed after retries");
|
|
615
|
+
}
|
|
616
|
+
async _fetch(path, init) {
|
|
617
|
+
const controller = new AbortController;
|
|
618
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
619
|
+
try {
|
|
620
|
+
const url = `${this.baseUrl}${path}`;
|
|
621
|
+
const headers = this._buildHeaders(init?.headers);
|
|
622
|
+
const res = await fetch(url, { ...init, headers, signal: controller.signal });
|
|
623
|
+
if (!res.ok) {
|
|
624
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
625
|
+
const message = body.error || `HTTP ${res.status}: ${res.statusText}`;
|
|
626
|
+
if (res.status === 401)
|
|
627
|
+
throw new TodosUnauthorizedError(message, body);
|
|
628
|
+
if (res.status === 404)
|
|
629
|
+
throw new TodosNotFoundError(message, body);
|
|
630
|
+
if (res.status === 409)
|
|
631
|
+
throw new TodosConflictError(message, body);
|
|
632
|
+
if (res.status === 429) {
|
|
633
|
+
const retryAfter = parseInt(res.headers.get("retry-after") || "60", 10);
|
|
634
|
+
throw new TodosRateLimitError(message, retryAfter, body);
|
|
635
|
+
}
|
|
636
|
+
throw new TodosAPIError(message, res.status, res.statusText, body);
|
|
637
|
+
}
|
|
638
|
+
const contentLength = res.headers.get("content-length");
|
|
639
|
+
if (contentLength === "0" || contentLength === "4")
|
|
640
|
+
return null;
|
|
641
|
+
return res.json();
|
|
642
|
+
} catch (e) {
|
|
643
|
+
if (e instanceof DOMException && e.name === "AbortError") {
|
|
644
|
+
throw new TodosTimeoutError(this.timeout);
|
|
645
|
+
}
|
|
646
|
+
throw e;
|
|
647
|
+
} finally {
|
|
648
|
+
clearTimeout(timer);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
async _get(path, query = "") {
|
|
652
|
+
return this._fetchWithRetry(`${path}${query}`);
|
|
653
|
+
}
|
|
654
|
+
async _post(path, body) {
|
|
655
|
+
return this._fetchWithRetry(path, {
|
|
656
|
+
method: "POST",
|
|
657
|
+
body: body ? JSON.stringify(body) : undefined
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
async _patch(path, body) {
|
|
661
|
+
return this._fetchWithRetry(path, {
|
|
662
|
+
method: "PATCH",
|
|
663
|
+
body: JSON.stringify(body)
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
async _delete(path) {
|
|
667
|
+
return this._fetchWithRetry(path, { method: "DELETE" });
|
|
668
|
+
}
|
|
669
|
+
_sleep(ms) {
|
|
670
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
671
|
+
}
|
|
672
|
+
async getHealth() {
|
|
673
|
+
return this._get("/api/health");
|
|
674
|
+
}
|
|
675
|
+
async isAlive() {
|
|
676
|
+
try {
|
|
677
|
+
await this._get("/api/stats");
|
|
678
|
+
return true;
|
|
679
|
+
} catch {
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
async getStats() {
|
|
684
|
+
return this._get("/api/stats");
|
|
685
|
+
}
|
|
686
|
+
async getReport(options) {
|
|
687
|
+
return this._get("/api/report", buildQuery({ days: options?.days, project_id: options?.projectId }));
|
|
688
|
+
}
|
|
689
|
+
async doctor() {
|
|
690
|
+
return this._get("/api/doctor");
|
|
691
|
+
}
|
|
692
|
+
async activity(limit) {
|
|
693
|
+
return this._get("/api/activity", limit ? `?limit=${limit}` : "");
|
|
694
|
+
}
|
|
695
|
+
async listTasks(filter = {}) {
|
|
696
|
+
return this.tasks.list(filter);
|
|
697
|
+
}
|
|
698
|
+
async getTask(id, options) {
|
|
699
|
+
return this.tasks.get(id, options);
|
|
700
|
+
}
|
|
701
|
+
async createTask(data) {
|
|
702
|
+
return this.tasks.create(data);
|
|
703
|
+
}
|
|
704
|
+
async updateTask(id, data) {
|
|
705
|
+
return this.tasks.update(id, data);
|
|
706
|
+
}
|
|
707
|
+
async deleteTask(id) {
|
|
708
|
+
await this.tasks.delete(id);
|
|
709
|
+
}
|
|
710
|
+
async startTask(id, agentId) {
|
|
711
|
+
return this.tasks.start(id, agentId);
|
|
712
|
+
}
|
|
713
|
+
async completeTask(id, agentId) {
|
|
714
|
+
return this.tasks.complete(id, agentId);
|
|
715
|
+
}
|
|
716
|
+
async failTask(id, options = {}) {
|
|
717
|
+
return this.tasks.fail(id, options);
|
|
718
|
+
}
|
|
719
|
+
async logProgress(taskId, message, pctComplete, agentId) {
|
|
720
|
+
return this.tasks.logProgress(taskId, message, pctComplete, agentId);
|
|
721
|
+
}
|
|
722
|
+
async getStatus(projectId, agentId) {
|
|
723
|
+
return this.tasks.status({ project_id: projectId, agent_id: agentId });
|
|
724
|
+
}
|
|
725
|
+
async getActiveWork(projectId) {
|
|
726
|
+
const res = await this.tasks.active(projectId);
|
|
727
|
+
return res.active;
|
|
728
|
+
}
|
|
729
|
+
async getTasksChangedSince(since, projectId) {
|
|
730
|
+
return this.tasks.changedSince(since, projectId);
|
|
731
|
+
}
|
|
732
|
+
async getStaleTasks(minutes, projectId) {
|
|
733
|
+
return this.tasks.stale({ minutes, project_id: projectId });
|
|
734
|
+
}
|
|
735
|
+
async getContext(options = {}) {
|
|
736
|
+
return this.tasks.context(options);
|
|
737
|
+
}
|
|
738
|
+
async exportTasks(filter = {}) {
|
|
739
|
+
return this.tasks.export(filter);
|
|
740
|
+
}
|
|
741
|
+
async claimNextTask(agentId, projectId) {
|
|
742
|
+
return this.tasks.claim(agentId, projectId);
|
|
743
|
+
}
|
|
744
|
+
async getTaskHistory(id, options) {
|
|
745
|
+
return this.tasks.getHistory(id, options);
|
|
746
|
+
}
|
|
747
|
+
async getTaskAttachments(id) {
|
|
748
|
+
return this.tasks.getAttachments(id);
|
|
749
|
+
}
|
|
750
|
+
async getTaskProgress(id, options) {
|
|
751
|
+
return this.tasks.getProgress(id, options);
|
|
752
|
+
}
|
|
753
|
+
async* subscribeToStream(options = {}) {
|
|
754
|
+
yield* this.tasks.subscribe(options);
|
|
755
|
+
}
|
|
756
|
+
async getProjects() {
|
|
757
|
+
return this.projects.list();
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function createClient(options) {
|
|
761
|
+
return new TodosClient(options);
|
|
762
|
+
}
|
|
763
|
+
export {
|
|
764
|
+
updateConfig,
|
|
765
|
+
normalizeApiUrl,
|
|
766
|
+
isRemoteMode,
|
|
767
|
+
getRemoteApiConfig,
|
|
768
|
+
createClient,
|
|
769
|
+
TodosClient
|
|
770
|
+
};
|