@csdwd/ai-teams-agent 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.
Files changed (2) hide show
  1. package/dist/index.js +1114 -0
  2. package/package.json +43 -0
package/dist/index.js ADDED
@@ -0,0 +1,1114 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { randomUUID as randomUUID2 } from "node:crypto";
5
+ import { fileURLToPath } from "node:url";
6
+ import WebSocket2 from "ws";
7
+
8
+ // src/config.ts
9
+ import path2 from "node:path";
10
+
11
+ // src/setup.ts
12
+ import fs from "node:fs";
13
+ import path from "node:path";
14
+ import os from "node:os";
15
+ import { createInterface } from "node:readline/promises";
16
+ import { stdin as input, stdout as output } from "node:process";
17
+ var CONFIG_DIR = path.join(os.homedir(), ".ai-teams");
18
+ var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
19
+ function loadConfigFile() {
20
+ try {
21
+ const raw = fs.readFileSync(CONFIG_FILE, "utf8");
22
+ return JSON.parse(raw);
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+ function saveConfigFile(config) {
28
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
29
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
30
+ }
31
+ async function runSetup(existing) {
32
+ const rl = createInterface({ input, output });
33
+ console.log("");
34
+ console.log(" AI Teams Agent \u914D\u7F6E");
35
+ console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
36
+ console.log("");
37
+ const serverUrl = await rl.question(` \u670D\u52A1\u5668\u5730\u5740 [${existing?.serverUrl ?? "ws://localhost:3789"}]: `);
38
+ const authToken = await rl.question(` \u8BA4\u8BC1 Token${existing ? " [******]" : ""}: `);
39
+ const employeeId = await rl.question(` \u5458\u5DE5 ID [${existing?.employeeId ?? "emp_local"}]: `);
40
+ const employeeName = await rl.question(` \u5458\u5DE5\u540D\u79F0 [${existing?.employeeName ?? "Local Agent"}]: `);
41
+ const workspace = await rl.question(` \u5DE5\u4F5C\u76EE\u5F55 [${existing?.workspace ?? process.cwd()}]: `);
42
+ const runnerMode = await rl.question(` Runner \u6A21\u5F0F (claude/fake) [${existing?.runnerMode ?? "claude"}]: `);
43
+ rl.close();
44
+ const config = {
45
+ serverUrl: serverUrl.trim() || existing?.serverUrl || "ws://localhost:3789",
46
+ authToken: authToken.trim() || existing?.authToken || "",
47
+ employeeId: employeeId.trim() || existing?.employeeId || "emp_local",
48
+ employeeName: employeeName.trim() || existing?.employeeName || "Local Agent",
49
+ workspace: workspace.trim() || existing?.workspace || process.cwd(),
50
+ runnerMode: runnerMode.trim() || existing?.runnerMode || "claude"
51
+ };
52
+ saveConfigFile(config);
53
+ console.log("");
54
+ console.log(` \u2713 \u914D\u7F6E\u5DF2\u4FDD\u5B58\u5230 ${CONFIG_FILE}`);
55
+ return config;
56
+ }
57
+
58
+ // src/config.ts
59
+ var fileConfig = loadConfigFile();
60
+ var SERVER_PORT = process.env.AI_TEAMS_SERVER_PORT || "3789";
61
+ var SERVER_URL = process.env.SERVER_URL || fileConfig?.serverUrl || `ws://localhost:${SERVER_PORT}`;
62
+ var AUTH_TOKEN = process.env.AI_TEAMS_AUTH_TOKEN || fileConfig?.authToken || "";
63
+ var EMPLOYEE_ID = process.env.EMPLOYEE_ID || fileConfig?.employeeId || "emp_local";
64
+ var EMPLOYEE_NAME = process.env.EMPLOYEE_NAME || fileConfig?.employeeName || "Local Agent";
65
+ var EMPLOYEE_LABELS = process.env.EMPLOYEE_LABELS?.split(",").map((item) => item.trim()).filter(Boolean) ?? [];
66
+ var RECONNECT_MS = Number(process.env.RECONNECT_MS) || 5e3;
67
+ var RUNNER_MODE = process.env.RUNNER_MODE || fileConfig?.runnerMode || "claude";
68
+ var DEFAULT_WORKSPACE = process.env.DEFAULT_WORKSPACE || fileConfig?.workspace || process.cwd();
69
+ var MAX_BUFFERED_MESSAGES = Number(process.env.AGENT_BUFFER_LIMIT) || 400;
70
+ var MAX_ERROR_TAIL = 16e3;
71
+ var CLAUDE_MISSING_CONVERSATION_PATTERN = /No conversation found with session ID/i;
72
+ var AGENT_RECORDS_DIR = process.env.AGENT_RECORDS_DIR || path2.join(DEFAULT_WORKSPACE, ".ai-teams", "agents", EMPLOYEE_ID);
73
+ var STATE_FILE = process.env.AGENT_STATE_FILE || path2.join(AGENT_RECORDS_DIR, "session-state.json");
74
+ var LEGACY_STATE_FILE = path2.join(process.cwd(), `.agent-state.${EMPLOYEE_ID}.json`);
75
+ var DAILY_RECORDS_DIR = path2.join(AGENT_RECORDS_DIR, "daily");
76
+ var HOOKS_DIR = path2.join(AGENT_RECORDS_DIR, "hooks");
77
+ var CLAUDE_HOOK_SCRIPT = path2.join(HOOKS_DIR, "claude-session-recorder.cjs");
78
+ var CLAUDE_HOOK_SETTINGS = path2.join(HOOKS_DIR, "claude-hooks.settings.json");
79
+ var CLAUDE_HOOKS_ENABLED = process.env.CLAUDE_HOOKS_ENABLED !== "false";
80
+ var WORKSPACE_CLAUDE_MD = path2.join(DEFAULT_WORKSPACE, "CLAUDE.md");
81
+ var CLAUDE_MD_SECTION_START = "<!-- AI_TEAMS_AGENT_RULES_START -->";
82
+ var CLAUDE_MD_SECTION_END = "<!-- AI_TEAMS_AGENT_RULES_END -->";
83
+
84
+ // src/state.ts
85
+ import fs2 from "node:fs";
86
+ import path3 from "node:path";
87
+ import { randomUUID } from "node:crypto";
88
+ function loadState() {
89
+ try {
90
+ const content = fs2.readFileSync(STATE_FILE, "utf8");
91
+ const parsed = JSON.parse(content);
92
+ return {
93
+ claudeSessionId: parsed.claudeSessionId || randomUUID(),
94
+ sessionReady: parsed.sessionReady ?? false
95
+ };
96
+ } catch {
97
+ const legacyState = loadLegacyState();
98
+ if (legacyState) {
99
+ persistState(legacyState);
100
+ return legacyState;
101
+ }
102
+ const state = { claudeSessionId: randomUUID(), sessionReady: false };
103
+ persistState(state);
104
+ return state;
105
+ }
106
+ }
107
+ function loadLegacyState() {
108
+ if (process.env.AGENT_STATE_FILE || LEGACY_STATE_FILE === STATE_FILE || !fs2.existsSync(LEGACY_STATE_FILE)) {
109
+ return null;
110
+ }
111
+ try {
112
+ const parsed = JSON.parse(fs2.readFileSync(LEGACY_STATE_FILE, "utf8"));
113
+ return {
114
+ claudeSessionId: parsed.claudeSessionId || randomUUID(),
115
+ sessionReady: parsed.sessionReady ?? false
116
+ };
117
+ } catch {
118
+ return null;
119
+ }
120
+ }
121
+ function persistState(state) {
122
+ fs2.mkdirSync(path3.dirname(STATE_FILE), { recursive: true });
123
+ fs2.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
124
+ }
125
+ function resetClaudeSession() {
126
+ const state = { claudeSessionId: randomUUID(), sessionReady: false };
127
+ persistState(state);
128
+ return state;
129
+ }
130
+
131
+ // src/records.ts
132
+ import fs3 from "node:fs";
133
+ import path4 from "node:path";
134
+
135
+ // src/claude-hook-script.ts
136
+ var CLAUDE_HOOK_SCRIPT_CONTENT = `#!/usr/bin/env node
137
+ const fs = require("node:fs");
138
+ const path = require("node:path");
139
+
140
+ function readStdin() {
141
+ return new Promise((resolve) => {
142
+ let content = "";
143
+ process.stdin.setEncoding("utf8");
144
+ process.stdin.on("data", (chunk) => {
145
+ content += chunk;
146
+ });
147
+ process.stdin.on("end", () => resolve(content));
148
+ });
149
+ }
150
+
151
+ function dateKey() {
152
+ return new Date().toISOString().slice(0, 10);
153
+ }
154
+
155
+ function timestamp() {
156
+ return new Date().toLocaleString();
157
+ }
158
+
159
+ function safeLine(value) {
160
+ return String(value ?? "").replace(/\\n/g, " ").trim();
161
+ }
162
+
163
+ function truncate(value, max = 1200) {
164
+ const text = safeLine(value);
165
+ return text.length > max ? text.slice(0, max) + "..." : text;
166
+ }
167
+
168
+ function describeTool(input) {
169
+ if (!input.tool_name) {
170
+ return "";
171
+ }
172
+ const toolInput = input.tool_input || {};
173
+ if (input.tool_name === "Bash" && toolInput.command) {
174
+ return " - " + input.tool_name + ": " + truncate(toolInput.command, 500);
175
+ }
176
+ const filePath = toolInput.file_path || toolInput.path;
177
+ if (filePath) {
178
+ return " - " + input.tool_name + ": " + filePath;
179
+ }
180
+ return " - " + input.tool_name;
181
+ }
182
+
183
+ (async () => {
184
+ try {
185
+ const raw = await readStdin();
186
+ const input = raw ? JSON.parse(raw) : {};
187
+ const recordDir = process.env.AI_TEAMS_RECORD_DIR;
188
+ if (!recordDir) {
189
+ process.exit(0);
190
+ }
191
+
192
+ const dailyDir = path.join(recordDir, "daily");
193
+ fs.mkdirSync(dailyDir, { recursive: true });
194
+ const filePath = path.join(dailyDir, dateKey() + ".md");
195
+ if (!fs.existsSync(filePath)) {
196
+ fs.writeFileSync(filePath, "# " + process.env.AI_TEAMS_AGENT_NAME + " Daily Activity - " + dateKey() + "\\n\\n");
197
+ }
198
+
199
+ const event = input.hook_event_name || "Unknown";
200
+ const lines = [
201
+ "### " + timestamp() + " Claude Hook: " + event,
202
+ "- Agent: " + process.env.AI_TEAMS_AGENT_NAME + " (" + process.env.AI_TEAMS_AGENT_ID + ")",
203
+ "- Task ID: " + (process.env.AI_TEAMS_TASK_ID || "none"),
204
+ "- Target mode: " + (process.env.AI_TEAMS_TASK_TARGET_MODE || "unknown"),
205
+ "- Managed session: " + (process.env.AI_TEAMS_TASK_SESSION_ID || "unknown"),
206
+ "- Claude hook session: " + (input.session_id || "unknown"),
207
+ "- CWD: " + (input.cwd || "unknown"),
208
+ input.transcript_path ? "- Transcript: " + input.transcript_path : "",
209
+ describeTool(input),
210
+ input.prompt ? "- Prompt: " + truncate(input.prompt) : "",
211
+ input.last_assistant_message ? "- Last assistant message: " + truncate(input.last_assistant_message) : "",
212
+ input.error ? "- Error: " + truncate(input.error) : "",
213
+ input.reason ? "- Reason: " + input.reason : "",
214
+ input.compact_summary ? "- Compact summary: " + truncate(input.compact_summary) : "",
215
+ "",
216
+ ].filter(Boolean);
217
+
218
+ fs.appendFileSync(filePath, lines.join("\\n") + "\\n");
219
+ process.exit(0);
220
+ } catch (error) {
221
+ process.stderr.write(String(error && error.stack ? error.stack : error));
222
+ process.exit(0);
223
+ }
224
+ })();
225
+ `;
226
+
227
+ // src/records.ts
228
+ function localTimestamp() {
229
+ return (/* @__PURE__ */ new Date()).toLocaleString();
230
+ }
231
+ function localDateKey() {
232
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
233
+ }
234
+ function appendDailyRecord(markdown) {
235
+ fs3.mkdirSync(DAILY_RECORDS_DIR, { recursive: true });
236
+ const filePath = path4.join(DAILY_RECORDS_DIR, `${localDateKey()}.md`);
237
+ if (!fs3.existsSync(filePath)) {
238
+ fs3.writeFileSync(filePath, `# ${EMPLOYEE_NAME} Daily Activity - ${localDateKey()}
239
+
240
+ `);
241
+ }
242
+ fs3.appendFileSync(filePath, markdown);
243
+ }
244
+ function formatPromptForRecord(prompt) {
245
+ return prompt.trim().replace(/\n/g, "\n ");
246
+ }
247
+ function recordTaskStart(task, prompt, workspace) {
248
+ appendDailyRecord(
249
+ [
250
+ `## ${localTimestamp()} Task Started`,
251
+ `- Agent: ${EMPLOYEE_NAME} (${EMPLOYEE_ID})`,
252
+ `- Task ID: ${task.taskId}`,
253
+ `- Target mode: ${task.targetMode}`,
254
+ `- Claude session: ${task.claudeSessionId}`,
255
+ `- Workspace: ${workspace || DEFAULT_WORKSPACE}`,
256
+ `- Prompt:`,
257
+ ` ${formatPromptForRecord(prompt)}`,
258
+ ""
259
+ ].join("\n")
260
+ );
261
+ }
262
+ function recordTaskFinish(task, status, detail) {
263
+ appendDailyRecord(
264
+ [
265
+ `## ${localTimestamp()} Task ${status}`,
266
+ `- Agent: ${EMPLOYEE_NAME} (${EMPLOYEE_ID})`,
267
+ `- Task ID: ${task.taskId}`,
268
+ `- Target mode: ${task.targetMode}`,
269
+ `- Claude session: ${task.claudeSessionId}`,
270
+ typeof detail === "undefined" ? "" : `- Detail: ${String(detail).replace(/\n/g, " ")}`,
271
+ ""
272
+ ].filter(Boolean).join("\n")
273
+ );
274
+ }
275
+ function ensureWorkspaceClaudeMd() {
276
+ fs3.mkdirSync(DEFAULT_WORKSPACE, { recursive: true });
277
+ const section = buildWorkspaceClaudeSection();
278
+ const current = fs3.existsSync(WORKSPACE_CLAUDE_MD) ? fs3.readFileSync(WORKSPACE_CLAUDE_MD, "utf8") : "";
279
+ const startIndex = current.indexOf(CLAUDE_MD_SECTION_START);
280
+ const endIndex = current.indexOf(CLAUDE_MD_SECTION_END);
281
+ if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
282
+ const next2 = `${current.slice(0, startIndex).trimEnd()}
283
+
284
+ ${section}
285
+
286
+ ${current.slice(endIndex + CLAUDE_MD_SECTION_END.length).trimStart()}`;
287
+ fs3.writeFileSync(WORKSPACE_CLAUDE_MD, next2.trimEnd() + "\n");
288
+ return;
289
+ }
290
+ const next = current.trim() ? `${current.trimEnd()}
291
+
292
+ ${section}
293
+ ` : `${section}
294
+ `;
295
+ fs3.writeFileSync(WORKSPACE_CLAUDE_MD, next);
296
+ }
297
+ function buildWorkspaceClaudeSection() {
298
+ return [
299
+ CLAUDE_MD_SECTION_START,
300
+ "## AI Teams Agent Operating Rules",
301
+ "",
302
+ `- Agent identity: ${EMPLOYEE_NAME} (${EMPLOYEE_ID}).`,
303
+ `- Default workspace: \`${DEFAULT_WORKSPACE}\`.`,
304
+ `- Default managed session state: \`${STATE_FILE}\`.`,
305
+ `- Daily memory files: \`${path4.join(DAILY_RECORDS_DIR, "YYYY-MM-DD.md")}\`.`,
306
+ `- Claude hook settings: \`${CLAUDE_HOOK_SETTINGS}\`.`,
307
+ "",
308
+ "### Conversation Responsibility",
309
+ "",
310
+ "- Treat direct `@Agent` or explicitly selected-Agent messages as this Agent's long-running default conversation.",
311
+ "- Keep continuity for direct Agent conversations by using the managed default session state.",
312
+ "- Treat queue tasks as isolated execution jobs; use their task-specific session context and avoid assuming they update the default conversation unless explicitly requested.",
313
+ "- When reporting back, summarize what changed, what was verified, and any remaining risks.",
314
+ "",
315
+ "### Memory And State Rules",
316
+ "",
317
+ "- At the start of a direct Agent conversation, read the most recent daily memory files before acting when continuity, prior decisions, or current workspace state could matter.",
318
+ "- Read today's memory file first, then recent previous days only as needed. Do not bulk-load all history unless the task asks for a retrospective.",
319
+ "- Use the daily memory files to understand what this Agent did, which tasks completed, which tools ran, and what unresolved work remains.",
320
+ "- Append durable observations through the AI Teams recorder and Claude hooks; avoid hand-editing generated hook records unless correcting an obvious mistake.",
321
+ "- Do not store secrets, tokens, private credentials, or sensitive user data in daily memory files.",
322
+ "",
323
+ "### Files Managed By AI Teams",
324
+ "",
325
+ "- `.ai-teams/agents/<EMPLOYEE_ID>/session-state.json` stores the default Claude session id for this Agent.",
326
+ "- `.ai-teams/agents/<EMPLOYEE_ID>/daily/` stores Markdown activity memory by date.",
327
+ "- `.ai-teams/agents/<EMPLOYEE_ID>/hooks/` stores generated Claude Code hook scripts and settings.",
328
+ "- These files are runtime state, not source code. Do not delete them unless explicitly asked to reset Agent memory.",
329
+ CLAUDE_MD_SECTION_END
330
+ ].join("\n");
331
+ }
332
+ function shellQuote(value) {
333
+ return `'${value.replace(/'/g, "'\\''")}'`;
334
+ }
335
+ function ensureClaudeHookFiles() {
336
+ if (!CLAUDE_HOOKS_ENABLED) {
337
+ return;
338
+ }
339
+ fs3.mkdirSync(HOOKS_DIR, { recursive: true });
340
+ fs3.writeFileSync(CLAUDE_HOOK_SCRIPT, CLAUDE_HOOK_SCRIPT_CONTENT);
341
+ fs3.chmodSync(CLAUDE_HOOK_SCRIPT, 493);
342
+ fs3.writeFileSync(CLAUDE_HOOK_SETTINGS, JSON.stringify(buildClaudeHookSettings(), null, 2));
343
+ }
344
+ function buildHookCommand() {
345
+ return `${shellQuote(process.execPath)} ${shellQuote(CLAUDE_HOOK_SCRIPT)}`;
346
+ }
347
+ function buildClaudeHookSettings() {
348
+ const hook = {
349
+ type: "command",
350
+ command: buildHookCommand(),
351
+ timeout: 10
352
+ };
353
+ return {
354
+ hooks: {
355
+ SessionStart: [{ matcher: "*", hooks: [hook] }],
356
+ UserPromptSubmit: [{ matcher: "*", hooks: [hook] }],
357
+ PostToolUse: [{ matcher: "*", hooks: [hook] }],
358
+ Stop: [{ matcher: "*", hooks: [hook] }],
359
+ StopFailure: [{ matcher: "*", hooks: [hook] }],
360
+ SessionEnd: [{ matcher: "*", hooks: [hook] }],
361
+ PostCompact: [{ matcher: "*", hooks: [hook] }]
362
+ }
363
+ };
364
+ }
365
+
366
+ // src/runner.ts
367
+ import { spawn } from "node:child_process";
368
+ import fs4 from "node:fs";
369
+ import os2 from "node:os";
370
+ import path5 from "node:path";
371
+ import readline from "node:readline";
372
+ function resolveWorkspace(raw) {
373
+ if (!raw || !raw.trim()) {
374
+ return DEFAULT_WORKSPACE;
375
+ }
376
+ let resolved = raw.trim();
377
+ if (resolved.startsWith("~")) {
378
+ resolved = path5.join(os2.homedir(), resolved.slice(1));
379
+ }
380
+ if (!path5.isAbsolute(resolved)) {
381
+ resolved = path5.join(DEFAULT_WORKSPACE, resolved);
382
+ }
383
+ if (!fs4.existsSync(resolved)) {
384
+ fs4.mkdirSync(resolved, { recursive: true });
385
+ }
386
+ return resolved;
387
+ }
388
+ function handleClaudeJsonLine(taskId, line, findActiveTask2, emitOutput2) {
389
+ if (!line.trim()) {
390
+ return;
391
+ }
392
+ let parsed;
393
+ try {
394
+ parsed = JSON.parse(line);
395
+ } catch {
396
+ emitOutput2(taskId, "stdout", `${line}
397
+ `);
398
+ return;
399
+ }
400
+ if (parsed.type === "stream_event") {
401
+ const event = parsed.event;
402
+ if (event?.type === "content_block_delta" && event.delta?.type === "text_delta" && event.delta.text) {
403
+ const task = findActiveTask2(taskId);
404
+ if (task) {
405
+ task.sawStreamText = true;
406
+ task.summary.push(event.delta.text);
407
+ }
408
+ emitOutput2(taskId, "stdout", event.delta.text);
409
+ }
410
+ return;
411
+ }
412
+ if (parsed.type === "assistant" && Array.isArray(parsed.message?.content)) {
413
+ const task = findActiveTask2(taskId);
414
+ if (task?.sawStreamText) {
415
+ return;
416
+ }
417
+ for (const block of parsed.message.content) {
418
+ if (block.type === "text" && block.text) {
419
+ task?.summary.push(block.text);
420
+ emitOutput2(taskId, "stdout", `${block.text}
421
+ `);
422
+ }
423
+ if (block.type === "tool_use" && block.name) {
424
+ emitOutput2(taskId, "stdout", `[tool] ${block.name}
425
+ `);
426
+ }
427
+ }
428
+ return;
429
+ }
430
+ if (parsed.type === "result" && typeof parsed.result === "string") {
431
+ const task = findActiveTask2(taskId);
432
+ if (task?.sawStreamText) {
433
+ emitOutput2(taskId, "stdout", formatClaudeDoneNode(parsed));
434
+ return;
435
+ }
436
+ task?.summary.push(parsed.result);
437
+ emitOutput2(taskId, "stdout", formatClaudeDoneNode(parsed));
438
+ extractMetrics(taskId, parsed, findActiveTask2);
439
+ }
440
+ }
441
+ function formatClaudeDoneNode(node) {
442
+ const lines = ["\n[done] Claude result"];
443
+ for (const key of ["subtype", "session_id", "duration_ms", "duration_api_ms", "num_turns", "total_cost_usd"]) {
444
+ const value = node[key];
445
+ if (value !== void 0 && value !== null) {
446
+ lines.push(`[done] ${key}: ${String(value)}`);
447
+ }
448
+ }
449
+ const usage = node.usage;
450
+ if (usage && typeof usage === "object") {
451
+ lines.push(`[done] usage: ${JSON.stringify(usage)}`);
452
+ }
453
+ const result = typeof node.result === "string" ? node.result.trim() : "";
454
+ if (result) {
455
+ lines.push(`[done] result: ${result}`);
456
+ }
457
+ return `${lines.join("\n")}
458
+ `;
459
+ }
460
+ function extractMetrics(taskId, node, findActiveTask2) {
461
+ const task = findActiveTask2(taskId);
462
+ if (!task) return;
463
+ if (typeof node.duration_ms === "number") task.resultMetrics.durationMs = node.duration_ms;
464
+ if (typeof node.duration_api_ms === "number") task.resultMetrics.durationApiMs = node.duration_api_ms;
465
+ if (typeof node.num_turns === "number") task.resultMetrics.numTurns = node.num_turns;
466
+ if (typeof node.total_cost_usd === "number") task.resultMetrics.totalCostUsd = node.total_cost_usd;
467
+ const usage = node.usage;
468
+ if (usage && typeof usage === "object") {
469
+ const u = usage;
470
+ if (typeof u.input_tokens === "number") task.resultMetrics.usageInputTokens = u.input_tokens;
471
+ if (typeof u.output_tokens === "number") task.resultMetrics.usageOutputTokens = u.output_tokens;
472
+ if (typeof u.cache_read_input_tokens === "number") task.resultMetrics.usageCacheReadTokens = u.cache_read_input_tokens;
473
+ if (typeof u.cache_creation_input_tokens === "number") task.resultMetrics.usageCacheCreationTokens = u.cache_creation_input_tokens;
474
+ }
475
+ }
476
+ function buildClaudeArgs(prompt, task, agentState2) {
477
+ ensureWorkspaceClaudeMd();
478
+ ensureClaudeHookFiles();
479
+ const cfg = task.cliConfig;
480
+ const args = [
481
+ "-p",
482
+ "--output-format",
483
+ "stream-json",
484
+ "--include-partial-messages",
485
+ "--verbose",
486
+ "--dangerously-skip-permissions"
487
+ ];
488
+ if (cfg?.model) {
489
+ args.push("--model", cfg.model);
490
+ }
491
+ if (cfg?.maxTurns) {
492
+ args.push("--max-turns", String(cfg.maxTurns));
493
+ }
494
+ if (cfg?.systemPrompt) {
495
+ args.push("--system-prompt", cfg.systemPrompt);
496
+ }
497
+ if (cfg?.appendSystemPrompt) {
498
+ args.push("--append-system-prompt", cfg.appendSystemPrompt);
499
+ }
500
+ if (cfg?.allowedTools && cfg.allowedTools.length > 0) {
501
+ for (const tool of cfg.allowedTools) {
502
+ args.push("--allowedTools", tool);
503
+ }
504
+ }
505
+ if (cfg?.disallowedTools && cfg.disallowedTools.length > 0) {
506
+ for (const tool of cfg.disallowedTools) {
507
+ args.push("--disallowedTools", tool);
508
+ }
509
+ }
510
+ if (cfg?.extraArgs) {
511
+ args.push(...cfg.extraArgs);
512
+ }
513
+ if (CLAUDE_HOOKS_ENABLED) {
514
+ args.push("--settings", CLAUDE_HOOK_SETTINGS);
515
+ }
516
+ if (task.targetMode === "queue") {
517
+ args.push("--session-id", task.claudeSessionId);
518
+ } else if (agentState2.sessionReady) {
519
+ args.push("--resume", agentState2.claudeSessionId);
520
+ } else {
521
+ args.push("--session-id", agentState2.claudeSessionId);
522
+ }
523
+ args.push(prompt);
524
+ return args;
525
+ }
526
+ function shouldRetryWithFreshClaudeSession(taskId, exitCode, findActiveTask2) {
527
+ const task = findActiveTask2(taskId);
528
+ return exitCode !== 0 && task !== null && task.targetMode !== "queue" && !task.cancelRequested && !task.retriedWithFreshSession && CLAUDE_MISSING_CONVERSATION_PATTERN.test(task.stderrTail);
529
+ }
530
+ function runFakeTask(taskId, prompt, deps) {
531
+ const { findActiveTask: findActiveTask2, send: send3, emitOutput: emitOutput2, finishTask: finishTask2 } = deps;
532
+ const task = findActiveTask2(taskId);
533
+ send3({ type: "task.started", taskId, pid: process.pid, sessionId: task?.claudeSessionId ?? null });
534
+ const steps = [
535
+ `\u6536\u5230\u4EFB\u52A1\uFF1A${prompt}
536
+ `,
537
+ "\u5206\u6790\u4EFB\u52A1\u4E0A\u4E0B\u6587...\n",
538
+ "\u6267\u884C\u6A21\u62DF\u6B65\u9AA4 1/3...\n",
539
+ "\u6267\u884C\u6A21\u62DF\u6B65\u9AA4 2/3...\n",
540
+ "\u6267\u884C\u6A21\u62DF\u6B65\u9AA4 3/3...\n"
541
+ ];
542
+ let index = 0;
543
+ const timer = setInterval(() => {
544
+ const current = findActiveTask2(taskId);
545
+ if (!current) {
546
+ clearInterval(timer);
547
+ return;
548
+ }
549
+ if (index >= steps.length) {
550
+ clearInterval(timer);
551
+ finishTask2(taskId, "completed", 0);
552
+ return;
553
+ }
554
+ const chunk = steps[index];
555
+ current.summary.push(chunk);
556
+ emitOutput2(taskId, "stdout", chunk);
557
+ index += 1;
558
+ }, 800);
559
+ }
560
+ function runClaudeTask(taskId, prompt, workspace, deps) {
561
+ const {
562
+ findActiveTask: findActiveTask2,
563
+ send: send3,
564
+ emitOutput: emitOutput2,
565
+ emitStderr: emitStderr2,
566
+ finishTask: finishTask2,
567
+ getAgentState,
568
+ setAgentState
569
+ } = deps;
570
+ const currentTask = findActiveTask2(taskId);
571
+ if (!currentTask) {
572
+ return;
573
+ }
574
+ const agentState2 = getAgentState();
575
+ const args = buildClaudeArgs(prompt, currentTask, agentState2);
576
+ const resolvedWorkspace = resolveWorkspace(workspace);
577
+ const child = spawn("claude", args, {
578
+ cwd: resolvedWorkspace,
579
+ env: {
580
+ ...process.env,
581
+ AI_TEAMS_AGENT_ID: EMPLOYEE_ID,
582
+ AI_TEAMS_AGENT_NAME: EMPLOYEE_NAME,
583
+ AI_TEAMS_RECORD_DIR: AGENT_RECORDS_DIR,
584
+ AI_TEAMS_DEFAULT_WORKSPACE: DEFAULT_WORKSPACE,
585
+ AI_TEAMS_DEFAULT_SESSION_ID: agentState2.claudeSessionId,
586
+ AI_TEAMS_TASK_ID: currentTask.taskId,
587
+ AI_TEAMS_TASK_TARGET_MODE: currentTask.targetMode,
588
+ AI_TEAMS_TASK_SESSION_ID: currentTask.claudeSessionId,
589
+ AI_TEAMS_TASK_PROMPT: prompt,
590
+ AI_TEAMS_TASK_WORKSPACE: resolvedWorkspace
591
+ },
592
+ stdio: ["ignore", "pipe", "pipe"]
593
+ });
594
+ const taskStillActive = findActiveTask2(taskId);
595
+ if (!taskStillActive) {
596
+ child.kill("SIGTERM");
597
+ return;
598
+ }
599
+ if (taskStillActive.cancelRequested) {
600
+ child.kill("SIGTERM");
601
+ return;
602
+ }
603
+ currentTask.generation += 1;
604
+ currentTask.child = child;
605
+ send3({ type: "task.started", taskId, pid: child.pid ?? 0, sessionId: currentTask.claudeSessionId });
606
+ const stdoutReader = readline.createInterface({ input: child.stdout });
607
+ stdoutReader.on("line", (line) => {
608
+ handleClaudeJsonLine(taskId, line, findActiveTask2, emitOutput2);
609
+ });
610
+ child.stderr.on("data", (chunk) => {
611
+ emitStderr2(taskId, chunk.toString());
612
+ });
613
+ child.on("error", (error) => {
614
+ finishTask2(taskId, "failed", error.message);
615
+ });
616
+ child.on("close", (code) => {
617
+ const task = findActiveTask2(taskId);
618
+ if (!task && code === null) {
619
+ return;
620
+ }
621
+ if (task?.cancelRequested) {
622
+ finishTask2(taskId, "cancelled");
623
+ return;
624
+ }
625
+ if (code === 0) {
626
+ finishTask2(taskId, "completed", 0);
627
+ return;
628
+ }
629
+ if (shouldRetryWithFreshClaudeSession(taskId, code, findActiveTask2)) {
630
+ const fresh = findActiveTask2(taskId);
631
+ if (!fresh) return;
632
+ if (fresh.cancelRequested) {
633
+ finishTask2(taskId, "cancelled");
634
+ return;
635
+ }
636
+ fresh.retriedWithFreshSession = true;
637
+ fresh.stderrTail = "";
638
+ fresh.sawStreamText = false;
639
+ fresh.child = null;
640
+ const newState = resetClaudeSession();
641
+ setAgentState(newState);
642
+ fresh.claudeSessionId = newState.claudeSessionId;
643
+ emitOutput2(taskId, "stdout", "\n[agent] Claude resume session was missing. Starting a new session and retrying this task.\n");
644
+ runClaudeTask(taskId, prompt, workspace, deps);
645
+ return;
646
+ }
647
+ finishTask2(taskId, "failed", `Claude CLI \u9000\u51FA\u7801 ${code ?? "unknown"}`);
648
+ });
649
+ }
650
+
651
+ // src/connection.ts
652
+ import crypto from "node:crypto";
653
+ import os3 from "node:os";
654
+ import WebSocket from "ws";
655
+
656
+ // ../../packages/shared/dist/index.js
657
+ var ProtocolError = class extends Error {
658
+ constructor(message) {
659
+ super(message);
660
+ this.name = "ProtocolError";
661
+ }
662
+ };
663
+ function parseJsonMessage(raw) {
664
+ try {
665
+ return JSON.parse(raw);
666
+ } catch {
667
+ throw new ProtocolError("Message is not valid JSON.");
668
+ }
669
+ }
670
+ function parseServerToEmployeeMessage(value) {
671
+ const message = objectValue(value, "message");
672
+ const type = stringField(message, "type");
673
+ if (type === "task.dispatch") {
674
+ return {
675
+ type,
676
+ taskId: nonEmptyStringField(message, "taskId"),
677
+ leaderCommandId: nonEmptyStringField(message, "leaderCommandId"),
678
+ employeeId: nonEmptyStringField(message, "employeeId"),
679
+ targetMode: taskTargetModeField(message, "targetMode"),
680
+ prompt: nonEmptyStringField(message, "prompt"),
681
+ workspace: nullableStringField(message, "workspace"),
682
+ timeoutSec: positiveNumberField(message, "timeoutSec"),
683
+ cliConfig: optionalCliConfigField(message, "cliConfig")
684
+ };
685
+ }
686
+ if (type === "task.cancel") {
687
+ return { type, taskId: nonEmptyStringField(message, "taskId") };
688
+ }
689
+ throw new ProtocolError(`Unsupported server-to-employee message type: ${type}`);
690
+ }
691
+ function objectValue(value, label) {
692
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
693
+ throw new ProtocolError(`${label} must be an object.`);
694
+ }
695
+ return value;
696
+ }
697
+ function stringField(record, key) {
698
+ const value = record[key];
699
+ if (typeof value !== "string") {
700
+ throw new ProtocolError(`${key} must be a string.`);
701
+ }
702
+ return value;
703
+ }
704
+ function nonEmptyStringField(record, key) {
705
+ const value = stringField(record, key).trim();
706
+ if (!value) {
707
+ throw new ProtocolError(`${key} must not be empty.`);
708
+ }
709
+ return value;
710
+ }
711
+ function nullableStringField(record, key) {
712
+ const value = record[key];
713
+ if (value === null) {
714
+ return null;
715
+ }
716
+ if (typeof value !== "string") {
717
+ throw new ProtocolError(`${key} must be a string or null.`);
718
+ }
719
+ return value;
720
+ }
721
+ function taskTargetModeField(record, key) {
722
+ const value = record[key];
723
+ if (value === "queue" || value === "direct" || value === "broadcast") {
724
+ return value;
725
+ }
726
+ throw new ProtocolError(`${key} must be queue, direct, or broadcast.`);
727
+ }
728
+ function positiveNumberField(record, key) {
729
+ const value = record[key];
730
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
731
+ throw new ProtocolError(`${key} must be a positive number.`);
732
+ }
733
+ return value;
734
+ }
735
+ function optionalCliConfigField(record, key) {
736
+ const value = record[key];
737
+ if (value === void 0 || value === null) {
738
+ return null;
739
+ }
740
+ if (typeof value !== "object" || Array.isArray(value)) {
741
+ throw new ProtocolError(`${key} must be an object or null.`);
742
+ }
743
+ return value;
744
+ }
745
+ function isEncryptedEnvelope(value) {
746
+ return typeof value === "object" && value !== null && value.encrypted === true && typeof value.iv === "string" && typeof value.ciphertext === "string" && typeof value.tag === "string";
747
+ }
748
+ function parseEncryptionKey(hex) {
749
+ const key = Buffer.from(hex, "hex");
750
+ if (key.length !== 32) {
751
+ throw new Error("AI_TEAMS_ENCRYPTION_KEY must be 32 bytes (64 hex characters).");
752
+ }
753
+ return key;
754
+ }
755
+
756
+ // src/connection.ts
757
+ var ALGORITHM = "aes-256-gcm";
758
+ var IV_LENGTH = 12;
759
+ var TAG_LENGTH = 16;
760
+ var encryptionKeyHex = process.env.AI_TEAMS_ENCRYPTION_KEY;
761
+ var encryptionKey = encryptionKeyHex ? parseEncryptionKey(encryptionKeyHex) : null;
762
+ function encrypt(plainText) {
763
+ if (!encryptionKey) return plainText;
764
+ const iv = crypto.randomBytes(IV_LENGTH);
765
+ const cipher = crypto.createCipheriv(ALGORITHM, encryptionKey, iv, { authTagLength: TAG_LENGTH });
766
+ const encrypted = Buffer.concat([cipher.update(plainText, "utf8"), cipher.final()]);
767
+ const tag = cipher.getAuthTag();
768
+ return JSON.stringify({
769
+ encrypted: true,
770
+ iv: iv.toString("base64"),
771
+ ciphertext: encrypted.toString("base64"),
772
+ tag: tag.toString("base64")
773
+ });
774
+ }
775
+ function decrypt(raw) {
776
+ if (!encryptionKey) return raw;
777
+ const parsed = JSON.parse(raw);
778
+ if (!isEncryptedEnvelope(parsed)) return raw;
779
+ const iv = Buffer.from(parsed.iv, "base64");
780
+ const ciphertext = Buffer.from(parsed.ciphertext, "base64");
781
+ const tag = Buffer.from(parsed.tag, "base64");
782
+ const decipher = crypto.createDecipheriv(ALGORITHM, encryptionKey, iv, { authTagLength: TAG_LENGTH });
783
+ decipher.setAuthTag(tag);
784
+ return decipher.update(ciphertext) + decipher.final("utf8");
785
+ }
786
+ function buildAgentWsUrl() {
787
+ const url = new URL("/ws/agent", SERVER_URL);
788
+ url.searchParams.set("token", AUTH_TOKEN);
789
+ return url.toString();
790
+ }
791
+ function isTaskMessage(payload) {
792
+ return payload.type.startsWith("task.");
793
+ }
794
+ function send(state, payload) {
795
+ if (state.socket && state.socket.readyState === WebSocket.OPEN) {
796
+ state.socket.send(encrypt(JSON.stringify(payload)));
797
+ return;
798
+ }
799
+ if (isTaskMessage(payload)) {
800
+ state.bufferedMessages.push(payload);
801
+ if (state.bufferedMessages.length > MAX_BUFFERED_MESSAGES) {
802
+ state.bufferedMessages = state.bufferedMessages.slice(-MAX_BUFFERED_MESSAGES);
803
+ }
804
+ }
805
+ }
806
+ function flushBufferedMessages(state) {
807
+ if (!state.socket || state.socket.readyState !== WebSocket.OPEN || state.bufferedMessages.length === 0) {
808
+ return;
809
+ }
810
+ const messages = state.bufferedMessages;
811
+ state.bufferedMessages = [];
812
+ for (const message of messages) {
813
+ state.socket.send(encrypt(JSON.stringify(message)));
814
+ }
815
+ }
816
+ function startHeartbeat(state) {
817
+ if (state.heartbeatTimer) {
818
+ clearInterval(state.heartbeatTimer);
819
+ }
820
+ state.heartbeatTimer = setInterval(() => {
821
+ send(state, { type: "agent.heartbeat", employeeId: EMPLOYEE_ID });
822
+ }, 1e4);
823
+ }
824
+ function scheduleReconnect(state, connectFn) {
825
+ if (state.reconnectTimer) {
826
+ return;
827
+ }
828
+ state.reconnectTimer = setTimeout(() => {
829
+ state.reconnectTimer = null;
830
+ connectFn();
831
+ }, RECONNECT_MS);
832
+ }
833
+ function registerAgent(state, mainTask2, queueTask2) {
834
+ send(state, {
835
+ type: "agent.register",
836
+ employeeId: EMPLOYEE_ID,
837
+ name: EMPLOYEE_NAME,
838
+ machineId: EMPLOYEE_ID,
839
+ hostname: os3.hostname(),
840
+ labels: EMPLOYEE_LABELS,
841
+ activeMainTaskId: mainTask2?.taskId ?? null,
842
+ activeQueueTaskId: queueTask2?.taskId ?? null,
843
+ lastOutputSeq: Math.max(mainTask2?.seq ?? 0, queueTask2?.seq ?? 0)
844
+ });
845
+ }
846
+ function requestTask(state) {
847
+ send(state, { type: "agent.request_task", employeeId: EMPLOYEE_ID });
848
+ }
849
+ function connect(state, getMainTask, getQueueTask, onMessage) {
850
+ if (!AUTH_TOKEN) {
851
+ console.error("[agent] AI_TEAMS_AUTH_TOKEN is required.");
852
+ process.exit(1);
853
+ }
854
+ state.socket = new WebSocket(buildAgentWsUrl());
855
+ state.socket.on("open", () => {
856
+ console.log(`[agent:${EMPLOYEE_ID}] connected to ${SERVER_URL}`);
857
+ registerAgent(state, getMainTask(), getQueueTask());
858
+ flushBufferedMessages(state);
859
+ requestTask(state);
860
+ startHeartbeat(state);
861
+ });
862
+ state.socket.on("message", (raw) => {
863
+ try {
864
+ const decrypted = decrypt(raw.toString());
865
+ const message = parseServerToEmployeeMessage(parseJsonMessage(decrypted));
866
+ onMessage(message);
867
+ } catch (error) {
868
+ console.error(`[agent:${EMPLOYEE_ID}] invalid server message`, error);
869
+ }
870
+ });
871
+ state.socket.on("close", () => {
872
+ console.log(`[agent:${EMPLOYEE_ID}] disconnected, reconnecting in ${RECONNECT_MS}ms...`);
873
+ scheduleReconnect(state, () => connect(state, getMainTask, getQueueTask, onMessage));
874
+ });
875
+ state.socket.on("error", (error) => {
876
+ console.error(`[agent:${EMPLOYEE_ID}] websocket error`, error.message);
877
+ });
878
+ }
879
+
880
+ // src/index.ts
881
+ var mainTask = null;
882
+ var queueTask = null;
883
+ var agentState = loadState();
884
+ var connState = {
885
+ socket: null,
886
+ reconnectTimer: null,
887
+ heartbeatTimer: null,
888
+ bufferedMessages: []
889
+ };
890
+ function findActiveTask(taskId) {
891
+ if (mainTask?.taskId === taskId) return mainTask;
892
+ if (queueTask?.taskId === taskId) return queueTask;
893
+ return null;
894
+ }
895
+ function slotForTargetMode(mode) {
896
+ if (mode === "queue") {
897
+ return { get: () => queueTask, set: (t) => {
898
+ queueTask = t;
899
+ } };
900
+ }
901
+ return { get: () => mainTask, set: (t) => {
902
+ mainTask = t;
903
+ } };
904
+ }
905
+ function send2(payload) {
906
+ send(connState, payload);
907
+ }
908
+ function emitOutput(taskId, stream, content) {
909
+ const task = findActiveTask(taskId);
910
+ if (!task || !content) return;
911
+ task.seq += 1;
912
+ send2({
913
+ type: "task.output",
914
+ taskId,
915
+ stream,
916
+ seq: task.seq,
917
+ content
918
+ });
919
+ }
920
+ function emitStderr(taskId, content) {
921
+ const task = findActiveTask(taskId);
922
+ if (!task || !content) return;
923
+ task.stderrTail = `${task.stderrTail}${content}`.slice(-MAX_ERROR_TAIL);
924
+ emitOutput(taskId, "stderr", content);
925
+ }
926
+ function requestTask2() {
927
+ send2({ type: "agent.request_task", employeeId: EMPLOYEE_ID });
928
+ }
929
+ function finishTask(taskId, status, payload) {
930
+ const current = findActiveTask(taskId);
931
+ if (!current) return;
932
+ const slot = slotForTargetMode(current.targetMode);
933
+ slot.set(null);
934
+ recordTaskFinish(current, status, payload);
935
+ if (status === "completed") {
936
+ if (current.targetMode !== "queue") {
937
+ agentState.sessionReady = true;
938
+ persistState(agentState);
939
+ }
940
+ send2({
941
+ type: "task.completed",
942
+ taskId,
943
+ exitCode: typeof payload === "number" ? payload : 0,
944
+ summary: current.summary.join("").trim().slice(0, 8e3) || "Claude \u5DF2\u5B8C\u6210\u4EFB\u52A1\u3002",
945
+ ...current.resultMetrics
946
+ });
947
+ if (current.targetMode === "queue") requestTask2();
948
+ return;
949
+ }
950
+ if (status === "cancelled") {
951
+ send2({ type: "task.cancelled", taskId });
952
+ if (current.targetMode === "queue") requestTask2();
953
+ return;
954
+ }
955
+ send2({
956
+ type: "task.failed",
957
+ taskId,
958
+ error: typeof payload === "string" ? payload : "\u4EFB\u52A1\u6267\u884C\u5931\u8D25\u3002"
959
+ });
960
+ if (current.targetMode === "queue") requestTask2();
961
+ }
962
+ var runnerDeps = {
963
+ findActiveTask,
964
+ emitOutput,
965
+ emitStderr,
966
+ finishTask,
967
+ send: send2,
968
+ getAgentState: () => agentState,
969
+ setAgentState: (state) => {
970
+ agentState = state;
971
+ }
972
+ };
973
+ function startTask(message) {
974
+ const slot = slotForTargetMode(message.targetMode);
975
+ if (slot.get()) {
976
+ send2({
977
+ type: "task.failed",
978
+ taskId: message.taskId,
979
+ error: `\u5458\u5DE5\u5F53\u524D\u5FD9\u788C\uFF0C${message.targetMode === "queue" ? "\u961F\u5217" : "\u4E3B"}\u4EFB\u52A1\u69FD\u6B63\u5728\u6267\u884C\u4EFB\u52A1 ${slot.get().taskId}\u3002`
980
+ });
981
+ return;
982
+ }
983
+ const task = {
984
+ taskId: message.taskId,
985
+ seq: 0,
986
+ child: null,
987
+ summary: [],
988
+ cancelRequested: false,
989
+ sawStreamText: false,
990
+ stderrTail: "",
991
+ retriedWithFreshSession: false,
992
+ generation: 0,
993
+ targetMode: message.targetMode,
994
+ claudeSessionId: message.targetMode === "queue" ? randomUUID2() : agentState.claudeSessionId,
995
+ cliConfig: message.cliConfig ?? null,
996
+ resultMetrics: {}
997
+ };
998
+ slot.set(task);
999
+ send2({ type: "task.accepted", taskId: message.taskId });
1000
+ recordTaskStart(task, message.prompt, message.workspace);
1001
+ if (RUNNER_MODE === "fake") {
1002
+ runFakeTask(message.taskId, message.prompt, runnerDeps);
1003
+ return;
1004
+ }
1005
+ runClaudeTask(message.taskId, message.prompt, message.workspace, runnerDeps);
1006
+ }
1007
+ function cancelTask(taskId) {
1008
+ const task = findActiveTask(taskId);
1009
+ if (!task) return;
1010
+ task.cancelRequested = true;
1011
+ if (task.child) {
1012
+ const gen = task.generation;
1013
+ task.child.kill("SIGTERM");
1014
+ setTimeout(() => {
1015
+ const t = findActiveTask(taskId);
1016
+ if (t && t.generation === gen && t.child) t.child.kill("SIGKILL");
1017
+ }, 3e3);
1018
+ } else {
1019
+ finishTask(taskId, "cancelled");
1020
+ }
1021
+ }
1022
+ function handleServerMessage(message) {
1023
+ if (message.type === "task.dispatch") {
1024
+ startTask(message);
1025
+ return;
1026
+ }
1027
+ cancelTask(message.taskId);
1028
+ }
1029
+ function connect2() {
1030
+ connect(connState, () => mainTask, () => queueTask, handleServerMessage);
1031
+ }
1032
+ var shuttingDown = false;
1033
+ function gracefulShutdown(signal) {
1034
+ if (shuttingDown) return;
1035
+ shuttingDown = true;
1036
+ console.log(`[agent:${EMPLOYEE_ID}] received ${signal}, shutting down...`);
1037
+ for (const task of [mainTask, queueTask]) {
1038
+ if (!task) continue;
1039
+ task.cancelRequested = true;
1040
+ task.child?.kill("SIGTERM");
1041
+ send2({ type: "task.cancelled", taskId: task.taskId });
1042
+ }
1043
+ setTimeout(() => {
1044
+ for (const task of [mainTask, queueTask]) {
1045
+ task?.child?.kill("SIGKILL");
1046
+ }
1047
+ }, 2e3);
1048
+ if (connState.reconnectTimer) clearTimeout(connState.reconnectTimer);
1049
+ if (connState.heartbeatTimer) clearInterval(connState.heartbeatTimer);
1050
+ if (connState.socket && connState.socket.readyState === WebSocket2.OPEN) {
1051
+ connState.socket.close(1e3, "agent shutting down");
1052
+ }
1053
+ setTimeout(() => process.exit(0), 500);
1054
+ }
1055
+ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
1056
+ process.on("SIGINT", () => gracefulShutdown("SIGINT"));
1057
+ var isCli = process.argv[1] === fileURLToPath(import.meta.url);
1058
+ if (isCli) {
1059
+ const args = process.argv.slice(2);
1060
+ if (args.includes("--help") || args.includes("-h")) {
1061
+ console.log(`ai-teams-agent \u2014 AI Teams \u5458\u5DE5\u4EE3\u7406
1062
+
1063
+ \u7528\u6CD5: ai-teams-agent [\u9009\u9879]
1064
+
1065
+ \u9009\u9879:
1066
+ --server <url> \u670D\u52A1\u5668\u5730\u5740
1067
+ --token <token> \u8BA4\u8BC1 Token
1068
+ --id <id> \u5458\u5DE5 ID
1069
+ --name <name> \u5458\u5DE5\u540D\u79F0
1070
+ --workspace <dir> \u5DE5\u4F5C\u76EE\u5F55
1071
+ --runner <mode> Runner \u6A21\u5F0F (claude/fake)
1072
+ --config \u91CD\u65B0\u8FD0\u884C\u914D\u7F6E\u5411\u5BFC
1073
+ -h, --help \u663E\u793A\u5E2E\u52A9
1074
+ `);
1075
+ process.exit(0);
1076
+ }
1077
+ if (args.includes("--config")) {
1078
+ void runSetup(loadConfigFile()).then(() => {
1079
+ console.log(" \u2713 \u91CD\u65B0\u914D\u7F6E\u5B8C\u6210\uFF0C\u8BF7\u91CD\u65B0\u542F\u52A8 agent\u3002");
1080
+ process.exit(0);
1081
+ });
1082
+ } else {
1083
+ void (async () => {
1084
+ const fileConfig2 = loadConfigFile();
1085
+ const hasEnvConfig = process.env.AI_TEAMS_AUTH_TOKEN || process.env.SERVER_URL;
1086
+ if (!fileConfig2 && !hasEnvConfig) {
1087
+ console.log("\n \u26A0 \u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\u4E14\u672A\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF\uFF0C\u542F\u52A8\u914D\u7F6E\u5411\u5BFC...\n");
1088
+ await runSetup(null);
1089
+ }
1090
+ function getArgValue(name) {
1091
+ const idx = args.indexOf(name);
1092
+ if (idx === -1) return void 0;
1093
+ return args[idx + 1];
1094
+ }
1095
+ const cliServer = getArgValue("--server");
1096
+ const cliToken = getArgValue("--token");
1097
+ const cliId = getArgValue("--id");
1098
+ const cliName = getArgValue("--name");
1099
+ const cliWorkspace = getArgValue("--workspace");
1100
+ const cliRunner = getArgValue("--runner");
1101
+ if (cliServer) process.env.SERVER_URL = cliServer;
1102
+ if (cliToken) process.env.AI_TEAMS_AUTH_TOKEN = cliToken;
1103
+ if (cliId) process.env.EMPLOYEE_ID = cliId;
1104
+ if (cliName) process.env.EMPLOYEE_NAME = cliName;
1105
+ if (cliWorkspace) process.env.DEFAULT_WORKSPACE = cliWorkspace;
1106
+ if (cliRunner) process.env.RUNNER_MODE = cliRunner;
1107
+ console.log(` \u2713 \u6B63\u5728\u8FDE\u63A5\u670D\u52A1\u5668...`);
1108
+ connect2();
1109
+ })();
1110
+ }
1111
+ }
1112
+ export {
1113
+ connect2 as connect
1114
+ };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@csdwd/ai-teams-agent",
3
+ "version": "0.1.0",
4
+ "description": "AI Teams agent — connects to server via WebSocket, spawns Claude CLI to execute tasks",
5
+ "type": "module",
6
+ "bin": {
7
+ "ai-teams-agent": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "dev": "tsx watch src/index.ts",
14
+ "build": "node ../../scripts/build-bundle.js agent",
15
+ "start": "node dist/index.js",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "dependencies": {
19
+ "ws": "^8.18.0"
20
+ },
21
+ "devDependencies": {
22
+ "@ai-teams/shared": "workspace:*",
23
+ "@types/node": "^24.5.2",
24
+ "@types/ws": "^8.5.0",
25
+ "tsx": "^4.19.0",
26
+ "typescript": "^5.7.0"
27
+ },
28
+ "engines": {
29
+ "node": ">=22"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/siliconbeat/ai-teams.git",
34
+ "directory": "apps/agent"
35
+ },
36
+ "license": "MIT",
37
+ "keywords": [
38
+ "ai-teams",
39
+ "ai-agent",
40
+ "claude",
41
+ "websocket"
42
+ ]
43
+ }