taktiko 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/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # taktiko
2
+
3
+ The **Taktiko daemon** — the small program you run on your own machine to connect it to a
4
+ [Taktiko](https://github.com/wangdinglu/taktiko) server.
5
+
6
+ Taktiko agents run *your own* local agent CLI (Claude Code / Codex / Kimi). The daemon pairs your
7
+ machine with the server, holds a WebSocket open, and for each turn the server dispatches it runs the
8
+ agent's CLI locally and streams the reply back. Your code, credentials, and CLI never leave your
9
+ machine — only the model's text output does.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install -g taktiko
15
+ ```
16
+
17
+ Requires Node.js ≥ 20. Provides a single `taktiko` command.
18
+
19
+ ## Use
20
+
21
+ 1. In the Taktiko web app, open the **Connect your computer** step (onboarding, or Settings →
22
+ Computers). It shows a pairing command with a one-time token.
23
+ 2. Run it on the machine you want to connect:
24
+
25
+ ```bash
26
+ taktiko pair <pairingToken> --server https://your-taktiko-server
27
+ taktiko daemon
28
+ ```
29
+
30
+ `pair` redeems the token and saves a daemon credential to `~/.taktiko/daemon.json`. `daemon`
31
+ connects and stays running — keep it open (or run it under a process manager). It reconnects
32
+ automatically with exponential backoff if the server restarts or the network blips.
33
+
34
+ The web app's computer status flips to **online** as soon as `taktiko daemon` connects; on connect
35
+ the daemon reports which agent CLIs are on your `PATH` (claude / codex / kimi) and candidate working
36
+ directories, so the agent editor only offers what this machine actually has.
37
+
38
+ ## Commands
39
+
40
+ | Command | What it does |
41
+ |---|---|
42
+ | `taktiko pair <token> [--server <url>] [--name <name>]` | Redeem a pairing token; store this machine as a daemon. |
43
+ | `taktiko daemon [--server <url>]` | Connect to the server and run dispatched turns (default command). |
44
+ | `taktiko --version` / `taktiko --help` | Version / help. |
45
+
46
+ Config is read from `~/.taktiko/daemon.json`, or from `TAKTIKO_SERVER` + `TAKTIKO_DAEMON_TOKEN`
47
+ environment variables if that file is absent.
48
+
49
+ ## Environment overrides
50
+
51
+ | Variable | Default | Meaning |
52
+ |---|---|---|
53
+ | `TAKTIKO_SERVER` | — | Server base URL (fallback when no config file). |
54
+ | `TAKTIKO_DAEMON_TOKEN` | — | Daemon token (fallback when no config file). |
55
+ | `TAKTIKO_JOB_TIMEOUT_MS` | `600000` | Hard wall-clock cap per turn. |
56
+ | `TAKTIKO_JOB_IDLE_MS` | `180000` | Idle (no-output) cap per turn. |
57
+ | `TAKTIKO_MAX_CONCURRENT` | `20` | Max concurrent local CLI processes. |
58
+
59
+ ## Safety
60
+
61
+ - **Single instance per machine** — a pidfile (`~/.taktiko/daemon.pid`) refuses a second daemon, and
62
+ the server supersedes a stale connection, so a turn is never run twice (e.g. no double trades).
63
+ - **Bounded turns** — every job has a hard timeout and an idle watchdog, with `SIGTERM`→`SIGKILL`
64
+ escalation, so a hung CLI can't wedge a conversation.
65
+
66
+ ## License
67
+
68
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,668 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ../backend/src/daemon/cli.ts
4
+ import { execFileSync, spawn as spawn2 } from "child_process";
5
+ import { createHash } from "crypto";
6
+ import { mkdirSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync } from "fs";
7
+ import { hostname } from "os";
8
+ import { homedir } from "os";
9
+ import { dirname, join, relative } from "path";
10
+ import { Command } from "commander";
11
+ import WebSocket from "ws";
12
+
13
+ // ../backend/src/backends.ts
14
+ var CLI_CATALOG = [
15
+ {
16
+ kind: "claude",
17
+ command: "claude",
18
+ displayName: "Claude Code",
19
+ models: [
20
+ { id: "opus", displayName: "Opus" },
21
+ { id: "sonnet", displayName: "Sonnet" },
22
+ { id: "haiku", displayName: "Haiku" }
23
+ ]
24
+ },
25
+ { kind: "codex", command: "codex", displayName: "Codex", models: [] },
26
+ { kind: "kimi", command: "kimi", displayName: "Kimi", models: [] }
27
+ ];
28
+ var CLI_COMMANDS = CLI_CATALOG.map((c) => c.command);
29
+
30
+ // ../backend/src/daemon/adapters/types.ts
31
+ import { spawn } from "child_process";
32
+ import { createInterface } from "readline";
33
+ var hasFlag = (args, ...flags) => flags.some((f) => args.includes(f));
34
+ async function spawnLines(ctx, command, args, opts) {
35
+ const child = ctx.spawn(command, args, {
36
+ cwd: opts.cwd ?? void 0,
37
+ env: { ...process.env, ...opts.env ?? {} }
38
+ });
39
+ let stderr = "";
40
+ const out = createInterface({ input: child.stdout });
41
+ out.on("line", (line) => {
42
+ try {
43
+ opts.onLine(line);
44
+ } catch {
45
+ }
46
+ });
47
+ if (child.stderr) {
48
+ const err = createInterface({ input: child.stderr });
49
+ err.on("line", (line) => {
50
+ stderr += `${line}
51
+ `;
52
+ opts.onStderr?.(line);
53
+ });
54
+ }
55
+ if (opts.stdin !== void 0) {
56
+ child.stdin?.write(opts.stdin);
57
+ }
58
+ child.stdin?.end();
59
+ const exited = new Promise((resolve) => child.on("close", resolve));
60
+ const drained = new Promise((resolve) => out.on("close", () => resolve()));
61
+ const code = await exited;
62
+ await drained;
63
+ return { code, stderr };
64
+ }
65
+ function parseJsonLine(line) {
66
+ const t = line.trim();
67
+ if (!t || t[0] !== "{") return null;
68
+ try {
69
+ return JSON.parse(t);
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+
75
+ // ../backend/src/daemon/adapters/claude.ts
76
+ var claudeAdapter = async (ctx) => {
77
+ const { job } = ctx;
78
+ const a = job.agent;
79
+ const args = [...a.args];
80
+ if (!hasFlag(args, "-p", "--print")) args.push("-p");
81
+ if (!args.includes("--output-format")) args.push("--output-format", "stream-json");
82
+ if (!args.includes("--verbose")) args.push("--verbose");
83
+ if (a.model && !args.includes("--model")) args.push("--model", a.model);
84
+ if (!hasFlag(args, "--permission-mode", "--dangerously-skip-permissions")) {
85
+ args.push("--permission-mode", "bypassPermissions");
86
+ }
87
+ if (a.systemPrompt && !args.includes("--append-system-prompt")) {
88
+ args.push("--append-system-prompt", a.systemPrompt);
89
+ }
90
+ const resume = job.resumeSessionId ?? a.env?.CLAUDE_SESSION_ID ?? null;
91
+ if (resume && !args.includes("--resume")) args.push("--resume", resume);
92
+ let sessionId = resume;
93
+ let finalText = "";
94
+ const messages = [];
95
+ const onLine = (line) => {
96
+ const ev = parseJsonLine(line);
97
+ if (!ev) return;
98
+ const sid = ev.session_id ?? ev.sessionId;
99
+ if (typeof sid === "string" && sid) {
100
+ sessionId = sid;
101
+ ctx.setSessionId(sid);
102
+ }
103
+ const msg = ev.message;
104
+ if (ev.type === "assistant" && Array.isArray(msg?.content)) {
105
+ for (const block of msg.content) {
106
+ if (block?.type === "text" && typeof block.text === "string") {
107
+ messages.push(block.text);
108
+ ctx.emitContent(messages.join(""));
109
+ } else if (block?.type === "tool_use" && typeof block.name === "string") {
110
+ ctx.emitToolCall({ id: typeof block.id === "string" ? block.id : void 0, name: block.name, input: block.input });
111
+ }
112
+ }
113
+ } else if (ev.type === "result" && typeof ev.result === "string") {
114
+ finalText = ev.result;
115
+ ctx.emitContent(finalText);
116
+ }
117
+ };
118
+ const { code, stderr } = await spawnLines(ctx, a.command, args, {
119
+ cwd: a.cwd,
120
+ env: a.env,
121
+ stdin: job.query,
122
+ onLine
123
+ });
124
+ const content = finalText || messages.join("");
125
+ if (code === 0) return { status: "succeeded", content, sessionId };
126
+ return { status: "failed", content, error: stderr.trim() || `claude exited with code ${code}`, sessionId };
127
+ };
128
+
129
+ // ../backend/src/daemon/adapters/codex.ts
130
+ var CODEX_TOOL_NAMES = {
131
+ command_execution: "shell",
132
+ file_change: "edit",
133
+ mcp_tool_call: "mcp",
134
+ web_search: "web_search"
135
+ };
136
+ var codexAdapter = async (ctx) => {
137
+ const { job } = ctx;
138
+ const a = job.agent;
139
+ const args = [...a.args];
140
+ if (!hasFlag(args, "exec", "e")) args.unshift("exec");
141
+ const resume = job.resumeSessionId ?? null;
142
+ if (resume && !args.includes("resume")) {
143
+ const i = args.indexOf("exec");
144
+ args.splice(i + 1, 0, "resume", resume);
145
+ }
146
+ if (!args.includes("--json")) args.push("--json");
147
+ if (!args.includes("--skip-git-repo-check")) args.push("--skip-git-repo-check");
148
+ if (a.model && !hasFlag(args, "--model", "-m")) args.push("--model", a.model);
149
+ if (!hasFlag(args, "--sandbox", "-s", "--ask-for-approval", "-a", "--full-auto", "--dangerously-bypass-approvals-and-sandbox")) {
150
+ args.push("--dangerously-bypass-approvals-and-sandbox");
151
+ }
152
+ args.push(job.query);
153
+ let sessionId = resume;
154
+ const messages = [];
155
+ const errors = [];
156
+ const onLine = (line) => {
157
+ const ev = parseJsonLine(line);
158
+ if (!ev) return;
159
+ const tid = ev.thread_id ?? ev.session_id;
160
+ if (typeof tid === "string" && tid) {
161
+ sessionId = tid;
162
+ ctx.setSessionId(tid);
163
+ }
164
+ const item = ev.item;
165
+ if (ev.type === "item.completed" && item?.type === "agent_message" && typeof item.text === "string") {
166
+ messages.push(item.text);
167
+ ctx.emitContent(messages.join("\n"));
168
+ } else if (ev.type === "item.completed" && item && item.type !== "reasoning" && item.type !== "agent_message") {
169
+ const name = CODEX_TOOL_NAMES[item.type] ?? (typeof item.type === "string" ? item.type : "tool");
170
+ ctx.emitToolCall({ id: typeof item.id === "string" ? item.id : void 0, name, input: item });
171
+ } else if (ev.type === "error" && typeof ev.message === "string") {
172
+ errors.push(ev.message);
173
+ } else if (ev.type === "turn.failed") {
174
+ const err = ev.error;
175
+ if (err?.message) errors.push(err.message);
176
+ }
177
+ };
178
+ const { code, stderr } = await spawnLines(ctx, a.command, args, { cwd: a.cwd, env: a.env, onLine });
179
+ const content = messages.join("\n");
180
+ if (code === 0 && !errors.length) return { status: "succeeded", content, sessionId };
181
+ const error = errors.join("; ") || stderr.trim() || `codex exited with code ${code}`;
182
+ return { status: "failed", content, error, sessionId };
183
+ };
184
+
185
+ // ../backend/src/daemon/adapters/generic.ts
186
+ var genericAdapter = async (ctx) => {
187
+ const { job } = ctx;
188
+ const a = job.agent;
189
+ const args = [...a.args];
190
+ if (job.resumeSessionId) args.push("--resume", job.resumeSessionId);
191
+ args.push(job.query);
192
+ let out = "";
193
+ const { code, stderr } = await spawnLines(ctx, a.command, args, {
194
+ cwd: a.cwd,
195
+ env: a.env,
196
+ onLine: (line) => {
197
+ out += `${line}
198
+ `;
199
+ ctx.emitContent(out.trimEnd());
200
+ }
201
+ });
202
+ const content = out.trim();
203
+ if (code === 0) return { status: "succeeded", content, sessionId: job.resumeSessionId ?? null };
204
+ return { status: "failed", content, error: stderr.trim() || `process exited with code ${code}`, sessionId: null };
205
+ };
206
+
207
+ // ../backend/src/daemon/adapters/kimi.ts
208
+ var kimiAdapter = async (ctx) => {
209
+ const { job } = ctx;
210
+ const a = job.agent;
211
+ const args = [...a.args];
212
+ if (!args.includes("--output-format")) args.push("--output-format", "stream-json");
213
+ if (a.model && !hasFlag(args, "--model", "-m")) args.push("--model", a.model);
214
+ const resume = job.resumeSessionId ?? a.env?.KIMI_SESSION_ID ?? null;
215
+ if (resume && !hasFlag(args, "-r", "--resume", "--session", "-S", "--continue", "-C")) {
216
+ args.push("-r", resume);
217
+ }
218
+ if (!hasFlag(args, "-p", "--prompt")) args.push("-p", job.query);
219
+ let sessionId = resume;
220
+ let stderrAll = "";
221
+ const messages = [];
222
+ const onLine = (line) => {
223
+ const ev = parseJsonLine(line);
224
+ if (!ev) return;
225
+ if (ev.role === "assistant") {
226
+ if (typeof ev.content === "string" && ev.content) {
227
+ messages.push(ev.content);
228
+ ctx.emitContent(messages.join(""));
229
+ } else if (Array.isArray(ev.content)) {
230
+ for (const block of ev.content) {
231
+ if (block?.type === "text" && typeof block.text === "string") {
232
+ messages.push(block.text);
233
+ ctx.emitContent(messages.join(""));
234
+ }
235
+ }
236
+ }
237
+ } else if (ev.role === "meta" && ev.type === "session.resume_hint" && typeof ev.session_id === "string") {
238
+ sessionId = ev.session_id;
239
+ ctx.setSessionId(ev.session_id);
240
+ }
241
+ };
242
+ const { code, stderr } = await spawnLines(ctx, a.command, args, {
243
+ cwd: a.cwd,
244
+ env: a.env,
245
+ onLine,
246
+ onStderr: (l) => {
247
+ stderrAll += `${l}
248
+ `;
249
+ }
250
+ });
251
+ if (!sessionId || sessionId === resume) {
252
+ const m = (stderr || stderrAll).match(/kimi\s+-r\s+([A-Za-z0-9_-]+)/);
253
+ if (m) {
254
+ sessionId = m[1];
255
+ ctx.setSessionId(m[1]);
256
+ }
257
+ }
258
+ const content = messages.join("");
259
+ if (code === 0) return { status: "succeeded", content, sessionId };
260
+ return { status: "failed", content, error: stderr.trim() || `kimi exited with code ${code}`, sessionId };
261
+ };
262
+
263
+ // ../backend/src/daemon/adapters/index.ts
264
+ var BY_KIND = {
265
+ claude: claudeAdapter,
266
+ "claude-code": claudeAdapter,
267
+ claude_code: claudeAdapter,
268
+ codex: codexAdapter,
269
+ kimi: kimiAdapter,
270
+ generic: genericAdapter,
271
+ shell: genericAdapter
272
+ };
273
+ function selectAdapter(kind, command) {
274
+ if (kind && BY_KIND[kind]) return BY_KIND[kind];
275
+ const base = (command.split("/").pop() ?? command).toLowerCase();
276
+ if (base === "claude") return claudeAdapter;
277
+ if (base === "codex") return codexAdapter;
278
+ if (base === "kimi") return kimiAdapter;
279
+ return genericAdapter;
280
+ }
281
+
282
+ // ../backend/src/daemon/cli.ts
283
+ function probeInstalledClis() {
284
+ const finder = process.platform === "win32" ? "where" : "which";
285
+ return CLI_COMMANDS.filter((cmd) => {
286
+ try {
287
+ execFileSync(finder, [cmd], { stdio: "ignore" });
288
+ return true;
289
+ } catch {
290
+ return false;
291
+ }
292
+ });
293
+ }
294
+ function gatherWorkDirs() {
295
+ const home = homedir();
296
+ const isDir = (p) => {
297
+ try {
298
+ return statSync(p).isDirectory();
299
+ } catch {
300
+ return false;
301
+ }
302
+ };
303
+ const out = [home];
304
+ const seen = new Set(out);
305
+ const add = (p) => {
306
+ if (!seen.has(p) && isDir(p)) {
307
+ seen.add(p);
308
+ out.push(p);
309
+ }
310
+ };
311
+ for (const name of ["Projects", "projects", "code", "Code", "dev", "Developer", "work", "repos", "src"]) {
312
+ add(join(home, name));
313
+ }
314
+ const NOISE = /* @__PURE__ */ new Set([
315
+ "Library",
316
+ "Applications",
317
+ "Desktop",
318
+ "Documents",
319
+ "Downloads",
320
+ "Movies",
321
+ "Music",
322
+ "Pictures",
323
+ "Public",
324
+ "Templates",
325
+ "Videos",
326
+ "AppData",
327
+ "OneDrive"
328
+ ]);
329
+ try {
330
+ for (const ent of readdirSync(home, { withFileTypes: true })) {
331
+ if (ent.isDirectory() && !ent.name.startsWith(".") && !NOISE.has(ent.name)) add(join(home, ent.name));
332
+ }
333
+ } catch {
334
+ }
335
+ return out.slice(0, 50);
336
+ }
337
+ var CONFIG_PATH = join(homedir(), ".taktiko", "daemon.json");
338
+ var PID_PATH = join(homedir(), ".taktiko", "daemon.pid");
339
+ var JOB_HARD_TIMEOUT_MS = Number(process.env.TAKTIKO_JOB_TIMEOUT_MS) || 10 * 6e4;
340
+ var JOB_IDLE_TIMEOUT_MS = Number(process.env.TAKTIKO_JOB_IDLE_MS) || 3 * 6e4;
341
+ var KILL_GRACE_MS = 3e3;
342
+ var MAX_CONCURRENT = Number(process.env.TAKTIKO_MAX_CONCURRENT) || 20;
343
+ function loadConfig() {
344
+ try {
345
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
346
+ } catch {
347
+ const server = process.env.TAKTIKO_SERVER;
348
+ const daemonToken = process.env.TAKTIKO_DAEMON_TOKEN;
349
+ return server && daemonToken ? { server, daemonToken } : null;
350
+ }
351
+ }
352
+ function saveConfig(cfg) {
353
+ mkdirSync(dirname(CONFIG_PATH), { recursive: true });
354
+ writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2));
355
+ }
356
+ function acquireSingleInstanceLock() {
357
+ try {
358
+ const existing = Number(readFileSync(PID_PATH, "utf8").trim());
359
+ if (existing && existing !== process.pid) {
360
+ try {
361
+ process.kill(existing, 0);
362
+ console.error(`[daemon] already running (pid ${existing}); refusing to start a second daemon on this machine`);
363
+ process.exit(1);
364
+ } catch {
365
+ }
366
+ }
367
+ } catch {
368
+ }
369
+ mkdirSync(dirname(PID_PATH), { recursive: true });
370
+ writeFileSync(PID_PATH, String(process.pid));
371
+ }
372
+ function releaseLock() {
373
+ try {
374
+ if (Number(readFileSync(PID_PATH, "utf8").trim()) === process.pid) unlinkSync(PID_PATH);
375
+ } catch {
376
+ }
377
+ }
378
+ var sha256 = (s) => createHash("sha256").update(s).digest("hex");
379
+ async function memoryPull(job) {
380
+ const env = job.agent.env ?? {};
381
+ const storeId = env.TAKTIKO_MEMORY_STORE_ID;
382
+ const apiUrl = env.TAKTIKO_API_URL;
383
+ const token = env.TAKTIKO_AGENT_TOKEN;
384
+ if (!storeId || !apiUrl || !token) return null;
385
+ const agentDir = join(homedir(), ".taktiko", "agents", job.agent.id);
386
+ const dir = join(agentDir, "memory");
387
+ const snapshot = /* @__PURE__ */ new Map();
388
+ try {
389
+ rmSync(dir, { recursive: true, force: true });
390
+ mkdirSync(dir, { recursive: true });
391
+ const res = await fetch(`${apiUrl}/api/agent/memory?storeId=${encodeURIComponent(storeId)}`, {
392
+ headers: { authorization: `Bearer ${token}` }
393
+ });
394
+ if (res.ok) {
395
+ const data = await res.json();
396
+ for (const doc of data.docs ?? []) {
397
+ const p = join(dir, doc.path);
398
+ mkdirSync(dirname(p), { recursive: true });
399
+ writeFileSync(p, doc.content);
400
+ snapshot.set(doc.path, sha256(doc.content));
401
+ }
402
+ }
403
+ } catch {
404
+ }
405
+ return { dir, agentDir, snapshot };
406
+ }
407
+ async function memoryPush(job, mem) {
408
+ const env = job.agent.env ?? {};
409
+ const storeId = env.TAKTIKO_MEMORY_STORE_ID;
410
+ const apiUrl = env.TAKTIKO_API_URL;
411
+ const token = env.TAKTIKO_AGENT_TOKEN;
412
+ if (!storeId || !apiUrl || !token) return;
413
+ const current = /* @__PURE__ */ new Map();
414
+ const listDir = (d) => {
415
+ try {
416
+ return readdirSync(d, { withFileTypes: true });
417
+ } catch {
418
+ return [];
419
+ }
420
+ };
421
+ const walk = (d) => {
422
+ for (const e of listDir(d)) {
423
+ const full = join(d, e.name);
424
+ if (e.isDirectory()) walk(full);
425
+ else if (e.isFile()) {
426
+ try {
427
+ current.set(relative(mem.dir, full), readFileSync(full, "utf8"));
428
+ } catch {
429
+ }
430
+ }
431
+ }
432
+ };
433
+ walk(mem.dir);
434
+ const writes = [];
435
+ for (const [path, content] of current) if (mem.snapshot.get(path) !== sha256(content)) writes.push({ path, content });
436
+ const deletes = [];
437
+ for (const path of mem.snapshot.keys()) if (!current.has(path)) deletes.push(path);
438
+ if (!writes.length && !deletes.length) return;
439
+ try {
440
+ await fetch(`${apiUrl}/api/agent/memory`, {
441
+ method: "PUT",
442
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
443
+ body: JSON.stringify({ storeId, writes, deletes })
444
+ });
445
+ } catch {
446
+ }
447
+ }
448
+ var children = /* @__PURE__ */ new Map();
449
+ function killJob(jobId) {
450
+ const child = children.get(jobId);
451
+ if (!child) return;
452
+ child.kill("SIGTERM");
453
+ setTimeout(() => {
454
+ if (children.get(jobId) === child) child.kill("SIGKILL");
455
+ }, KILL_GRACE_MS);
456
+ }
457
+ function killAllJobs() {
458
+ for (const jobId of children.keys()) killJob(jobId);
459
+ }
460
+ var jobQueue = [];
461
+ var activeCount = 0;
462
+ function submitJob(ws, frame) {
463
+ jobQueue.push({ ws, frame });
464
+ pump();
465
+ }
466
+ function pump() {
467
+ while (activeCount < MAX_CONCURRENT && jobQueue.length > 0) {
468
+ const next = jobQueue.shift();
469
+ activeCount += 1;
470
+ void runJob(next.ws, next.frame, () => {
471
+ activeCount -= 1;
472
+ pump();
473
+ });
474
+ }
475
+ }
476
+ function cancelJob(jobId) {
477
+ const qi = jobQueue.findIndex((q) => q.frame.job.id === jobId);
478
+ if (qi >= 0) {
479
+ const [removed] = jobQueue.splice(qi, 1);
480
+ if (removed.ws.readyState === WebSocket.OPEN) {
481
+ removed.ws.send(JSON.stringify({ type: "daemon_job_result", jobId, status: "failed", error: "cancelled" }));
482
+ }
483
+ return;
484
+ }
485
+ killJob(jobId);
486
+ }
487
+ async function runJob(ws, frame, onDone) {
488
+ const { job } = frame;
489
+ const mem = await memoryPull(job);
490
+ job.agent.env = {
491
+ ...job.agent.env ?? {},
492
+ ...job.conversationId ? { TAKTIKO_CONVERSATION_ID: job.conversationId } : {},
493
+ ...job.threadId ? { TAKTIKO_THREAD_ID: job.threadId } : {},
494
+ ...mem ? { TAKTIKO_MEMORY_DIR: mem.dir, TAKTIKO_AGENT_DIR: mem.agentDir } : {}
495
+ };
496
+ ws.send(JSON.stringify({ type: "daemon_job_started", jobId: job.id }));
497
+ const adapter = selectAdapter(job.agent.kind, job.agent.command);
498
+ let lastContent = "";
499
+ let captured = null;
500
+ const toolCalls = [];
501
+ let resultSent = false;
502
+ let timedOut = null;
503
+ let idleTimer;
504
+ let hardTimer;
505
+ let fallbackTimer;
506
+ const clearTimers = () => {
507
+ if (idleTimer) clearTimeout(idleTimer);
508
+ if (hardTimer) clearTimeout(hardTimer);
509
+ if (fallbackTimer) clearTimeout(fallbackTimer);
510
+ };
511
+ const finish = (out) => {
512
+ if (resultSent) return;
513
+ resultSent = true;
514
+ clearTimers();
515
+ children.delete(job.id);
516
+ if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(out));
517
+ onDone();
518
+ };
519
+ const sendUpdate = () => {
520
+ if (ws.readyState !== WebSocket.OPEN) return;
521
+ ws.send(
522
+ JSON.stringify({ type: "daemon_job_update", jobId: job.id, update: { content: lastContent, toolCalls } })
523
+ );
524
+ };
525
+ const resetIdle = () => {
526
+ if (idleTimer) clearTimeout(idleTimer);
527
+ idleTimer = setTimeout(() => {
528
+ timedOut = `no output for ${Math.round(JOB_IDLE_TIMEOUT_MS / 1e3)}s`;
529
+ killJob(job.id);
530
+ }, JOB_IDLE_TIMEOUT_MS);
531
+ };
532
+ hardTimer = setTimeout(() => {
533
+ timedOut = `exceeded ${Math.round(JOB_HARD_TIMEOUT_MS / 1e3)}s`;
534
+ killJob(job.id);
535
+ fallbackTimer = setTimeout(
536
+ () => finish({ type: "daemon_job_result", jobId: job.id, status: "failed", error: `timed out: ${timedOut}` }),
537
+ KILL_GRACE_MS + 2e3
538
+ );
539
+ }, JOB_HARD_TIMEOUT_MS);
540
+ resetIdle();
541
+ const ctx = {
542
+ job,
543
+ emitContent(content) {
544
+ resetIdle();
545
+ if (content === lastContent) return;
546
+ lastContent = content;
547
+ sendUpdate();
548
+ },
549
+ emitToolCall(call) {
550
+ resetIdle();
551
+ toolCalls.push(call);
552
+ sendUpdate();
553
+ },
554
+ setSessionId(id) {
555
+ captured = id;
556
+ },
557
+ spawn(command, args, opts) {
558
+ const child = spawn2(command, args, { ...opts, stdio: ["pipe", "pipe", "pipe"] });
559
+ children.set(job.id, child);
560
+ child.on("close", () => {
561
+ if (children.get(job.id) === child) children.delete(job.id);
562
+ });
563
+ return child;
564
+ }
565
+ };
566
+ const pushMem = async () => {
567
+ if (mem) await memoryPush(job, mem);
568
+ };
569
+ adapter(ctx).then(async (result) => {
570
+ await pushMem();
571
+ const sessionId = result.sessionId ?? captured ?? void 0;
572
+ if (timedOut && result.status !== "succeeded") {
573
+ finish({ type: "daemon_job_result", jobId: job.id, status: "failed", error: `timed out: ${timedOut}`, sessionId });
574
+ return;
575
+ }
576
+ finish(
577
+ result.status === "succeeded" ? { type: "daemon_job_result", jobId: job.id, status: "succeeded", result: result.content, sessionId } : { type: "daemon_job_result", jobId: job.id, status: "failed", error: result.error ?? "failed", sessionId }
578
+ );
579
+ }).catch(async (e) => {
580
+ await pushMem();
581
+ finish({ type: "daemon_job_result", jobId: job.id, status: "failed", error: String(e) });
582
+ });
583
+ }
584
+ function runDaemon(cfg) {
585
+ acquireSingleInstanceLock();
586
+ process.on("exit", releaseLock);
587
+ for (const sig of ["SIGINT", "SIGTERM"]) {
588
+ process.on(sig, () => {
589
+ killAllJobs();
590
+ releaseLock();
591
+ process.exit(0);
592
+ });
593
+ }
594
+ const wsUrl = `${cfg.server.replace(/^http/, "ws")}/ws/daemon?token=${encodeURIComponent(cfg.daemonToken)}`;
595
+ let ping = null;
596
+ let retryMs = 1e3;
597
+ const MAX_RETRY_MS = 3e4;
598
+ function connect() {
599
+ console.log(`[daemon] connecting to ${wsUrl}`);
600
+ const ws = new WebSocket(wsUrl);
601
+ ws.on("open", () => {
602
+ console.log("[daemon] connected");
603
+ retryMs = 1e3;
604
+ ws.send(
605
+ JSON.stringify({
606
+ type: "daemon_ready",
607
+ installedClis: probeInstalledClis(),
608
+ workDirs: gatherWorkDirs(),
609
+ homeDir: homedir()
610
+ })
611
+ );
612
+ ping = setInterval(() => ws.readyState === WebSocket.OPEN && ws.send(JSON.stringify({ type: "ping" })), 25e3);
613
+ });
614
+ ws.on("message", (raw) => {
615
+ let frame;
616
+ try {
617
+ frame = JSON.parse(raw.toString());
618
+ } catch {
619
+ return;
620
+ }
621
+ if (frame.type === "daemon_job") {
622
+ submitJob(ws, frame);
623
+ } else if (frame.type === "daemon_job_cancel") {
624
+ cancelJob(frame.jobId);
625
+ }
626
+ });
627
+ ws.on("close", (code) => {
628
+ if (ping) clearInterval(ping);
629
+ if (code === 4e3) {
630
+ console.error("[daemon] another daemon on this machine took over (superseded); exiting");
631
+ killAllJobs();
632
+ releaseLock();
633
+ process.exit(0);
634
+ }
635
+ console.log(`[daemon] disconnected; retrying in ${Math.round(retryMs / 1e3)}s`);
636
+ setTimeout(connect, retryMs);
637
+ retryMs = Math.min(retryMs * 2, MAX_RETRY_MS);
638
+ });
639
+ ws.on("error", (e) => console.error("[daemon] ws error:", e.message));
640
+ }
641
+ connect();
642
+ }
643
+ var program = new Command();
644
+ program.name("taktiko").description("Taktiko daemon \u2014 runs your local agent CLIs").version("0.1.0");
645
+ program.command("pair <pairingToken>").description("redeem a pairing token from the server and store this machine as a daemon").option("--server <url>", "server base url", process.env.TAKTIKO_SERVER ?? "http://localhost:7100").option("--name <name>", "daemon name", hostname()).action(async (pairingToken, opts) => {
646
+ const res = await fetch(`${opts.server}/api/daemons/pair`, {
647
+ method: "POST",
648
+ headers: { "content-type": "application/json" },
649
+ body: JSON.stringify({ pairingToken, name: opts.name, machine: hostname() })
650
+ });
651
+ const data = await res.json();
652
+ if (!res.ok || !data.daemonToken) {
653
+ console.error("pairing failed:", data);
654
+ process.exit(1);
655
+ }
656
+ saveConfig({ server: opts.server, daemonToken: data.daemonToken });
657
+ console.log(`paired. daemonId=${data.daemonId}. config saved to ${CONFIG_PATH}`);
658
+ });
659
+ program.command("daemon", { isDefault: true }).description("connect to the server and run dispatched turns").option("--server <url>", "override server base url").action((opts) => {
660
+ const cfg = loadConfig();
661
+ if (!cfg) {
662
+ console.error(`no daemon config \u2014 run \`taktiko pair <token>\` first (or set TAKTIKO_SERVER + TAKTIKO_DAEMON_TOKEN)`);
663
+ process.exit(1);
664
+ }
665
+ runDaemon({ ...cfg, server: opts.server ?? cfg.server });
666
+ });
667
+ program.parseAsync(process.argv);
668
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../backend/src/daemon/cli.ts","../../backend/src/backends.ts","../../backend/src/daemon/adapters/types.ts","../../backend/src/daemon/adapters/claude.ts","../../backend/src/daemon/adapters/codex.ts","../../backend/src/daemon/adapters/generic.ts","../../backend/src/daemon/adapters/kimi.ts","../../backend/src/daemon/adapters/index.ts"],"sourcesContent":["#!/usr/bin/env node\n// `taktiko` — the daemon that runs on a user's machine. It pairs with the server, connects over\n// WebSocket, and for each dispatched job runs the agent's local CLI (e.g. `claude`), streaming\n// stdout back as the reply.\n//\n// Provider-specific adapters (src/daemon/adapters/) build the CLI invocation, parse the stream, and\n// capture the native session id for resume. The runner here adds the machine-side hardening:\n// single-instance locking, a per-job timeout/idle watchdog, SIGTERM→SIGKILL escalation, a\n// concurrency cap, and exponential-backoff reconnect.\nimport { type ChildProcess, execFileSync, spawn } from 'node:child_process'\nimport { createHash } from 'node:crypto'\nimport { mkdirSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { hostname } from 'node:os'\nimport { homedir } from 'node:os'\nimport { dirname, join, relative } from 'node:path'\n\nimport { Command } from 'commander'\nimport WebSocket from 'ws'\n\nimport { CLI_COMMANDS } from '../backends.js'\nimport { type RunContext, type ToolCall, selectAdapter } from './adapters/index.js'\n\n// ── machine capability probing ───────────────────────────────────────────────────────────────────\n// Reported to the server on connect so the agent editor offers only the CLIs/dirs this computer has.\n\n/** Which known agent CLIs are on PATH (resolved via which/where, so PATH shims count). */\nfunction probeInstalledClis(): string[] {\n const finder = process.platform === 'win32' ? 'where' : 'which'\n return CLI_COMMANDS.filter((cmd) => {\n try {\n execFileSync(finder, [cmd], { stdio: 'ignore' })\n return true\n } catch {\n return false\n }\n })\n}\n\n/** Candidate project directories to offer as the agent's working dir: common dev roots plus the\n * non-hidden immediate subdirectories of home. Home leads the list; capped so the picker stays sane. */\nfunction gatherWorkDirs(): string[] {\n const home = homedir()\n const isDir = (p: string): boolean => {\n try {\n return statSync(p).isDirectory()\n } catch {\n return false\n }\n }\n const out: string[] = [home]\n const seen = new Set(out)\n const add = (p: string) => {\n if (!seen.has(p) && isDir(p)) {\n seen.add(p)\n out.push(p)\n }\n }\n for (const name of ['Projects', 'projects', 'code', 'Code', 'dev', 'Developer', 'work', 'repos', 'src']) {\n add(join(home, name))\n }\n // Standard OS home folders that are never project dirs — keep the picker focused.\n const NOISE = new Set([\n 'Library', 'Applications', 'Desktop', 'Documents', 'Downloads', 'Movies', 'Music',\n 'Pictures', 'Public', 'Templates', 'Videos', 'AppData', 'OneDrive',\n ])\n try {\n for (const ent of readdirSync(home, { withFileTypes: true })) {\n if (ent.isDirectory() && !ent.name.startsWith('.') && !NOISE.has(ent.name)) add(join(home, ent.name))\n }\n } catch {\n /* unreadable home — home alone is fine */\n }\n return out.slice(0, 50)\n}\n\ninterface DaemonConfig {\n server: string\n daemonToken: string\n}\n\nconst CONFIG_PATH = join(homedir(), '.taktiko', 'daemon.json')\nconst PID_PATH = join(homedir(), '.taktiko', 'daemon.pid')\n\n// A hung CLI must never wedge the server's per-conversation serialization key, so every job is\n// bounded by a hard wall-clock cap and an idle (no-output) cap; both are env-overridable.\nconst JOB_HARD_TIMEOUT_MS = Number(process.env.TAKTIKO_JOB_TIMEOUT_MS) || 10 * 60_000\nconst JOB_IDLE_TIMEOUT_MS = Number(process.env.TAKTIKO_JOB_IDLE_MS) || 3 * 60_000\nconst KILL_GRACE_MS = 3_000\n// Cap concurrent local CLIs so one user's many conversations can't fork-bomb their machine.\nconst MAX_CONCURRENT = Number(process.env.TAKTIKO_MAX_CONCURRENT) || 20\n\nfunction loadConfig(): DaemonConfig | null {\n try {\n return JSON.parse(readFileSync(CONFIG_PATH, 'utf8')) as DaemonConfig\n } catch {\n const server = process.env.TAKTIKO_SERVER\n const daemonToken = process.env.TAKTIKO_DAEMON_TOKEN\n return server && daemonToken ? { server, daemonToken } : null\n }\n}\n\nfunction saveConfig(cfg: DaemonConfig): void {\n mkdirSync(dirname(CONFIG_PATH), { recursive: true })\n writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2))\n}\n\n// ---- single-instance lock ----\n// Two daemons sharing one token would both pull jobs → double execution (e.g. double trades). The\n// pidfile blocks a second local daemon; the server independently supersedes a stale connection.\nfunction acquireSingleInstanceLock(): void {\n try {\n const existing = Number(readFileSync(PID_PATH, 'utf8').trim())\n if (existing && existing !== process.pid) {\n try {\n process.kill(existing, 0) // throws if the pid is gone\n console.error(`[daemon] already running (pid ${existing}); refusing to start a second daemon on this machine`)\n process.exit(1)\n } catch {\n /* stale pidfile — overwrite it */\n }\n }\n } catch {\n /* no pidfile yet — first run */\n }\n mkdirSync(dirname(PID_PATH), { recursive: true })\n writeFileSync(PID_PATH, String(process.pid))\n}\n\nfunction releaseLock(): void {\n try {\n if (Number(readFileSync(PID_PATH, 'utf8').trim()) === process.pid) unlinkSync(PID_PATH)\n } catch {\n /* ignore */\n }\n}\n\ntype ResultOut =\n | { type: 'daemon_job_result'; jobId: string; status: 'succeeded'; result: string; sessionId?: string }\n | { type: 'daemon_job_result'; jobId: string; status: 'failed'; error: string; sessionId?: string }\n\ninterface JobFrame {\n type: 'daemon_job'\n job: {\n id: string\n query: string\n conversationId: string | null\n threadId: string | null\n resumeSessionId: string | null\n agent: {\n id: string\n name: string\n kind: string\n command: string\n args: string[]\n model: string | null\n cwd: string | null\n env: Record<string, string> | null\n systemPrompt: string | null\n }\n }\n}\n\n// ── memory sync ──────────────────────────────────────────────────────────────────────────────────\n// Server-side memory is a versioned doc store; with a local daemon we mirror the turn's store into a\n// dedicated agent dir the CLI reads/writes with normal file tools, then push the diff back. The agent's\n// own skills live alongside (the agent dir, $TAKTIKO_AGENT_DIR); workspace skills live in the env's\n// working dir — both reachable by the CLI, both meant to land in the system prompt's context.\nconst sha256 = (s: string) => createHash('sha256').update(s).digest('hex')\n\ninterface MemoryCtx {\n dir: string // <agentDir>/memory — the mirrored store\n agentDir: string // ~/.taktiko/agents/<id> — memory + the agent's own skills\n snapshot: Map<string, string> // store-relative path → sha256(content) as pulled\n}\n\n/** Pull the turn's memory store into the agent dir before the CLI runs. Resilient: never throws. */\nasync function memoryPull(job: JobFrame['job']): Promise<MemoryCtx | null> {\n const env = job.agent.env ?? {}\n const storeId = env.TAKTIKO_MEMORY_STORE_ID\n const apiUrl = env.TAKTIKO_API_URL\n const token = env.TAKTIKO_AGENT_TOKEN\n if (!storeId || !apiUrl || !token) return null\n const agentDir = join(homedir(), '.taktiko', 'agents', job.agent.id)\n const dir = join(agentDir, 'memory')\n const snapshot = new Map<string, string>()\n try {\n rmSync(dir, { recursive: true, force: true }) // mirror exactly — reflect server-side deletes\n mkdirSync(dir, { recursive: true })\n const res = await fetch(`${apiUrl}/api/agent/memory?storeId=${encodeURIComponent(storeId)}`, {\n headers: { authorization: `Bearer ${token}` },\n })\n if (res.ok) {\n const data = (await res.json()) as { docs?: { path: string; content: string }[] }\n for (const doc of data.docs ?? []) {\n const p = join(dir, doc.path)\n mkdirSync(dirname(p), { recursive: true })\n writeFileSync(p, doc.content)\n snapshot.set(doc.path, sha256(doc.content))\n }\n }\n } catch {\n /* server unreachable — run with whatever is (or isn't) on disk */\n }\n return { dir, agentDir, snapshot }\n}\n\n/** Push back any memory the CLI changed in the agent dir. Resilient: never throws. */\nasync function memoryPush(job: JobFrame['job'], mem: MemoryCtx): Promise<void> {\n const env = job.agent.env ?? {}\n const storeId = env.TAKTIKO_MEMORY_STORE_ID\n const apiUrl = env.TAKTIKO_API_URL\n const token = env.TAKTIKO_AGENT_TOKEN\n if (!storeId || !apiUrl || !token) return\n const current = new Map<string, string>()\n const listDir = (d: string) => {\n try {\n return readdirSync(d, { withFileTypes: true })\n } catch {\n return []\n }\n }\n const walk = (d: string): void => {\n for (const e of listDir(d)) {\n const full = join(d, e.name)\n if (e.isDirectory()) walk(full)\n else if (e.isFile()) {\n try {\n current.set(relative(mem.dir, full), readFileSync(full, 'utf8'))\n } catch {\n /* skip unreadable */\n }\n }\n }\n }\n walk(mem.dir)\n const writes: { path: string; content: string }[] = []\n for (const [path, content] of current) if (mem.snapshot.get(path) !== sha256(content)) writes.push({ path, content })\n const deletes: string[] = []\n for (const path of mem.snapshot.keys()) if (!current.has(path)) deletes.push(path)\n if (!writes.length && !deletes.length) return\n try {\n await fetch(`${apiUrl}/api/agent/memory`, {\n method: 'PUT',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },\n body: JSON.stringify({ storeId, writes, deletes }),\n })\n } catch {\n /* best-effort */\n }\n}\n\nconst children = new Map<string, ChildProcess>()\n\n/** SIGTERM a job's child, escalating to SIGKILL if it ignores the term within the grace window. */\nfunction killJob(jobId: string): void {\n const child = children.get(jobId)\n if (!child) return\n child.kill('SIGTERM')\n setTimeout(() => {\n if (children.get(jobId) === child) child.kill('SIGKILL')\n }, KILL_GRACE_MS)\n}\n\nfunction killAllJobs(): void {\n for (const jobId of children.keys()) killJob(jobId)\n}\n\n// ---- concurrency cap ----\nconst jobQueue: Array<{ ws: WebSocket; frame: JobFrame }> = []\nlet activeCount = 0\n\nfunction submitJob(ws: WebSocket, frame: JobFrame): void {\n jobQueue.push({ ws, frame })\n pump()\n}\n\nfunction pump(): void {\n while (activeCount < MAX_CONCURRENT && jobQueue.length > 0) {\n const next = jobQueue.shift()!\n activeCount += 1\n void runJob(next.ws, next.frame, () => {\n activeCount -= 1\n pump()\n })\n }\n}\n\n/** Cancel a job whether it's still queued (drop it, report failed) or already running (kill it). */\nfunction cancelJob(jobId: string): void {\n const qi = jobQueue.findIndex((q) => q.frame.job.id === jobId)\n if (qi >= 0) {\n const [removed] = jobQueue.splice(qi, 1)\n if (removed.ws.readyState === WebSocket.OPEN) {\n removed.ws.send(JSON.stringify({ type: 'daemon_job_result', jobId, status: 'failed', error: 'cancelled' }))\n }\n return\n }\n killJob(jobId)\n}\n\n// Run a turn through the provider adapter selected by the agent's `kind`. The adapter builds the\n// CLI invocation, parses the provider's stream into cumulative content + tool calls, and captures\n// the native session id. Exactly one result frame is emitted, even on timeout, so the server's\n// serialization key is always freed.\nasync function runJob(ws: WebSocket, frame: JobFrame, onDone: () => void): Promise<void> {\n const { job } = frame\n // Mirror the turn's memory store into the agent dir before the CLI runs (pushed back after).\n const mem = await memoryPull(job)\n // Expose this turn's conversation/thread (so the agent can schedule itself \"back here\" via\n // POST /api/agent/schedules) plus the agent/memory dirs the CLI reads/writes.\n job.agent.env = {\n ...(job.agent.env ?? {}),\n ...(job.conversationId ? { TAKTIKO_CONVERSATION_ID: job.conversationId } : {}),\n ...(job.threadId ? { TAKTIKO_THREAD_ID: job.threadId } : {}),\n ...(mem ? { TAKTIKO_MEMORY_DIR: mem.dir, TAKTIKO_AGENT_DIR: mem.agentDir } : {}),\n }\n ws.send(JSON.stringify({ type: 'daemon_job_started', jobId: job.id }))\n\n const adapter = selectAdapter(job.agent.kind, job.agent.command)\n let lastContent = ''\n let captured: string | null = null\n const toolCalls: ToolCall[] = []\n\n let resultSent = false\n let timedOut: string | null = null\n let idleTimer: ReturnType<typeof setTimeout> | undefined\n let hardTimer: ReturnType<typeof setTimeout> | undefined\n let fallbackTimer: ReturnType<typeof setTimeout> | undefined\n\n const clearTimers = (): void => {\n if (idleTimer) clearTimeout(idleTimer)\n if (hardTimer) clearTimeout(hardTimer)\n if (fallbackTimer) clearTimeout(fallbackTimer)\n }\n const finish = (out: ResultOut): void => {\n if (resultSent) return\n resultSent = true\n clearTimers()\n children.delete(job.id)\n if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(out))\n onDone()\n }\n const sendUpdate = (): void => {\n if (ws.readyState !== WebSocket.OPEN) return\n ws.send(\n JSON.stringify({ type: 'daemon_job_update', jobId: job.id, update: { content: lastContent, toolCalls } }),\n )\n }\n const resetIdle = (): void => {\n if (idleTimer) clearTimeout(idleTimer)\n idleTimer = setTimeout(() => {\n timedOut = `no output for ${Math.round(JOB_IDLE_TIMEOUT_MS / 1000)}s`\n killJob(job.id)\n }, JOB_IDLE_TIMEOUT_MS)\n }\n hardTimer = setTimeout(() => {\n timedOut = `exceeded ${Math.round(JOB_HARD_TIMEOUT_MS / 1000)}s`\n killJob(job.id)\n // If killing the child doesn't make the adapter settle, free the key anyway.\n fallbackTimer = setTimeout(\n () => finish({ type: 'daemon_job_result', jobId: job.id, status: 'failed', error: `timed out: ${timedOut}` }),\n KILL_GRACE_MS + 2_000,\n )\n }, JOB_HARD_TIMEOUT_MS)\n resetIdle()\n\n const ctx: RunContext = {\n job,\n emitContent(content) {\n resetIdle()\n if (content === lastContent) return\n lastContent = content\n sendUpdate()\n },\n emitToolCall(call) {\n resetIdle()\n toolCalls.push(call)\n sendUpdate()\n },\n setSessionId(id) {\n captured = id\n },\n spawn(command, args, opts) {\n const child = spawn(command, args, { ...opts, stdio: ['pipe', 'pipe', 'pipe'] })\n children.set(job.id, child)\n child.on('close', () => {\n if (children.get(job.id) === child) children.delete(job.id)\n })\n return child\n },\n }\n\n const pushMem = async (): Promise<void> => {\n if (mem) await memoryPush(job, mem)\n }\n adapter(ctx)\n .then(async (result) => {\n await pushMem() // persist whatever the CLI wrote to its memory dir before settling the turn\n const sessionId = result.sessionId ?? captured ?? undefined\n if (timedOut && result.status !== 'succeeded') {\n finish({ type: 'daemon_job_result', jobId: job.id, status: 'failed', error: `timed out: ${timedOut}`, sessionId })\n return\n }\n finish(\n result.status === 'succeeded'\n ? { type: 'daemon_job_result', jobId: job.id, status: 'succeeded', result: result.content, sessionId }\n : { type: 'daemon_job_result', jobId: job.id, status: 'failed', error: result.error ?? 'failed', sessionId },\n )\n })\n .catch(async (e) => {\n await pushMem()\n finish({ type: 'daemon_job_result', jobId: job.id, status: 'failed', error: String(e) })\n })\n}\n\nfunction runDaemon(cfg: DaemonConfig): void {\n acquireSingleInstanceLock()\n process.on('exit', releaseLock)\n for (const sig of ['SIGINT', 'SIGTERM'] as const) {\n process.on(sig, () => {\n killAllJobs()\n releaseLock()\n process.exit(0)\n })\n }\n\n const wsUrl = `${cfg.server.replace(/^http/, 'ws')}/ws/daemon?token=${encodeURIComponent(cfg.daemonToken)}`\n let ping: ReturnType<typeof setInterval> | null = null\n let retryMs = 1000\n const MAX_RETRY_MS = 30_000\n\n function connect(): void {\n console.log(`[daemon] connecting to ${wsUrl}`)\n const ws = new WebSocket(wsUrl)\n\n ws.on('open', () => {\n console.log('[daemon] connected')\n retryMs = 1000 // reset backoff on a successful connection\n ws.send(\n JSON.stringify({\n type: 'daemon_ready',\n installedClis: probeInstalledClis(),\n workDirs: gatherWorkDirs(),\n homeDir: homedir(),\n }),\n )\n ping = setInterval(() => ws.readyState === WebSocket.OPEN && ws.send(JSON.stringify({ type: 'ping' })), 25_000)\n })\n ws.on('message', (raw: Buffer) => {\n let frame: { type: string } & Record<string, unknown>\n try {\n frame = JSON.parse(raw.toString())\n } catch {\n return\n }\n if (frame.type === 'daemon_job') {\n submitJob(ws, frame as unknown as JobFrame)\n } else if (frame.type === 'daemon_job_cancel') {\n cancelJob(frame.jobId as string)\n }\n })\n ws.on('close', (code: number) => {\n if (ping) clearInterval(ping)\n if (code === 4000) {\n console.error('[daemon] another daemon on this machine took over (superseded); exiting')\n killAllJobs()\n releaseLock()\n process.exit(0)\n }\n console.log(`[daemon] disconnected; retrying in ${Math.round(retryMs / 1000)}s`)\n setTimeout(connect, retryMs)\n retryMs = Math.min(retryMs * 2, MAX_RETRY_MS)\n })\n ws.on('error', (e: Error) => console.error('[daemon] ws error:', e.message))\n }\n\n connect()\n}\n\nconst program = new Command()\nprogram\n .name('taktiko')\n .description('Taktiko daemon — runs your local agent CLIs')\n // Stamped at publish time by the @taktiko/cli build (tsup define); '0.0.0-dev' when run from source.\n .version(process.env.TAKTIKO_CLI_VERSION ?? '0.0.0-dev')\n\nprogram\n .command('pair <pairingToken>')\n .description('redeem a pairing token from the server and store this machine as a daemon')\n .option('--server <url>', 'server base url', process.env.TAKTIKO_SERVER ?? 'http://localhost:7100')\n .option('--name <name>', 'daemon name', hostname())\n .action(async (pairingToken: string, opts: { server: string; name: string }) => {\n const res = await fetch(`${opts.server}/api/daemons/pair`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ pairingToken, name: opts.name, machine: hostname() }),\n })\n const data = (await res.json()) as { daemonToken?: string; daemonId?: string; error?: string }\n if (!res.ok || !data.daemonToken) {\n console.error('pairing failed:', data)\n process.exit(1)\n }\n saveConfig({ server: opts.server, daemonToken: data.daemonToken })\n console.log(`paired. daemonId=${data.daemonId}. config saved to ${CONFIG_PATH}`)\n })\n\nprogram\n .command('daemon', { isDefault: true })\n .description('connect to the server and run dispatched turns')\n .option('--server <url>', 'override server base url')\n .action((opts: { server?: string }) => {\n const cfg = loadConfig()\n if (!cfg) {\n console.error(`no daemon config — run \\`taktiko pair <token>\\` first (or set TAKTIKO_SERVER + TAKTIKO_DAEMON_TOKEN)`)\n process.exit(1)\n }\n runDaemon({ ...cfg, server: opts.server ?? cfg.server })\n })\n\nprogram.parseAsync(process.argv)\n","// Backend presets — the fixed set of provider+model combinations an agent can run as (adapted from\n// the reference IM). Each agent owns its backend; picking one rewrites the agent's runtime columns\n// (kind/command/args/model). The user picks a model directly — no free-form command, no `generic`.\n//\n// taktiko's adapters inject `--model` from the agent's `model` column (see daemon/adapters/*), so a\n// preset only needs kind + command + model; `args` stays empty. A null model = the CLI's own default\n// (the adapter then injects no `--model`, so fast-moving CLIs like codex/kimi don't get pinned stale).\n\nexport interface BackendPreset {\n id: string\n displayName: string\n kind: string\n command: string\n args: string[]\n model: string | null\n}\n\nexport const BACKEND_PRESETS: BackendPreset[] = [\n { id: 'claude-opus', displayName: 'Claude Code · Opus', kind: 'claude', command: 'claude', args: [], model: 'opus' },\n { id: 'claude-sonnet', displayName: 'Claude Code · Sonnet', kind: 'claude', command: 'claude', args: [], model: 'sonnet' },\n { id: 'codex', displayName: 'Codex', kind: 'codex', command: 'codex', args: [], model: null },\n { id: 'kimi', displayName: 'Kimi', kind: 'kimi', command: 'kimi', args: [], model: null },\n]\n\nexport const DEFAULT_BACKEND_ID = 'claude-sonnet'\n\nexport function getBackendPreset(id: string | null | undefined): BackendPreset | null {\n if (!id) return null\n return BACKEND_PRESETS.find((b) => b.id === id) ?? null\n}\n\n// ── CLI catalog ────────────────────────────────────────────────────────────────────────────────\n// The agent editor picks a CLI first — constrained to the ones actually installed on the chosen\n// computer (the daemon probes its PATH and reports them) — then a model from that CLI's list. This is\n// the two-level decomposition of the flat presets above. `command` doubles as the binary probed on\n// PATH. An empty `models` array = the CLI's own default (no `--model`), for single-line / fast-moving\n// CLIs like codex & kimi (avoids pinning a version that goes stale).\nexport interface CliModelOption {\n id: string // stored on agent.model\n displayName: string\n}\nexport interface CliOption {\n kind: string // adapter selector + stored on agent.kind\n command: string // executable the daemon spawns + the binary probed on PATH\n displayName: string\n models: CliModelOption[]\n}\n\nexport const CLI_CATALOG: CliOption[] = [\n {\n kind: 'claude',\n command: 'claude',\n displayName: 'Claude Code',\n models: [\n { id: 'opus', displayName: 'Opus' },\n { id: 'sonnet', displayName: 'Sonnet' },\n { id: 'haiku', displayName: 'Haiku' },\n ],\n },\n { kind: 'codex', command: 'codex', displayName: 'Codex', models: [] },\n { kind: 'kimi', command: 'kimi', displayName: 'Kimi', models: [] },\n]\n\n/** The CLI binaries the daemon probes for on PATH. */\nexport const CLI_COMMANDS: string[] = CLI_CATALOG.map((c) => c.command)\n","// Provider adapter contract for the daemon. Each adapter knows how to drive one family of agent\n// CLIs (Claude Code, codex, kimi, …) in non-interactive mode: build the right argv, parse the\n// provider's streaming output into cumulative text + tool calls, and capture the provider-native\n// session id so the next turn can resume. Modeled on botapp/multica/kitty's per-provider runners.\nimport { type ChildProcess, spawn, type SpawnOptionsWithoutStdio } from 'node:child_process'\nimport { createInterface } from 'node:readline'\n\nexport interface AdapterJob {\n id: string\n query: string\n resumeSessionId: string | null\n agent: {\n kind: string\n command: string\n args: string[]\n model: string | null\n cwd: string | null\n env: Record<string, string> | null\n systemPrompt: string | null\n }\n}\n\n/** A tool invocation surfaced to the UI as the turn runs (rendered as a chip on the message). */\nexport interface ToolCall {\n id?: string\n name: string\n input?: unknown\n}\n\nexport interface RunContext {\n job: AdapterJob\n /** Stream a cumulative content update to the server (it overwrites the assistant message). */\n emitContent(content: string): void\n /** Append a tool call to the cumulative list streamed alongside content (for live tool chips). */\n emitToolCall(call: ToolCall): void\n /** Record the provider-native session id (returned in the result for next-turn resume). */\n setSessionId(id: string): void\n /** Spawn a child registered for cancellation (SIGTERM on daemon_job_cancel). */\n spawn(command: string, args: string[], opts: SpawnOptionsWithoutStdio): ChildProcess\n}\n\nexport interface AdapterResult {\n status: 'succeeded' | 'failed'\n content: string\n error?: string\n sessionId: string | null\n}\n\nexport type Adapter = (ctx: RunContext) => Promise<AdapterResult>\n\nexport const hasFlag = (args: string[], ...flags: string[]): boolean => flags.some((f) => args.includes(f))\n\n/** Spawn a child and stream stdout/stderr line-by-line; resolves once both the process has exited\n * and stdout has been fully drained (so the last JSON line is never dropped). */\nexport async function spawnLines(\n ctx: RunContext,\n command: string,\n args: string[],\n opts: {\n cwd?: string | null\n env?: Record<string, string> | null\n stdin?: string // written then closed; omit to just close stdin\n onLine: (line: string) => void\n onStderr?: (line: string) => void\n },\n): Promise<{ code: number | null; stderr: string }> {\n const child = ctx.spawn(command, args, {\n cwd: opts.cwd ?? undefined,\n env: { ...process.env, ...(opts.env ?? {}) },\n })\n let stderr = ''\n const out = createInterface({ input: child.stdout! })\n out.on('line', (line) => {\n try {\n opts.onLine(line)\n } catch {\n /* a malformed line must not kill the run */\n }\n })\n if (child.stderr) {\n const err = createInterface({ input: child.stderr })\n err.on('line', (line) => {\n stderr += `${line}\\n`\n opts.onStderr?.(line)\n })\n }\n if (opts.stdin !== undefined) {\n child.stdin?.write(opts.stdin)\n }\n child.stdin?.end()\n\n const exited = new Promise<number | null>((resolve) => child.on('close', resolve))\n const drained = new Promise<void>((resolve) => out.on('close', () => resolve()))\n const code = await exited\n await drained\n return { code, stderr }\n}\n\n/** Parse a line as JSON only if it looks like a JSON object (skips human-readable noise). */\nexport function parseJsonLine(line: string): Record<string, unknown> | null {\n const t = line.trim()\n if (!t || t[0] !== '{') return null\n try {\n return JSON.parse(t) as Record<string, unknown>\n } catch {\n return null\n }\n}\n\nexport { spawn } // re-export for the runner\n","// Claude Code adapter: `claude -p --output-format stream-json --verbose [...] [--resume <sid>]`,\n// prompt over stdin (avoids argv length limits). Parses the stream-json events, accumulates\n// assistant text, and captures session_id (present on every event) for next-turn --resume.\nimport { type Adapter, hasFlag, parseJsonLine, spawnLines } from './types.js'\n\nexport const claudeAdapter: Adapter = async (ctx) => {\n const { job } = ctx\n const a = job.agent\n const args = [...a.args]\n\n if (!hasFlag(args, '-p', '--print')) args.push('-p')\n if (!args.includes('--output-format')) args.push('--output-format', 'stream-json')\n if (!args.includes('--verbose')) args.push('--verbose')\n if (a.model && !args.includes('--model')) args.push('--model', a.model)\n if (!hasFlag(args, '--permission-mode', '--dangerously-skip-permissions')) {\n args.push('--permission-mode', 'bypassPermissions')\n }\n if (a.systemPrompt && !args.includes('--append-system-prompt')) {\n args.push('--append-system-prompt', a.systemPrompt)\n }\n const resume = job.resumeSessionId ?? a.env?.CLAUDE_SESSION_ID ?? null\n if (resume && !args.includes('--resume')) args.push('--resume', resume)\n\n let sessionId: string | null = resume\n let finalText = ''\n const messages: string[] = []\n\n const onLine = (line: string): void => {\n const ev = parseJsonLine(line)\n if (!ev) return\n const sid = (ev.session_id ?? ev.sessionId) as string | undefined\n if (typeof sid === 'string' && sid) {\n sessionId = sid\n ctx.setSessionId(sid)\n }\n const msg = ev.message as { content?: unknown } | undefined\n if (ev.type === 'assistant' && Array.isArray(msg?.content)) {\n for (const block of msg.content as Array<Record<string, unknown>>) {\n if (block?.type === 'text' && typeof block.text === 'string') {\n messages.push(block.text)\n ctx.emitContent(messages.join(''))\n } else if (block?.type === 'tool_use' && typeof block.name === 'string') {\n ctx.emitToolCall({ id: typeof block.id === 'string' ? block.id : undefined, name: block.name, input: block.input })\n }\n }\n } else if (ev.type === 'result' && typeof ev.result === 'string') {\n finalText = ev.result\n ctx.emitContent(finalText)\n }\n }\n\n const { code, stderr } = await spawnLines(ctx, a.command, args, {\n cwd: a.cwd,\n env: a.env,\n stdin: job.query,\n onLine,\n })\n\n const content = finalText || messages.join('')\n if (code === 0) return { status: 'succeeded', content, sessionId }\n return { status: 'failed', content, error: stderr.trim() || `claude exited with code ${code}`, sessionId }\n}\n","// Codex adapter: `codex exec [resume <thread>] --json --skip-git-repo-check\n// --dangerously-bypass-approvals-and-sandbox [--model X] <query>`. Parses the NDJSON event\n// stream; the thread id (from thread.started) is the session handle reused via `exec resume`.\nimport { type Adapter, hasFlag, parseJsonLine, spawnLines } from './types.js'\n\n// Friendlier chip labels for codex's structured item types (anything else uses the raw type).\nconst CODEX_TOOL_NAMES: Record<string, string> = {\n command_execution: 'shell',\n file_change: 'edit',\n mcp_tool_call: 'mcp',\n web_search: 'web_search',\n}\n\nexport const codexAdapter: Adapter = async (ctx) => {\n const { job } = ctx\n const a = job.agent\n const args = [...a.args]\n\n if (!hasFlag(args, 'exec', 'e')) args.unshift('exec')\n const resume = job.resumeSessionId ?? null\n if (resume && !args.includes('resume')) {\n const i = args.indexOf('exec')\n args.splice(i + 1, 0, 'resume', resume) // codex exec resume <thread_id>\n }\n if (!args.includes('--json')) args.push('--json')\n if (!args.includes('--skip-git-repo-check')) args.push('--skip-git-repo-check')\n if (a.model && !hasFlag(args, '--model', '-m')) args.push('--model', a.model)\n if (!hasFlag(args, '--sandbox', '-s', '--ask-for-approval', '-a', '--full-auto', '--dangerously-bypass-approvals-and-sandbox')) {\n args.push('--dangerously-bypass-approvals-and-sandbox')\n }\n args.push(job.query)\n\n let sessionId: string | null = resume\n const messages: string[] = []\n const errors: string[] = []\n\n const onLine = (line: string): void => {\n const ev = parseJsonLine(line)\n if (!ev) return\n const tid = (ev.thread_id ?? ev.session_id) as string | undefined\n if (typeof tid === 'string' && tid) {\n sessionId = tid\n ctx.setSessionId(tid)\n }\n const item = ev.item as Record<string, unknown> | undefined\n if (ev.type === 'item.completed' && item?.type === 'agent_message' && typeof item.text === 'string') {\n messages.push(item.text)\n ctx.emitContent(messages.join('\\n'))\n } else if (ev.type === 'item.completed' && item && item.type !== 'reasoning' && item.type !== 'agent_message') {\n // command_execution / file_change / mcp_tool_call / web_search → a tool chip.\n const name = CODEX_TOOL_NAMES[item.type as string] ?? (typeof item.type === 'string' ? item.type : 'tool')\n ctx.emitToolCall({ id: typeof item.id === 'string' ? item.id : undefined, name, input: item })\n } else if (ev.type === 'error' && typeof ev.message === 'string') {\n errors.push(ev.message)\n } else if (ev.type === 'turn.failed') {\n const err = ev.error as { message?: string } | undefined\n if (err?.message) errors.push(err.message)\n }\n }\n\n const { code, stderr } = await spawnLines(ctx, a.command, args, { cwd: a.cwd, env: a.env, onLine })\n\n const content = messages.join('\\n')\n if (code === 0 && !errors.length) return { status: 'succeeded', content, sessionId }\n const error = errors.join('; ') || stderr.trim() || `codex exited with code ${code}`\n return { status: 'failed', content, error, sessionId }\n}\n","// Generic stdio adapter — the fallback for any command without a provider-specific adapter.\n// Runs `<command> <args...> [--resume <id>] <query>` and streams raw stdout as the reply. No\n// structured parsing, so no native session capture (the server still threads --resume through).\nimport { type Adapter, spawnLines } from './types.js'\n\nexport const genericAdapter: Adapter = async (ctx) => {\n const { job } = ctx\n const a = job.agent\n const args = [...a.args]\n if (job.resumeSessionId) args.push('--resume', job.resumeSessionId)\n args.push(job.query)\n\n let out = ''\n const { code, stderr } = await spawnLines(ctx, a.command, args, {\n cwd: a.cwd,\n env: a.env,\n onLine: (line) => {\n out += `${line}\\n`\n ctx.emitContent(out.trimEnd())\n },\n })\n\n const content = out.trim()\n if (code === 0) return { status: 'succeeded', content, sessionId: job.resumeSessionId ?? null }\n return { status: 'failed', content, error: stderr.trim() || `process exited with code ${code}`, sessionId: null }\n}\n","// Kimi adapter: `kimi -p \"<prompt>\" --output-format stream-json [-r <sid>] [--model X]`.\n// Parses the stream-json events; the session id arrives as a meta `session.resume_hint` event\n// (with a stderr \"kimi -r <id>\" fallback) and is reused via `-r` on the next turn.\nimport { type Adapter, hasFlag, parseJsonLine, spawnLines } from './types.js'\n\nexport const kimiAdapter: Adapter = async (ctx) => {\n const { job } = ctx\n const a = job.agent\n const args = [...a.args]\n\n if (!args.includes('--output-format')) args.push('--output-format', 'stream-json')\n if (a.model && !hasFlag(args, '--model', '-m')) args.push('--model', a.model)\n const resume = job.resumeSessionId ?? a.env?.KIMI_SESSION_ID ?? null\n if (resume && !hasFlag(args, '-r', '--resume', '--session', '-S', '--continue', '-C')) {\n args.push('-r', resume)\n }\n if (!hasFlag(args, '-p', '--prompt')) args.push('-p', job.query)\n\n let sessionId: string | null = resume\n let stderrAll = ''\n const messages: string[] = []\n\n const onLine = (line: string): void => {\n const ev = parseJsonLine(line)\n if (!ev) return\n if (ev.role === 'assistant') {\n if (typeof ev.content === 'string' && ev.content) {\n messages.push(ev.content)\n ctx.emitContent(messages.join(''))\n } else if (Array.isArray(ev.content)) {\n for (const block of ev.content as Array<Record<string, unknown>>) {\n if (block?.type === 'text' && typeof block.text === 'string') {\n messages.push(block.text)\n ctx.emitContent(messages.join(''))\n }\n }\n }\n } else if (ev.role === 'meta' && ev.type === 'session.resume_hint' && typeof ev.session_id === 'string') {\n sessionId = ev.session_id\n ctx.setSessionId(ev.session_id)\n }\n }\n\n const { code, stderr } = await spawnLines(ctx, a.command, args, {\n cwd: a.cwd,\n env: a.env,\n onLine,\n onStderr: (l) => {\n stderrAll += `${l}\\n`\n },\n })\n\n if (!sessionId || sessionId === resume) {\n const m = (stderr || stderrAll).match(/kimi\\s+-r\\s+([A-Za-z0-9_-]+)/)\n if (m) {\n sessionId = m[1]\n ctx.setSessionId(m[1])\n }\n }\n\n const content = messages.join('')\n if (code === 0) return { status: 'succeeded', content, sessionId }\n return { status: 'failed', content, error: stderr.trim() || `kimi exited with code ${code}`, sessionId }\n}\n","// Adapter registry — pick a provider adapter by the agent's `kind` (the migration's \"adapter\n// selector\"), falling back to the command name, then to the generic stdio runner.\nimport { claudeAdapter } from './claude.js'\nimport { codexAdapter } from './codex.js'\nimport { genericAdapter } from './generic.js'\nimport { kimiAdapter } from './kimi.js'\nimport type { Adapter } from './types.js'\n\nexport type { Adapter, AdapterJob, AdapterResult, RunContext, ToolCall } from './types.js'\n\nconst BY_KIND: Record<string, Adapter> = {\n claude: claudeAdapter,\n 'claude-code': claudeAdapter,\n claude_code: claudeAdapter,\n codex: codexAdapter,\n kimi: kimiAdapter,\n generic: genericAdapter,\n shell: genericAdapter,\n}\n\n/** Resolve an adapter for an agent. `kind` wins; otherwise infer from the command basename. */\nexport function selectAdapter(kind: string | null | undefined, command: string): Adapter {\n if (kind && BY_KIND[kind]) return BY_KIND[kind]\n const base = (command.split('/').pop() ?? command).toLowerCase()\n if (base === 'claude') return claudeAdapter\n if (base === 'codex') return codexAdapter\n if (base === 'kimi') return kimiAdapter\n return genericAdapter\n}\n"],"mappings":"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","names":["spawn","spawn"]}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "taktiko",
3
+ "version": "0.1.0",
4
+ "description": "Taktiko daemon — pairs your machine with a Taktiko server and runs your local agent CLIs (Claude Code / Codex / Kimi) for each dispatched turn.",
5
+ "type": "module",
6
+ "bin": {
7
+ "taktiko": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "engines": {
14
+ "node": ">=20"
15
+ },
16
+ "scripts": {
17
+ "build": "tsup",
18
+ "dev": "tsup --watch",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "keywords": [
22
+ "taktiko",
23
+ "cli",
24
+ "daemon",
25
+ "ai",
26
+ "agent",
27
+ "claude",
28
+ "codex",
29
+ "kimi"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/wangdinglu/taktiko.git",
34
+ "directory": "cli"
35
+ },
36
+ "homepage": "https://github.com/wangdinglu/taktiko#readme",
37
+ "license": "MIT",
38
+ "dependencies": {
39
+ "commander": "^13.0.0",
40
+ "ws": "^8.18.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^22.10.0",
44
+ "@types/ws": "^8.5.13",
45
+ "tsup": "^8.4.0",
46
+ "typescript": "^5.8.0"
47
+ }
48
+ }