@humanclaw/humanclaw 1.0.2 → 1.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/dist/index.js +505 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -368,6 +368,246 @@ function dispatchJob(request, db2) {
|
|
|
368
368
|
return { ...job, tasks };
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
+
// src/llm/claude.ts
|
|
372
|
+
var ClaudeProvider = class {
|
|
373
|
+
apiKey;
|
|
374
|
+
model;
|
|
375
|
+
constructor(apiKey, model) {
|
|
376
|
+
this.apiKey = apiKey;
|
|
377
|
+
this.model = model || "claude-sonnet-4-20250514";
|
|
378
|
+
}
|
|
379
|
+
async complete(request) {
|
|
380
|
+
const systemMessages = request.messages.filter((m) => m.role === "system");
|
|
381
|
+
const nonSystemMessages = request.messages.filter((m) => m.role !== "system");
|
|
382
|
+
const body = {
|
|
383
|
+
model: this.model,
|
|
384
|
+
max_tokens: request.max_tokens || 4096,
|
|
385
|
+
messages: nonSystemMessages.map((m) => ({ role: m.role, content: m.content }))
|
|
386
|
+
};
|
|
387
|
+
if (request.temperature !== void 0) {
|
|
388
|
+
body.temperature = request.temperature;
|
|
389
|
+
}
|
|
390
|
+
if (systemMessages.length > 0) {
|
|
391
|
+
body.system = systemMessages.map((m) => m.content).join("\n\n");
|
|
392
|
+
}
|
|
393
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
394
|
+
method: "POST",
|
|
395
|
+
headers: {
|
|
396
|
+
"Content-Type": "application/json",
|
|
397
|
+
"x-api-key": this.apiKey,
|
|
398
|
+
"anthropic-version": "2023-06-01"
|
|
399
|
+
},
|
|
400
|
+
body: JSON.stringify(body)
|
|
401
|
+
});
|
|
402
|
+
if (!response.ok) {
|
|
403
|
+
const errorText = await response.text();
|
|
404
|
+
throw new Error(`Claude API error (${response.status}): ${errorText}`);
|
|
405
|
+
}
|
|
406
|
+
const data = await response.json();
|
|
407
|
+
const textBlock = data.content.find((c) => c.type === "text");
|
|
408
|
+
if (!textBlock) {
|
|
409
|
+
throw new Error("Claude API returned no text content");
|
|
410
|
+
}
|
|
411
|
+
return { content: textBlock.text };
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// src/llm/openai.ts
|
|
416
|
+
var OpenAIProvider = class {
|
|
417
|
+
apiKey;
|
|
418
|
+
model;
|
|
419
|
+
constructor(apiKey, model) {
|
|
420
|
+
this.apiKey = apiKey;
|
|
421
|
+
this.model = model || "gpt-4o";
|
|
422
|
+
}
|
|
423
|
+
async complete(request) {
|
|
424
|
+
const body = {
|
|
425
|
+
model: this.model,
|
|
426
|
+
max_tokens: request.max_tokens || 4096,
|
|
427
|
+
messages: request.messages.map((m) => ({ role: m.role, content: m.content }))
|
|
428
|
+
};
|
|
429
|
+
if (request.temperature !== void 0) {
|
|
430
|
+
body.temperature = request.temperature;
|
|
431
|
+
}
|
|
432
|
+
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
433
|
+
method: "POST",
|
|
434
|
+
headers: {
|
|
435
|
+
"Content-Type": "application/json",
|
|
436
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
437
|
+
},
|
|
438
|
+
body: JSON.stringify(body)
|
|
439
|
+
});
|
|
440
|
+
if (!response.ok) {
|
|
441
|
+
const errorText = await response.text();
|
|
442
|
+
throw new Error(`OpenAI API error (${response.status}): ${errorText}`);
|
|
443
|
+
}
|
|
444
|
+
const data = await response.json();
|
|
445
|
+
const content = data.choices[0]?.message?.content;
|
|
446
|
+
if (!content) {
|
|
447
|
+
throw new Error("OpenAI API returned no content");
|
|
448
|
+
}
|
|
449
|
+
return { content };
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
// src/llm/index.ts
|
|
454
|
+
function getLlmConfig() {
|
|
455
|
+
const provider = process.env.HUMANCLAW_LLM_PROVIDER || "claude";
|
|
456
|
+
const apiKey = process.env.HUMANCLAW_LLM_API_KEY || "";
|
|
457
|
+
const model = process.env.HUMANCLAW_LLM_MODEL;
|
|
458
|
+
if (!apiKey) {
|
|
459
|
+
throw new Error(
|
|
460
|
+
"\u672A\u914D\u7F6E LLM API Key\u3002\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF HUMANCLAW_LLM_API_KEY\u3002\n\u4F8B\u5982: export HUMANCLAW_LLM_API_KEY=sk-..."
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
if (provider !== "claude" && provider !== "openai") {
|
|
464
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684 LLM \u63D0\u4F9B\u5546: ${provider}\u3002\u652F\u6301: claude, openai`);
|
|
465
|
+
}
|
|
466
|
+
return { provider, apiKey, model };
|
|
467
|
+
}
|
|
468
|
+
function createLlmProvider(config) {
|
|
469
|
+
const cfg = config || getLlmConfig();
|
|
470
|
+
switch (cfg.provider) {
|
|
471
|
+
case "claude":
|
|
472
|
+
return new ClaudeProvider(cfg.apiKey, cfg.model);
|
|
473
|
+
case "openai":
|
|
474
|
+
return new OpenAIProvider(cfg.apiKey, cfg.model);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/services/planner.ts
|
|
479
|
+
function buildSystemPrompt() {
|
|
480
|
+
return `\u4F60\u662F HumanClaw \u4EFB\u52A1\u7F16\u6392\u89C4\u5212\u5668\u3002\u4F60\u7684\u5DE5\u4F5C\u662F\u5C06\u7528\u6237\u7684\u9700\u6C42\u62C6\u89E3\u4E3A\u53EF\u4EE5\u5206\u53D1\u7ED9\u7269\u7406\u8282\u70B9\uFF08\u771F\u5B9E\u4EBA\u7C7B\uFF09\u6267\u884C\u7684\u72EC\u7ACB\u5B50\u4EFB\u52A1\u3002
|
|
481
|
+
|
|
482
|
+
\u89C4\u5219\uFF1A
|
|
483
|
+
1. \u5C06\u9700\u6C42\u62C6\u89E3\u4E3A\u6241\u5E73\u7684\u3001\u65E0\u4F9D\u8D56\u7684\u5B50\u4EFB\u52A1\u5217\u8868\uFF08\u6BCF\u4E2A\u4EFB\u52A1\u53EF\u4EE5\u72EC\u7ACB\u6267\u884C\uFF09
|
|
484
|
+
2. \u6839\u636E\u6BCF\u4E2A Agent \u7684\u6280\u80FD\uFF08capabilities\uFF09\u548C\u5F53\u524D\u8D1F\u8F7D\u6765\u5339\u914D\u5206\u914D
|
|
485
|
+
3. \u4E3A\u6BCF\u4E2A\u4EFB\u52A1\u751F\u6210\u4E00\u6BB5\u300C\u8BDD\u672F\u300D\u2014\u2014 \u8FD9\u662F\u76F4\u63A5\u53D1\u7ED9\u8BE5\u4EBA\u7C7B\u6267\u884C\u8005\u7684\u4EFB\u52A1\u8BF4\u660E\uFF0C\u8BED\u6C14\u81EA\u7136\u3001\u6E05\u6670\u3001\u4E13\u4E1A\uFF0C\u5305\u542B\u5177\u4F53\u7684\u4EA4\u4ED8\u7269\u8981\u6C42
|
|
486
|
+
4. \u6839\u636E\u4EFB\u52A1\u590D\u6742\u5EA6\u8BBE\u7F6E\u5408\u7406\u7684\u622A\u6B62\u65F6\u95F4\uFF08\u76F8\u5BF9\u4E8E\u5F53\u524D\u65F6\u95F4\uFF09
|
|
487
|
+
5. \u6BCF\u4E2A Agent \u6700\u591A\u5206\u914D\u4E00\u4E2A\u4EFB\u52A1\uFF08\u9664\u975E\u4EBA\u624B\u4E0D\u591F\uFF09
|
|
488
|
+
|
|
489
|
+
\u4F60\u5FC5\u987B\u4E25\u683C\u8F93\u51FA\u4EE5\u4E0B JSON \u683C\u5F0F\uFF08\u4E0D\u8981\u8F93\u51FA\u4EFB\u4F55\u5176\u4ED6\u5185\u5BB9\uFF09\uFF1A
|
|
490
|
+
|
|
491
|
+
\`\`\`json
|
|
492
|
+
[
|
|
493
|
+
{
|
|
494
|
+
"assignee_id": "agent\u7684ID",
|
|
495
|
+
"assignee_name": "agent\u7684\u540D\u5B57",
|
|
496
|
+
"todo_description": "\u7B80\u77ED\u7684\u4EFB\u52A1\u6807\u9898/\u63CF\u8FF0",
|
|
497
|
+
"briefing": "\u8BDD\u672F\uFF1A\u8BE6\u7EC6\u7684\u4EFB\u52A1\u8BF4\u660E\uFF0C\u5305\u62EC\u76EE\u6807\u3001\u4EA4\u4ED8\u7269\u8981\u6C42\u3001\u6CE8\u610F\u4E8B\u9879\u3002\u8BED\u6C14\u50CF\u4E00\u4E2A\u9760\u8C31\u7684\u9879\u76EE\u7ECF\u7406\u5728\u7ED9\u7EC4\u5458\u5206\u914D\u4EFB\u52A1\u3002",
|
|
498
|
+
"deadline": "ISO 8601 \u65F6\u95F4"
|
|
499
|
+
}
|
|
500
|
+
]
|
|
501
|
+
\`\`\``;
|
|
502
|
+
}
|
|
503
|
+
function buildUserPrompt(prompt, agents) {
|
|
504
|
+
const now = /* @__PURE__ */ new Date();
|
|
505
|
+
const agentList = agents.map((a) => {
|
|
506
|
+
const load = a.active_task_count > 0 ? `\u5F53\u524D\u6709 ${a.active_task_count} \u4E2A\u8FDB\u884C\u4E2D\u7684\u4EFB\u52A1` : "\u5F53\u524D\u7A7A\u95F2";
|
|
507
|
+
const speed = a.avg_delivery_hours !== null ? `\u5E73\u5747\u4EA4\u4ED8\u65F6\u95F4 ${a.avg_delivery_hours}h` : "\u6682\u65E0\u5386\u53F2\u6570\u636E";
|
|
508
|
+
return `- ${a.name} (ID: ${a.agent_id}) \u6280\u80FD: [${a.capabilities.join(", ")}] ${load} ${speed}`;
|
|
509
|
+
}).join("\n");
|
|
510
|
+
return `\u5F53\u524D\u65F6\u95F4: ${now.toISOString()}
|
|
511
|
+
|
|
512
|
+
\u53EF\u7528\u7684\u7269\u7406\u8282\u70B9\uFF1A
|
|
513
|
+
${agentList}
|
|
514
|
+
|
|
515
|
+
\u9700\u6C42\uFF1A
|
|
516
|
+
${prompt}
|
|
517
|
+
|
|
518
|
+
\u8BF7\u6839\u636E\u4EE5\u4E0A\u4FE1\u606F\u62C6\u89E3\u4EFB\u52A1\u5E76\u5206\u914D\u3002\u8F93\u51FA JSON \u6570\u7EC4\u3002`;
|
|
519
|
+
}
|
|
520
|
+
function extractJson(raw) {
|
|
521
|
+
const codeBlockMatch = raw.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
|
|
522
|
+
if (codeBlockMatch) {
|
|
523
|
+
return codeBlockMatch[1].trim();
|
|
524
|
+
}
|
|
525
|
+
const arrayMatch = raw.match(/\[[\s\S]*\]/);
|
|
526
|
+
if (arrayMatch) {
|
|
527
|
+
return arrayMatch[0];
|
|
528
|
+
}
|
|
529
|
+
return raw.trim();
|
|
530
|
+
}
|
|
531
|
+
function validatePlannedTasks(parsed, validAgentIds, agents) {
|
|
532
|
+
if (!Array.isArray(parsed)) {
|
|
533
|
+
throw new Error("LLM \u8FD4\u56DE\u7684\u4E0D\u662F\u6709\u6548\u7684\u4EFB\u52A1\u6570\u7EC4");
|
|
534
|
+
}
|
|
535
|
+
if (parsed.length === 0) {
|
|
536
|
+
throw new Error("LLM \u672A\u751F\u6210\u4EFB\u4F55\u4EFB\u52A1");
|
|
537
|
+
}
|
|
538
|
+
return parsed.map((item, i) => {
|
|
539
|
+
if (!item.todo_description || typeof item.todo_description !== "string") {
|
|
540
|
+
throw new Error(`\u4EFB\u52A1 ${i + 1} \u7F3A\u5C11 todo_description`);
|
|
541
|
+
}
|
|
542
|
+
if (!item.briefing || typeof item.briefing !== "string") {
|
|
543
|
+
throw new Error(`\u4EFB\u52A1 ${i + 1} \u7F3A\u5C11 briefing (\u8BDD\u672F)`);
|
|
544
|
+
}
|
|
545
|
+
let assigneeId = String(item.assignee_id || "");
|
|
546
|
+
let assigneeName = String(item.assignee_name || "");
|
|
547
|
+
if (!validAgentIds.has(assigneeId)) {
|
|
548
|
+
const fallback = agents[0];
|
|
549
|
+
if (fallback) {
|
|
550
|
+
assigneeId = fallback.agent_id;
|
|
551
|
+
assigneeName = fallback.name;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
let deadline = String(item.deadline || "");
|
|
555
|
+
if (!deadline || isNaN(Date.parse(deadline))) {
|
|
556
|
+
deadline = new Date(Date.now() + 24 * 60 * 60 * 1e3).toISOString();
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
assignee_id: assigneeId,
|
|
560
|
+
assignee_name: assigneeName,
|
|
561
|
+
todo_description: String(item.todo_description),
|
|
562
|
+
briefing: String(item.briefing),
|
|
563
|
+
deadline
|
|
564
|
+
};
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
async function planJob(request, provider, db2) {
|
|
568
|
+
const conn = db2 ?? getDb();
|
|
569
|
+
const allAgents = listAgentsWithMetrics(conn);
|
|
570
|
+
let agents;
|
|
571
|
+
if (request.agent_ids && request.agent_ids.length > 0) {
|
|
572
|
+
agents = allAgents.filter((a) => request.agent_ids.includes(a.agent_id));
|
|
573
|
+
if (agents.length === 0) {
|
|
574
|
+
throw new Error("\u6307\u5B9A\u7684 Agent \u5747\u4E0D\u5B58\u5728");
|
|
575
|
+
}
|
|
576
|
+
} else {
|
|
577
|
+
agents = allAgents.filter((a) => a.status === "IDLE");
|
|
578
|
+
if (agents.length === 0) {
|
|
579
|
+
agents = allAgents.filter((a) => a.status !== "OFFLINE" && a.status !== "OOM");
|
|
580
|
+
}
|
|
581
|
+
if (agents.length === 0) {
|
|
582
|
+
throw new Error("\u6CA1\u6709\u53EF\u7528\u7684\u7269\u7406\u8282\u70B9\u3002\u8BF7\u5148\u5728\u78B3\u57FA\u7B97\u529B\u6C60\u4E2D\u6DFB\u52A0\u8282\u70B9\u3002");
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const llm = provider ?? createLlmProvider();
|
|
586
|
+
const systemPrompt = buildSystemPrompt();
|
|
587
|
+
const userPrompt = buildUserPrompt(request.prompt, agents);
|
|
588
|
+
const response = await llm.complete({
|
|
589
|
+
messages: [
|
|
590
|
+
{ role: "system", content: systemPrompt },
|
|
591
|
+
{ role: "user", content: userPrompt }
|
|
592
|
+
],
|
|
593
|
+
temperature: 0.3,
|
|
594
|
+
max_tokens: 4096
|
|
595
|
+
});
|
|
596
|
+
const jsonStr = extractJson(response.content);
|
|
597
|
+
let parsed;
|
|
598
|
+
try {
|
|
599
|
+
parsed = JSON.parse(jsonStr);
|
|
600
|
+
} catch {
|
|
601
|
+
throw new Error("AI \u8FD4\u56DE\u7684\u5185\u5BB9\u65E0\u6CD5\u89E3\u6790\u4E3A JSON\uFF0C\u8BF7\u91CD\u8BD5");
|
|
602
|
+
}
|
|
603
|
+
const validIds = new Set(agents.map((a) => a.agent_id));
|
|
604
|
+
const plannedTasks = validatePlannedTasks(parsed, validIds, agents);
|
|
605
|
+
return {
|
|
606
|
+
original_prompt: request.prompt,
|
|
607
|
+
planned_tasks: plannedTasks
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
371
611
|
// src/routes/jobs.ts
|
|
372
612
|
var router2 = Router2();
|
|
373
613
|
router2.post("/create", (req, res) => {
|
|
@@ -399,6 +639,21 @@ router2.get("/active", (_req, res) => {
|
|
|
399
639
|
const jobs = listActiveJobs();
|
|
400
640
|
res.json({ jobs });
|
|
401
641
|
});
|
|
642
|
+
router2.post("/plan", async (req, res) => {
|
|
643
|
+
const body = req.body;
|
|
644
|
+
if (!body.prompt || typeof body.prompt !== "string") {
|
|
645
|
+
res.status(400).json({ error: "prompt is required" });
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
try {
|
|
649
|
+
const plan = await planJob(body);
|
|
650
|
+
res.json(plan);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
653
|
+
const status = message.includes("API Key") || message.includes("API key") ? 503 : 400;
|
|
654
|
+
res.status(status).json({ error: message });
|
|
655
|
+
}
|
|
656
|
+
});
|
|
402
657
|
router2.get("/:job_id", (req, res) => {
|
|
403
658
|
const { job_id } = req.params;
|
|
404
659
|
const job = getJobWithTasks(job_id);
|
|
@@ -586,11 +841,11 @@ nav{display:flex;border-bottom:1px solid var(--border);padding:0 32px;gap:4px}
|
|
|
586
841
|
.tab.active{color:var(--accent);border-bottom-color:var(--accent)}
|
|
587
842
|
main{padding:24px 32px;max-width:1200px}
|
|
588
843
|
.panel{display:none}.panel.active{display:block}
|
|
589
|
-
/*
|
|
844
|
+
/* Cards */
|
|
590
845
|
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;transition:border-color .15s}
|
|
591
846
|
.card:hover{border-color:color-mix(in srgb,var(--accent) 40%,transparent)}
|
|
592
847
|
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;margin-top:16px}
|
|
593
|
-
/*
|
|
848
|
+
/* Agent Card */
|
|
594
849
|
.agent-header{display:flex;align-items:center;gap:10px;margin-bottom:10px}
|
|
595
850
|
.dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}
|
|
596
851
|
.dot.IDLE{background:var(--green);box-shadow:0 0 8px var(--green)}.dot.BUSY{background:var(--yellow);box-shadow:0 0 8px var(--yellow)}.dot.OFFLINE{background:var(--red);box-shadow:0 0 6px var(--red)}.dot.OOM{background:var(--purple);box-shadow:0 0 8px var(--purple)}
|
|
@@ -603,13 +858,13 @@ main{padding:24px 32px;max-width:1200px}
|
|
|
603
858
|
.agent-actions select{background:var(--surface-hover);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:11px;cursor:pointer;outline:none}
|
|
604
859
|
.agent-actions button{background:var(--red);color:#fff;border:none;border-radius:6px;padding:4px 10px;font-size:11px;cursor:pointer}
|
|
605
860
|
.agent-actions button:hover{opacity:.8}
|
|
606
|
-
/*
|
|
861
|
+
/* Stats Bar */
|
|
607
862
|
.stats{display:flex;gap:12px;flex-wrap:wrap}
|
|
608
863
|
.stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 22px;text-align:center;min-width:90px}
|
|
609
864
|
.stat-val{font-size:28px;font-weight:700;font-family:var(--font-mono)}
|
|
610
865
|
.stat-lbl{font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:1px;margin-top:2px}
|
|
611
|
-
/*
|
|
612
|
-
.form-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px;margin-top:16px;max-width:
|
|
866
|
+
/* Forms */
|
|
867
|
+
.form-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px;margin-top:16px;max-width:620px}
|
|
613
868
|
.form-card h3{font-size:15px;color:var(--accent);font-family:var(--font-mono);margin-bottom:16px}
|
|
614
869
|
.fg{margin-bottom:14px}
|
|
615
870
|
.fg label{display:block;font-size:12px;color:var(--text-dim);margin-bottom:5px;font-weight:500}
|
|
@@ -620,17 +875,18 @@ main{padding:24px 32px;max-width:1200px}
|
|
|
620
875
|
.btn{padding:10px 22px;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;transition:all .15s;display:inline-flex;align-items:center;gap:6px}
|
|
621
876
|
.btn:hover{opacity:.85;transform:translateY(-1px)}
|
|
622
877
|
.btn:active{transform:translateY(0)}
|
|
878
|
+
.btn:disabled{opacity:.5;cursor:not-allowed;transform:none}
|
|
623
879
|
.btn-primary{background:var(--accent);color:var(--bg)}
|
|
624
880
|
.btn-green{background:var(--green);color:#fff}
|
|
625
881
|
.btn-danger{background:var(--red);color:#fff}
|
|
626
882
|
.btn-ghost{background:transparent;color:var(--accent);border:1px solid var(--border)}
|
|
627
883
|
.btn-ghost:hover{background:var(--accent-dim)}
|
|
628
884
|
.btn-sm{padding:6px 14px;font-size:12px}
|
|
629
|
-
.btn-group{display:flex;gap:10px;margin-top:16px}
|
|
630
|
-
/*
|
|
885
|
+
.btn-group{display:flex;gap:10px;margin-top:16px;flex-wrap:wrap}
|
|
886
|
+
/* Section header */
|
|
631
887
|
.section-hd{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}
|
|
632
888
|
.section-hd h2{font-size:16px;font-weight:600}
|
|
633
|
-
/*
|
|
889
|
+
/* Job card */
|
|
634
890
|
.job-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-top:14px}
|
|
635
891
|
.job-hd{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}
|
|
636
892
|
.job-title{font-weight:600;font-size:14px}
|
|
@@ -646,24 +902,50 @@ main{padding:24px 32px;max-width:1200px}
|
|
|
646
902
|
.tcard{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:6px;font-size:12px}
|
|
647
903
|
.tcard-trace{font-family:var(--font-mono);font-size:10px;color:var(--accent);margin-bottom:3px}
|
|
648
904
|
.tcard-meta{font-size:10px;color:var(--text-dim);margin-top:3px}
|
|
649
|
-
/*
|
|
650
|
-
.task-row{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:14px;margin-bottom:10px;position:relative}
|
|
651
|
-
.task-row .remove-task{position:absolute;top:8px;right:10px;background:none;border:none;color:var(--red);cursor:pointer;font-size:16px;line-height:1}
|
|
652
|
-
.task-row .fg{margin-bottom:10px}
|
|
653
|
-
.task-row .fg:last-child{margin-bottom:0}
|
|
654
|
-
.task-row-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
|
655
|
-
/* \u2500\u2500\u2500 Toast \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
905
|
+
/* Toast */
|
|
656
906
|
.toast{position:fixed;bottom:24px;right:24px;padding:12px 20px;border-radius:8px;font-size:13px;z-index:9999;animation:slide-up .25s ease;font-weight:500;box-shadow:0 8px 24px rgba(0,0,0,.4)}
|
|
657
907
|
.toast.ok{background:var(--green);color:#fff}.toast.err{background:var(--red);color:#fff}
|
|
658
908
|
@keyframes slide-up{from{transform:translateY(16px);opacity:0}to{transform:translateY(0);opacity:1}}
|
|
659
|
-
/*
|
|
909
|
+
/* Empty state */
|
|
660
910
|
.empty-state{text-align:center;padding:48px 20px;color:var(--text-dim)}
|
|
661
911
|
.empty-state .icon{font-size:48px;margin-bottom:12px;opacity:.6}
|
|
662
912
|
.empty-state p{font-size:14px;margin-bottom:16px;max-width:360px;margin-left:auto;margin-right:auto;line-height:1.6}
|
|
663
|
-
/*
|
|
664
|
-
.overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:100;display:flex;align-items:flex-start;justify-content:center;padding-top:
|
|
665
|
-
.overlay .form-card{max-width:
|
|
913
|
+
/* Modal/Overlay */
|
|
914
|
+
.overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:100;display:flex;align-items:flex-start;justify-content:center;padding-top:60px;animation:fade-in .15s ease}
|
|
915
|
+
.overlay .form-card{max-width:620px;width:100%;max-height:85vh;overflow-y:auto;margin:0}
|
|
666
916
|
@keyframes fade-in{from{opacity:0}to{opacity:1}}
|
|
917
|
+
/* Agent chips for AI planning */
|
|
918
|
+
.chip-bar{display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap}
|
|
919
|
+
.chip-filter{background:var(--surface-hover);color:var(--text-dim);border:1px solid var(--border);border-radius:14px;padding:3px 12px;font-size:11px;cursor:pointer;transition:all .15s}
|
|
920
|
+
.chip-filter.active{background:var(--accent-dim);color:var(--accent);border-color:var(--accent)}
|
|
921
|
+
.agent-chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
|
|
922
|
+
.achip{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:6px;user-select:none}
|
|
923
|
+
.achip:hover{border-color:var(--text-dim)}
|
|
924
|
+
.achip.selected{border-color:var(--accent);background:var(--accent-dim)}
|
|
925
|
+
.achip .adot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
|
926
|
+
.achip .adot.IDLE{background:var(--green)}.achip .adot.BUSY{background:var(--yellow)}.achip .adot.OFFLINE{background:var(--red)}.achip .adot.OOM{background:var(--purple)}
|
|
927
|
+
/* Plan preview cards */
|
|
928
|
+
.plan-card{background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:16px;margin-bottom:12px}
|
|
929
|
+
.plan-card-hd{display:flex;align-items:center;gap:8px;margin-bottom:8px}
|
|
930
|
+
.plan-card-hd .adot{width:8px;height:8px;border-radius:50%}
|
|
931
|
+
.plan-card-agent{font-weight:600;font-size:13px}
|
|
932
|
+
.plan-card-agentid{font-family:var(--font-mono);font-size:10px;color:var(--text-dim)}
|
|
933
|
+
.plan-card-desc{font-size:13px;margin-bottom:10px;font-weight:500}
|
|
934
|
+
.briefing-box{background:var(--surface);border-left:3px solid var(--accent);border-radius:0 6px 6px 0;padding:10px 14px;font-size:12px;line-height:1.6;color:var(--text);margin-bottom:8px;position:relative}
|
|
935
|
+
.briefing-label{font-size:10px;color:var(--accent);font-weight:600;margin-bottom:4px;font-family:var(--font-mono)}
|
|
936
|
+
.briefing-copy{position:absolute;top:8px;right:8px;background:var(--surface-hover);border:1px solid var(--border);color:var(--text-dim);border-radius:4px;padding:2px 8px;font-size:10px;cursor:pointer}
|
|
937
|
+
.briefing-copy:hover{color:var(--accent);border-color:var(--accent)}
|
|
938
|
+
.plan-card-dl{font-size:11px;color:var(--text-dim);font-family:var(--font-mono)}
|
|
939
|
+
/* Spinner */
|
|
940
|
+
.spinner-wrap{text-align:center;padding:40px 20px}
|
|
941
|
+
.spinner{display:inline-block;width:28px;height:28px;border:3px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .6s linear infinite}
|
|
942
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
943
|
+
.spinner-text{color:var(--text-dim);font-size:13px;margin-top:12px}
|
|
944
|
+
/* Manual task row */
|
|
945
|
+
.task-row{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:14px;margin-bottom:10px;position:relative}
|
|
946
|
+
.task-row .remove-task{position:absolute;top:8px;right:10px;background:none;border:none;color:var(--red);cursor:pointer;font-size:16px;line-height:1}
|
|
947
|
+
.task-row .fg{margin-bottom:10px}
|
|
948
|
+
.task-row .fg:last-child{margin-bottom:0}
|
|
667
949
|
</style>
|
|
668
950
|
</head>
|
|
669
951
|
<body>
|
|
@@ -691,6 +973,8 @@ function toast(msg,ok){
|
|
|
691
973
|
setTimeout(()=>t.remove(),3500);
|
|
692
974
|
}
|
|
693
975
|
let cachedAgents=[];
|
|
976
|
+
let currentPlan=null;
|
|
977
|
+
let selectedAgentIds=new Set();
|
|
694
978
|
|
|
695
979
|
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
696
980
|
// FLEET
|
|
@@ -781,13 +1065,12 @@ function tcard(t){
|
|
|
781
1065
|
async function loadPipeline(el){
|
|
782
1066
|
el.innerHTML='<div class="empty-state"><p>\u52A0\u8F7D\u4E2D...</p></div>';
|
|
783
1067
|
try{
|
|
784
|
-
// refresh agents cache
|
|
785
1068
|
try{const ar=await fetch(API+'/nodes/status');const ad=await ar.json();cachedAgents=ad.agents||[]}catch{}
|
|
786
1069
|
const r=await fetch(API+'/jobs/active');
|
|
787
1070
|
const d=await r.json();
|
|
788
1071
|
let h='<div class="section-hd"><h2>\u5F02\u6B65\u7F16\u6392\u5927\u76D8</h2><button class="btn btn-primary btn-sm" onclick="showCreateJob()">+ \u521B\u5EFA\u4EFB\u52A1</button></div>';
|
|
789
1072
|
if(!d.jobs.length){
|
|
790
|
-
h+='<div class="empty-state" style="margin-top:32px"><div class="icon">\u{1F4CB}</div><p>\u6682\u65E0\u8FDB\u884C\u4E2D\u7684\u4EFB\u52A1\u3002\u70B9\u51FB\u4E0A\u65B9\u300C+ \u521B\u5EFA\u4EFB\u52A1\u300D\
|
|
1073
|
+
h+='<div class="empty-state" style="margin-top:32px"><div class="icon">\u{1F4CB}</div><p>\u6682\u65E0\u8FDB\u884C\u4E2D\u7684\u4EFB\u52A1\u3002\u70B9\u51FB\u4E0A\u65B9\u300C+ \u521B\u5EFA\u4EFB\u52A1\u300D\uFF0C\u8F93\u5165\u9700\u6C42\u540E AI \u81EA\u52A8\u89C4\u5212\u5206\u53D1\u3002';
|
|
791
1074
|
if(!cachedAgents.length)h+='<br/><br/>\u26A0\uFE0F \u9700\u8981\u5148\u5728\u300C\u78B3\u57FA\u7B97\u529B\u6C60\u300D\u4E2D\u6DFB\u52A0\u7269\u7406\u8282\u70B9\u3002';
|
|
792
1075
|
h+='</p></div>';
|
|
793
1076
|
el.innerHTML=h;return;
|
|
@@ -809,19 +1092,143 @@ async function loadPipeline(el){
|
|
|
809
1092
|
el.innerHTML=h;
|
|
810
1093
|
}catch(e){el.innerHTML='<div class="empty-state"><p>\u52A0\u8F7D\u5931\u8D25: '+e.message+'</p></div>'}
|
|
811
1094
|
}
|
|
1095
|
+
|
|
1096
|
+
// \u2500\u2500\u2500 AI Planning Flow \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
812
1097
|
window.showCreateJob=function(){
|
|
813
1098
|
if(!cachedAgents.length){toast('\u8BF7\u5148\u5728\u300C\u78B3\u57FA\u7B97\u529B\u6C60\u300D\u4E2D\u6DFB\u52A0\u81F3\u5C11\u4E00\u4E2A\u7269\u7406\u8282\u70B9',false);return}
|
|
1099
|
+
currentPlan=null;
|
|
1100
|
+
// Pre-select all IDLE agents
|
|
1101
|
+
selectedAgentIds=new Set(cachedAgents.filter(a=>a.status==='IDLE').map(a=>a.agent_id));
|
|
814
1102
|
const ov=document.createElement('div');ov.className='overlay';ov.id='overlay';
|
|
815
1103
|
ov.addEventListener('click',e=>{if(e.target===ov)ov.remove()});
|
|
1104
|
+
renderPlanStep1(ov);
|
|
1105
|
+
document.body.appendChild(ov);
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
function renderPlanStep1(ov){
|
|
1109
|
+
let chipFilter='all';
|
|
1110
|
+
function renderChips(filter){
|
|
1111
|
+
chipFilter=filter;
|
|
1112
|
+
const filtered=filter==='idle'?cachedAgents.filter(a=>a.status==='IDLE'):cachedAgents;
|
|
1113
|
+
let ch='<div class="chip-bar">';
|
|
1114
|
+
ch+='<span class="chip-filter'+(filter==='all'?' active':'')+'" onclick="window._filterChips(\\'all\\')">\u5168\u90E8</span>';
|
|
1115
|
+
ch+='<span class="chip-filter'+(filter==='idle'?' active':'')+'" onclick="window._filterChips(\\'idle\\')">\u4EC5\u7A7A\u95F2</span>';
|
|
1116
|
+
ch+='</div>';
|
|
1117
|
+
ch+='<div class="agent-chips">';
|
|
1118
|
+
for(const a of filtered){
|
|
1119
|
+
const sel=selectedAgentIds.has(a.agent_id);
|
|
1120
|
+
ch+='<div class="achip'+(sel?' selected':'')+'" onclick="window._toggleChip(\\''+a.agent_id+'\\')">';
|
|
1121
|
+
ch+='<span class="adot '+a.status+'"></span>';
|
|
1122
|
+
ch+=esc(a.name);
|
|
1123
|
+
ch+='</div>';
|
|
1124
|
+
}
|
|
1125
|
+
ch+='</div>';
|
|
1126
|
+
return ch;
|
|
1127
|
+
}
|
|
1128
|
+
window._filterChips=function(f){
|
|
1129
|
+
chipFilter=f;
|
|
1130
|
+
const el=document.getElementById('agent-chip-area');
|
|
1131
|
+
if(el)el.innerHTML=renderChips(f);
|
|
1132
|
+
};
|
|
1133
|
+
window._toggleChip=function(id){
|
|
1134
|
+
if(selectedAgentIds.has(id))selectedAgentIds.delete(id);
|
|
1135
|
+
else selectedAgentIds.add(id);
|
|
1136
|
+
const el=document.getElementById('agent-chip-area');
|
|
1137
|
+
if(el)el.innerHTML=renderChips(chipFilter);
|
|
1138
|
+
};
|
|
1139
|
+
|
|
1140
|
+
ov.innerHTML='<div class="form-card"><h3>AI \u667A\u80FD\u89C4\u5212</h3>'
|
|
1141
|
+
+'<div class="fg"><label>\u8F93\u5165\u4F60\u7684\u9700\u6C42</label><textarea id="plan-prompt" rows="3" placeholder="\u4F8B: \u5B8C\u6210\u9996\u9875\u91CD\u6784\uFF0C\u5305\u62EC\u5BFC\u822A\u680F\u3001\u5185\u5BB9\u533A\u548C\u9875\u811A\u7684\u54CD\u5E94\u5F0F\u6539\u7248" style="font-family:var(--font-sans);font-size:13px"></textarea></div>'
|
|
1142
|
+
+'<div class="fg"><label>\u9009\u62E9\u53C2\u4E0E\u7684\u7269\u7406\u8282\u70B9 <span style="color:var(--text-dim);font-weight:400">(\u9ED8\u8BA4\u9009\u4E2D\u7A7A\u95F2\u8282\u70B9)</span></label>'
|
|
1143
|
+
+'<div id="agent-chip-area">'+renderChips('all')+'</div></div>'
|
|
1144
|
+
+'<div class="fg"><label>OpenClaw \u56DE\u8C03\u5730\u5740 <span style="color:var(--text-dim);font-weight:400">(\u53EF\u9009)</span></label><input id="plan-callback" placeholder="https://..."/></div>'
|
|
1145
|
+
+'<div class="btn-group">'
|
|
1146
|
+
+'<button class="btn btn-primary" onclick="planWithAI()">AI \u89C4\u5212</button>'
|
|
1147
|
+
+'<button class="btn btn-ghost" onclick="showManualCreate()">\u624B\u52A8\u521B\u5EFA</button>'
|
|
1148
|
+
+'<button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button>'
|
|
1149
|
+
+'</div></div>';
|
|
1150
|
+
setTimeout(()=>{const ta=document.getElementById('plan-prompt');if(ta)ta.focus()},50);
|
|
1151
|
+
};
|
|
1152
|
+
|
|
1153
|
+
window.planWithAI=async function(){
|
|
1154
|
+
const prompt=document.getElementById('plan-prompt').value.trim();
|
|
1155
|
+
const callback=document.getElementById('plan-callback')?.value.trim()||'';
|
|
1156
|
+
if(!prompt){toast('\u8BF7\u8F93\u5165\u9700\u6C42\u63CF\u8FF0',false);return}
|
|
1157
|
+
if(selectedAgentIds.size===0){toast('\u8BF7\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A\u7269\u7406\u8282\u70B9',false);return}
|
|
1158
|
+
|
|
1159
|
+
const ov=document.getElementById('overlay');
|
|
1160
|
+
const fc=ov.querySelector('.form-card');
|
|
1161
|
+
fc.innerHTML='<h3>AI \u667A\u80FD\u89C4\u5212</h3><div class="spinner-wrap"><div class="spinner"></div><div class="spinner-text">AI \u6B63\u5728\u5206\u6790\u9700\u6C42\u3001\u5339\u914D\u8282\u70B9\u3001\u751F\u6210\u8BDD\u672F...</div></div>';
|
|
1162
|
+
|
|
1163
|
+
try{
|
|
1164
|
+
const r=await fetch(API+'/jobs/plan',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({prompt,agent_ids:Array.from(selectedAgentIds)})});
|
|
1165
|
+
const d=await r.json();
|
|
1166
|
+
if(!r.ok){
|
|
1167
|
+
toast(d.error||'\u89C4\u5212\u5931\u8D25',false);
|
|
1168
|
+
renderPlanStep1(ov);
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
currentPlan={...d,openclaw_callback:callback};
|
|
1172
|
+
renderPlanStep2(ov);
|
|
1173
|
+
}catch(e){
|
|
1174
|
+
toast('\u7F51\u7EDC\u9519\u8BEF: '+e.message,false);
|
|
1175
|
+
renderPlanStep1(ov);
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
function renderPlanStep2(ov){
|
|
1180
|
+
const p=currentPlan;
|
|
1181
|
+
let h='<h3>\u89C4\u5212\u9884\u89C8</h3>';
|
|
1182
|
+
h+='<div style="font-size:12px;color:var(--text-dim);margin-bottom:14px">\u9700\u6C42: '+esc(p.original_prompt)+'</div>';
|
|
1183
|
+
for(let i=0;i<p.planned_tasks.length;i++){
|
|
1184
|
+
const t=p.planned_tasks[i];
|
|
1185
|
+
const agent=cachedAgents.find(a=>a.agent_id===t.assignee_id);
|
|
1186
|
+
const status=agent?agent.status:'IDLE';
|
|
1187
|
+
h+='<div class="plan-card">';
|
|
1188
|
+
h+='<div class="plan-card-hd"><span class="adot '+status+'"></span><span class="plan-card-agent">'+esc(t.assignee_name)+'</span><span class="plan-card-agentid">'+t.assignee_id+'</span></div>';
|
|
1189
|
+
h+='<div class="plan-card-desc">'+esc(t.todo_description)+'</div>';
|
|
1190
|
+
h+='<div class="briefing-box"><div class="briefing-label">\u8BDD\u672F (\u53EF\u76F4\u63A5\u53D1\u7ED9 TA)</div><button class="briefing-copy" onclick="window._copyBriefing('+i+')">\u590D\u5236</button>'+esc(t.briefing)+'</div>';
|
|
1191
|
+
h+='<div class="plan-card-dl">\u622A\u6B62: '+new Date(t.deadline).toLocaleString()+'</div>';
|
|
1192
|
+
h+='</div>';
|
|
1193
|
+
}
|
|
1194
|
+
h+='<div class="btn-group">';
|
|
1195
|
+
h+='<button class="btn btn-green" onclick="dispatchPlan()">\u786E\u8BA4\u5206\u53D1</button>';
|
|
1196
|
+
h+='<button class="btn btn-ghost" onclick="renderPlanStep1(document.getElementById(\\'overlay\\'))">\u91CD\u65B0\u89C4\u5212</button>';
|
|
1197
|
+
h+='<button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button>';
|
|
1198
|
+
h+='</div>';
|
|
1199
|
+
ov.querySelector('.form-card').innerHTML=h;
|
|
1200
|
+
}
|
|
1201
|
+
window.renderPlanStep1=renderPlanStep1;
|
|
1202
|
+
|
|
1203
|
+
window._copyBriefing=function(i){
|
|
1204
|
+
const text=currentPlan.planned_tasks[i].briefing;
|
|
1205
|
+
navigator.clipboard.writeText(text).then(()=>toast('\u8BDD\u672F\u5DF2\u590D\u5236',true)).catch(()=>toast('\u590D\u5236\u5931\u8D25',false));
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
window.dispatchPlan=async function(){
|
|
1209
|
+
const p=currentPlan;
|
|
1210
|
+
const tasks=p.planned_tasks.map(t=>({assignee_id:t.assignee_id,todo_description:t.todo_description,deadline:t.deadline}));
|
|
1211
|
+
try{
|
|
1212
|
+
const r=await fetch(API+'/jobs/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({original_prompt:p.original_prompt,openclaw_callback:p.openclaw_callback||'',tasks})});
|
|
1213
|
+
const d=await r.json();
|
|
1214
|
+
if(!r.ok){toast(d.error||'\u5206\u53D1\u5931\u8D25',false);return}
|
|
1215
|
+
toast('\u4EFB\u52A1\u5DF2\u5206\u53D1\uFF01Job: '+d.job_id,true);
|
|
1216
|
+
document.getElementById('overlay').remove();
|
|
1217
|
+
load('pipeline');
|
|
1218
|
+
}catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
|
|
1219
|
+
};
|
|
1220
|
+
|
|
1221
|
+
// \u2500\u2500\u2500 Manual creation (fallback) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1222
|
+
window.showManualCreate=function(){
|
|
1223
|
+
const ov=document.getElementById('overlay');
|
|
816
1224
|
const optionsHtml=cachedAgents.map(a=>'<option value="'+a.agent_id+'">'+esc(a.name)+' ('+a.agent_id+')</option>').join('');
|
|
817
1225
|
const tomorrow=new Date(Date.now()+86400000).toISOString().slice(0,16);
|
|
818
|
-
ov.innerHTML='<
|
|
1226
|
+
ov.querySelector('.form-card').innerHTML='<h3>\u624B\u52A8\u521B\u5EFA\u4EFB\u52A1</h3>'
|
|
819
1227
|
+'<div class="fg"><label>\u4EFB\u52A1\u63CF\u8FF0 (\u539F\u59CB\u9700\u6C42)</label><input id="cj-prompt" placeholder="\u4F8B: \u5B8C\u6210\u9996\u9875\u6539\u7248"/></div>'
|
|
820
1228
|
+'<div class="fg"><label>OpenClaw \u56DE\u8C03\u5730\u5740 (\u53EF\u9009)</label><input id="cj-callback" placeholder="https://..."/></div>'
|
|
821
1229
|
+'<div style="margin-bottom:10px;display:flex;justify-content:space-between;align-items:center"><label style="font-size:13px;font-weight:600;color:var(--text)">\u5B50\u4EFB\u52A1\u5217\u8868</label><button class="btn btn-ghost btn-sm" onclick="addTaskRow()">+ \u6DFB\u52A0\u5B50\u4EFB\u52A1</button></div>'
|
|
822
|
-
+'<div id="task-rows"><div class="task-row"
|
|
823
|
-
+'<div class="btn-group"><button class="btn btn-primary" onclick="submitJob()">\u521B\u5EFA\u5E76\u5206\u53D1</button><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button></div
|
|
824
|
-
document.body.appendChild(ov);
|
|
1230
|
+
+'<div id="task-rows"><div class="task-row"><div class="fg"><label>\u6307\u6D3E\u8282\u70B9</label><select class="tr-agent">'+optionsHtml+'</select></div><div class="fg"><label>\u4EFB\u52A1\u63CF\u8FF0</label><input class="tr-desc" placeholder="\u5177\u4F53\u8981\u505A\u4EC0\u4E48..."/></div><div class="fg"><label>\u622A\u6B62\u65F6\u95F4</label><input class="tr-deadline" type="datetime-local" value="'+tomorrow+'"/></div></div></div>'
|
|
1231
|
+
+'<div class="btn-group"><button class="btn btn-primary" onclick="submitJob()">\u521B\u5EFA\u5E76\u5206\u53D1</button><button class="btn btn-ghost" onclick="renderPlanStep1(document.getElementById(\\'overlay\\'))">\u8FD4\u56DE AI \u89C4\u5212</button><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button></div>';
|
|
825
1232
|
};
|
|
826
1233
|
window.addTaskRow=function(){
|
|
827
1234
|
const optionsHtml=cachedAgents.map(a=>'<option value="'+a.agent_id+'">'+esc(a.name)+' ('+a.agent_id+')</option>').join('');
|
|
@@ -1059,5 +1466,78 @@ program.command("status").description("Show active jobs overview").action(() =>
|
|
|
1059
1466
|
console.log();
|
|
1060
1467
|
}
|
|
1061
1468
|
});
|
|
1469
|
+
program.command("plan").description("AI-powered task planning from natural language").argument("[prompt]", "What you want to get done").option("--agents <ids>", "Comma-separated agent IDs (default: all IDLE)").option("--dispatch", "Automatically dispatch after planning").action(async (promptArg, opts) => {
|
|
1470
|
+
const db2 = getDb();
|
|
1471
|
+
initSchema(db2);
|
|
1472
|
+
p.intro(chalk.bgCyan(" AI \u667A\u80FD\u89C4\u5212 "));
|
|
1473
|
+
let prompt = promptArg;
|
|
1474
|
+
if (!prompt) {
|
|
1475
|
+
const input = await p.text({
|
|
1476
|
+
message: "\u8F93\u5165\u4F60\u7684\u9700\u6C42:",
|
|
1477
|
+
placeholder: "\u4F8B: \u5B8C\u6210\u9996\u9875\u91CD\u6784\uFF0C\u5305\u62EC\u5BFC\u822A\u680F\u548C\u5185\u5BB9\u533A\u7684\u54CD\u5E94\u5F0F\u6539\u7248",
|
|
1478
|
+
validate: (v) => !v ? "\u9700\u6C42\u63CF\u8FF0\u4E0D\u80FD\u4E3A\u7A7A" : void 0
|
|
1479
|
+
});
|
|
1480
|
+
if (p.isCancel(input)) {
|
|
1481
|
+
p.cancel("\u5DF2\u53D6\u6D88");
|
|
1482
|
+
process.exit(0);
|
|
1483
|
+
}
|
|
1484
|
+
prompt = input;
|
|
1485
|
+
}
|
|
1486
|
+
const agentIds = opts?.agents?.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1487
|
+
const spin = p.spinner();
|
|
1488
|
+
spin.start("AI \u6B63\u5728\u5206\u6790\u9700\u6C42\u3001\u5339\u914D\u8282\u70B9\u3001\u751F\u6210\u8BDD\u672F...");
|
|
1489
|
+
try {
|
|
1490
|
+
const plan = await planJob({ prompt, agent_ids: agentIds }, void 0, db2);
|
|
1491
|
+
spin.stop("\u89C4\u5212\u5B8C\u6210\uFF01");
|
|
1492
|
+
console.log(chalk.bold(`
|
|
1493
|
+
\u9700\u6C42: ${chalk.white(plan.original_prompt)}
|
|
1494
|
+
`));
|
|
1495
|
+
for (const task of plan.planned_tasks) {
|
|
1496
|
+
console.log(chalk.cyan(` \u250C\u2500 ${chalk.bold(task.assignee_name)} (${chalk.dim(task.assignee_id)})`));
|
|
1497
|
+
console.log(chalk.cyan(" \u2502") + ` \u4EFB\u52A1: ${task.todo_description}`);
|
|
1498
|
+
console.log(chalk.cyan(" \u2502") + ` \u8BDD\u672F: ${chalk.italic(task.briefing)}`);
|
|
1499
|
+
console.log(chalk.cyan(" \u2502") + ` \u622A\u6B62: ${new Date(task.deadline).toLocaleString()}`);
|
|
1500
|
+
console.log(chalk.cyan(" \u2514\u2500"));
|
|
1501
|
+
console.log();
|
|
1502
|
+
}
|
|
1503
|
+
if (opts?.dispatch) {
|
|
1504
|
+
const tasks = plan.planned_tasks.map((t) => ({
|
|
1505
|
+
assignee_id: t.assignee_id,
|
|
1506
|
+
todo_description: t.todo_description,
|
|
1507
|
+
deadline: t.deadline
|
|
1508
|
+
}));
|
|
1509
|
+
const job = dispatchJob({
|
|
1510
|
+
original_prompt: plan.original_prompt,
|
|
1511
|
+
openclaw_callback: "",
|
|
1512
|
+
tasks
|
|
1513
|
+
}, db2);
|
|
1514
|
+
p.outro(`${chalk.green("\u5DF2\u5206\u53D1\uFF01")} Job: ${chalk.bold(job.job_id)}`);
|
|
1515
|
+
} else {
|
|
1516
|
+
const confirm2 = await p.confirm({
|
|
1517
|
+
message: "\u786E\u8BA4\u5206\u53D1\u8FD9\u4E9B\u4EFB\u52A1\uFF1F"
|
|
1518
|
+
});
|
|
1519
|
+
if (p.isCancel(confirm2) || !confirm2) {
|
|
1520
|
+
p.outro(chalk.dim("\u5DF2\u53D6\u6D88\u5206\u53D1"));
|
|
1521
|
+
process.exit(0);
|
|
1522
|
+
}
|
|
1523
|
+
const tasks = plan.planned_tasks.map((t) => ({
|
|
1524
|
+
assignee_id: t.assignee_id,
|
|
1525
|
+
todo_description: t.todo_description,
|
|
1526
|
+
deadline: t.deadline
|
|
1527
|
+
}));
|
|
1528
|
+
const job = dispatchJob({
|
|
1529
|
+
original_prompt: plan.original_prompt,
|
|
1530
|
+
openclaw_callback: "",
|
|
1531
|
+
tasks
|
|
1532
|
+
}, db2);
|
|
1533
|
+
p.outro(`${chalk.green("\u5DF2\u5206\u53D1\uFF01")} Job: ${chalk.bold(job.job_id)}`);
|
|
1534
|
+
}
|
|
1535
|
+
} catch (error) {
|
|
1536
|
+
spin.stop("\u89C4\u5212\u5931\u8D25");
|
|
1537
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1538
|
+
p.outro(chalk.red(message));
|
|
1539
|
+
process.exit(1);
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1062
1542
|
program.parse();
|
|
1063
1543
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/db/connection.ts","../src/db/schema.ts","../src/routes/nodes.ts","../src/models/agent.ts","../src/utils/trace-id.ts","../src/routes/jobs.ts","../src/models/task.ts","../src/models/job.ts","../src/services/dispatch.ts","../src/routes/tasks.ts","../src/services/resume.ts","../src/routes/sync.ts","../src/services/aggregation.ts","../src/dashboard.ts"],"sourcesContent":["import { Command } from 'commander';\nimport * as p from '@clack/prompts';\nimport chalk from 'chalk';\nimport { startServer } from './server.js';\nimport { getDb } from './db/connection.js';\nimport { initSchema } from './db/schema.js';\nimport { createAgent, listAgents } from './models/agent.js';\nimport { listActiveJobs } from './models/job.js';\nimport { markOverdueTasks } from './models/task.js';\nimport { generateId } from './utils/trace-id.js';\nimport type { AgentStatus } from './models/types.js';\n\nconst program = new Command();\n\nprogram\n .name('humanclaw')\n .description(\n 'Async physical node orchestration framework - treating humans as distributed worker nodes'\n )\n .version('1.0.0');\n\n// ─── serve ───────────────────────────────────────────────────────────────────\n\nprogram\n .command('serve')\n .description('Start the HumanClaw server')\n .option('-p, --port <port>', 'Server port', '2026')\n .action(opts => {\n const port = parseInt(opts.port, 10);\n startServer(port);\n });\n\n// ─── agent ───────────────────────────────────────────────────────────────────\n\nconst agentCmd = program\n .command('agent')\n .description('Manage physical nodes (HumanAgent)');\n\nagentCmd\n .command('add')\n .description('Register a new physical node')\n .action(async () => {\n const db = getDb();\n initSchema(db);\n\n p.intro(chalk.bgCyan(' Register New Physical Node '));\n\n const name = await p.text({\n message: 'Node alias (name):',\n placeholder: 'e.g. Frontend Lao Li',\n validate: v => (!v ? 'Name is required' : undefined),\n });\n\n if (p.isCancel(name)) {\n p.cancel('Cancelled.');\n process.exit(0);\n }\n\n const capInput = await p.text({\n message: 'Capabilities (comma-separated):',\n placeholder: 'e.g. UI/UX, Frontend Dev, Stress Resistant',\n validate: v => (!v ? 'At least one capability required' : undefined),\n });\n\n if (p.isCancel(capInput)) {\n p.cancel('Cancelled.');\n process.exit(0);\n }\n\n const capabilities = (capInput as string)\n .split(',')\n .map(s => s.trim())\n .filter(Boolean);\n\n const agent = createAgent({\n agent_id: generateId('emp'),\n name: name as string,\n capabilities,\n status: 'IDLE',\n });\n\n p.outro(\n `${chalk.green('Node registered!')} ID: ${chalk.bold(agent.agent_id)}`\n );\n });\n\nagentCmd\n .command('list')\n .description('Show fleet status')\n .action(() => {\n const db = getDb();\n initSchema(db);\n\n const agents = listAgents();\n if (agents.length === 0) {\n console.log(chalk.dim(' No physical nodes registered.'));\n return;\n }\n\n const statusIcon: Record<AgentStatus, string> = {\n IDLE: '🟢',\n BUSY: '🟡',\n OFFLINE: '🔴',\n OOM: '🟣',\n };\n\n console.log(chalk.bold('\\n Carbon Compute Pool\\n'));\n\n for (const agent of agents) {\n const icon = statusIcon[agent.status];\n const caps = agent.capabilities.join(', ');\n console.log(\n ` ${icon} ${chalk.bold(agent.name)} (${chalk.dim(agent.agent_id)})`\n );\n console.log(` Capabilities: ${chalk.cyan(caps)}`);\n console.log(` Status: ${agent.status}\\n`);\n }\n });\n\n// ─── status ──────────────────────────────────────────────────────────────────\n\nprogram\n .command('status')\n .description('Show active jobs overview')\n .action(() => {\n const db = getDb();\n initSchema(db);\n\n markOverdueTasks();\n const jobs = listActiveJobs();\n\n if (jobs.length === 0) {\n console.log(chalk.dim('\\n No active jobs.\\n'));\n return;\n }\n\n console.log(chalk.bold('\\n Async Orchestration Dashboard\\n'));\n\n for (const job of jobs) {\n const resolved = job.tasks.filter(t => t.status === 'RESOLVED').length;\n const total = job.tasks.length;\n const pct = Math.round((resolved / total) * 100);\n const bar = '█'.repeat(Math.round(pct / 5)) + '░'.repeat(20 - Math.round(pct / 5));\n\n console.log(` ${chalk.bold(job.job_id)} - ${job.original_prompt}`);\n console.log(` Progress: [${bar}] ${resolved}/${total} (${pct}%)`);\n\n for (const task of job.tasks) {\n const statusColor =\n task.status === 'RESOLVED'\n ? chalk.green\n : task.status === 'OVERDUE'\n ? chalk.red\n : chalk.yellow;\n console.log(\n ` ${statusColor(task.status.padEnd(10))} ${task.trace_id} -> ${chalk.dim(task.assignee_id)}`\n );\n console.log(` ${task.todo_description}`);\n }\n console.log();\n }\n });\n\nprogram.parse();\n","import express from 'express';\nimport cors from 'cors';\nimport { getDb } from './db/connection.js';\nimport { initSchema } from './db/schema.js';\nimport nodesRouter from './routes/nodes.js';\nimport jobsRouter from './routes/jobs.js';\nimport tasksRouter from './routes/tasks.js';\nimport syncRouter from './routes/sync.js';\nimport { getDashboardHtml } from './dashboard.js';\n\nexport function createServer(port = 2026) {\n // Initialize database\n const db = getDb();\n initSchema(db);\n\n const app = express();\n\n // Middleware\n app.use(cors());\n app.use(express.json({ limit: '10mb' }));\n\n // API routes\n app.use('/api/v1/nodes', nodesRouter);\n app.use('/api/v1/jobs', jobsRouter);\n app.use('/api/v1/tasks', tasksRouter);\n app.use('/api/v1/jobs', syncRouter);\n\n // Serve dashboard as inline HTML (no build step needed)\n const dashboardHtml = getDashboardHtml();\n app.get('/', (_req, res) => {\n res.type('html').send(dashboardHtml);\n });\n\n // Error handler\n app.use(\n (\n err: Error,\n _req: express.Request,\n res: express.Response,\n _next: express.NextFunction\n ) => {\n console.error('Server error:', err.message);\n res.status(500).json({ error: 'Internal server error' });\n }\n );\n\n return { app, port };\n}\n\nexport function startServer(port = 2026) {\n const { app } = createServer(port);\n\n app.listen(port, () => {\n console.log(`\\n HumanClaw server running at http://localhost:${port}`);\n console.log(` Dashboard: http://localhost:${port}`);\n console.log(` API base: http://localhost:${port}/api/v1\\n`);\n });\n}\n","import Database from 'better-sqlite3';\nimport path from 'node:path';\nimport fs from 'node:fs';\n\nlet db: Database.Database | null = null;\n\nconst DEFAULT_DB_PATH = path.join(\n process.env.HUMANCLAW_DATA_DIR ?? process.cwd(),\n 'humanclaw.db'\n);\n\nexport function getDb(dbPath?: string): Database.Database {\n if (db) return db;\n\n const resolvedPath = dbPath ?? DEFAULT_DB_PATH;\n const dir = path.dirname(resolvedPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n db = new Database(resolvedPath);\n db.pragma('journal_mode = WAL');\n db.pragma('foreign_keys = ON');\n\n return db;\n}\n\nexport function createInMemoryDb(): Database.Database {\n const memDb = new Database(':memory:');\n memDb.pragma('foreign_keys = ON');\n return memDb;\n}\n\nexport function closeDb(): void {\n if (db) {\n db.close();\n db = null;\n }\n}\n\nexport function setDb(newDb: Database.Database): void {\n db = newDb;\n}\n","import type Database from 'better-sqlite3';\n\nexport function initSchema(db: Database.Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS agents (\n agent_id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n capabilities TEXT NOT NULL DEFAULT '[]',\n status TEXT NOT NULL DEFAULT 'IDLE'\n CHECK (status IN ('IDLE', 'BUSY', 'OFFLINE', 'OOM')),\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE TABLE IF NOT EXISTS jobs (\n job_id TEXT PRIMARY KEY,\n original_prompt TEXT NOT NULL,\n openclaw_callback TEXT NOT NULL DEFAULT '',\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE TABLE IF NOT EXISTS tasks (\n trace_id TEXT PRIMARY KEY,\n job_id TEXT NOT NULL REFERENCES jobs(job_id) ON DELETE CASCADE,\n assignee_id TEXT NOT NULL REFERENCES agents(agent_id),\n todo_description TEXT NOT NULL,\n deadline TEXT NOT NULL,\n payload TEXT NOT NULL DEFAULT '{}',\n status TEXT NOT NULL DEFAULT 'PENDING'\n CHECK (status IN ('PENDING', 'DISPATCHED', 'RESOLVED', 'OVERDUE')),\n result_data TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE INDEX IF NOT EXISTS idx_tasks_job_id ON tasks(job_id);\n CREATE INDEX IF NOT EXISTS idx_tasks_assignee_id ON tasks(assignee_id);\n CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);\n `);\n}\n","import { Router } from 'express';\nimport {\n listAgentsWithMetrics,\n createAgent,\n updateAgentStatus,\n deleteAgent,\n} from '../models/agent.js';\nimport { generateId } from '../utils/trace-id.js';\nimport type { AgentStatus } from '../models/types.js';\n\nconst router = Router();\n\n// GET /api/v1/nodes/status - Fleet status with metrics\nrouter.get('/status', (_req, res) => {\n const agents = listAgentsWithMetrics();\n res.json({\n total: agents.length,\n idle: agents.filter(a => a.status === 'IDLE').length,\n busy: agents.filter(a => a.status === 'BUSY').length,\n offline: agents.filter(a => a.status === 'OFFLINE').length,\n oom: agents.filter(a => a.status === 'OOM').length,\n agents,\n });\n});\n\n// POST /api/v1/nodes - Register a new agent\nrouter.post('/', (req, res) => {\n const { name, capabilities, status } = req.body;\n\n if (!name || !Array.isArray(capabilities)) {\n res.status(400).json({ error: 'name and capabilities[] are required' });\n return;\n }\n\n const agent = createAgent({\n agent_id: generateId('emp'),\n name,\n capabilities,\n status: (status as AgentStatus) ?? 'IDLE',\n });\n\n res.status(201).json(agent);\n});\n\n// PATCH /api/v1/nodes/:agent_id/status - Update agent status\nrouter.patch('/:agent_id/status', (req, res) => {\n const { agent_id } = req.params;\n const { status } = req.body;\n\n const validStatuses: AgentStatus[] = ['IDLE', 'BUSY', 'OFFLINE', 'OOM'];\n if (!validStatuses.includes(status)) {\n res.status(400).json({\n error: `Invalid status. Must be one of: ${validStatuses.join(', ')}`,\n });\n return;\n }\n\n const updated = updateAgentStatus(agent_id, status);\n if (!updated) {\n res.status(404).json({ error: `Agent not found: ${agent_id}` });\n return;\n }\n\n res.json({ agent_id, status });\n});\n\n// DELETE /api/v1/nodes/:agent_id\nrouter.delete('/:agent_id', (req, res) => {\n const { agent_id } = req.params;\n const deleted = deleteAgent(agent_id);\n if (!deleted) {\n res.status(404).json({ error: `Agent not found: ${agent_id}` });\n return;\n }\n res.json({ deleted: agent_id });\n});\n\nexport default router;\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport type {\n HumanAgent,\n AgentRow,\n AgentStatus,\n AgentWithMetrics,\n} from './types.js';\n\nfunction rowToAgent(row: AgentRow): HumanAgent {\n return {\n ...row,\n capabilities: JSON.parse(row.capabilities) as string[],\n };\n}\n\nexport function createAgent(\n agent: Omit<HumanAgent, 'created_at'>,\n db?: Database.Database\n): HumanAgent {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n conn\n .prepare(\n `INSERT INTO agents (agent_id, name, capabilities, status, created_at)\n VALUES (?, ?, ?, ?, ?)`\n )\n .run(\n agent.agent_id,\n agent.name,\n JSON.stringify(agent.capabilities),\n agent.status,\n now\n );\n return { ...agent, created_at: now };\n}\n\nexport function getAgent(\n agentId: string,\n db?: Database.Database\n): HumanAgent | undefined {\n const conn = db ?? getDb();\n const row = conn\n .prepare('SELECT * FROM agents WHERE agent_id = ?')\n .get(agentId) as AgentRow | undefined;\n return row ? rowToAgent(row) : undefined;\n}\n\nexport function listAgents(db?: Database.Database): HumanAgent[] {\n const conn = db ?? getDb();\n const rows = conn.prepare('SELECT * FROM agents ORDER BY created_at').all() as AgentRow[];\n return rows.map(rowToAgent);\n}\n\nexport function updateAgentStatus(\n agentId: string,\n status: AgentStatus,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const result = conn\n .prepare('UPDATE agents SET status = ? WHERE agent_id = ?')\n .run(status, agentId);\n return result.changes > 0;\n}\n\nexport function deleteAgent(\n agentId: string,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const result = conn\n .prepare('DELETE FROM agents WHERE agent_id = ?')\n .run(agentId);\n return result.changes > 0;\n}\n\nexport function listAgentsWithMetrics(\n db?: Database.Database\n): AgentWithMetrics[] {\n const conn = db ?? getDb();\n const rows = conn\n .prepare(\n `SELECT\n a.*,\n COUNT(CASE WHEN t.status IN ('DISPATCHED', 'PENDING') THEN 1 END) AS active_task_count,\n AVG(\n CASE WHEN t.status = 'RESOLVED'\n THEN (julianday(t.updated_at) - julianday(t.created_at)) * 24\n END\n ) AS avg_delivery_hours\n FROM agents a\n LEFT JOIN tasks t ON a.agent_id = t.assignee_id\n GROUP BY a.agent_id\n ORDER BY a.created_at`\n )\n .all() as (AgentRow & { active_task_count: number; avg_delivery_hours: number | null })[];\n\n return rows.map(row => ({\n ...rowToAgent(row),\n active_task_count: row.active_task_count,\n avg_delivery_hours: row.avg_delivery_hours\n ? Math.round(row.avg_delivery_hours * 100) / 100\n : null,\n }));\n}\n","import { nanoid } from 'nanoid';\n\nexport function generateTraceId(): string {\n const num = Math.floor(Math.random() * 10000)\n .toString()\n .padStart(4, '0');\n return `TK-${num}`;\n}\n\nexport function generateId(prefix: string): string {\n return `${prefix}_${nanoid(8)}`;\n}\n","import { Router } from 'express';\nimport { dispatchJob } from '../services/dispatch.js';\nimport { listActiveJobs, getJobWithTasks } from '../models/job.js';\nimport { markOverdueTasks } from '../models/task.js';\nimport type { CreateJobRequest } from '../models/types.js';\n\nconst router = Router();\n\n// POST /api/v1/jobs/create - Create and dispatch a new job\nrouter.post('/create', (req, res) => {\n const body = req.body as CreateJobRequest;\n\n if (!body.original_prompt || !Array.isArray(body.tasks) || body.tasks.length === 0) {\n res.status(400).json({\n error: 'original_prompt and non-empty tasks[] are required',\n });\n return;\n }\n\n for (const task of body.tasks) {\n if (!task.assignee_id || !task.todo_description || !task.deadline) {\n res.status(400).json({\n error: 'Each task requires assignee_id, todo_description, and deadline',\n });\n return;\n }\n }\n\n try {\n const job = dispatchJob(body);\n res.status(201).json(job);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n res.status(400).json({ error: message });\n }\n});\n\n// GET /api/v1/jobs/active - List active jobs for kanban\nrouter.get('/active', (_req, res) => {\n // Mark overdue tasks before returning\n markOverdueTasks();\n const jobs = listActiveJobs();\n res.json({ jobs });\n});\n\n// GET /api/v1/jobs/:job_id - Get a single job with tasks\nrouter.get('/:job_id', (req, res) => {\n const { job_id } = req.params;\n const job = getJobWithTasks(job_id);\n if (!job) {\n res.status(404).json({ error: `Job not found: ${job_id}` });\n return;\n }\n res.json(job);\n});\n\nexport default router;\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport type { HumanTask, TaskRow, TaskStatus } from './types.js';\n\nfunction rowToTask(row: TaskRow): HumanTask {\n return {\n ...row,\n payload: JSON.parse(row.payload) as Record<string, unknown>,\n result_data: row.result_data ? JSON.parse(row.result_data) : null,\n };\n}\n\nexport function createTask(\n task: Omit<HumanTask, 'created_at' | 'updated_at' | 'result_data'>,\n db?: Database.Database\n): HumanTask {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n conn\n .prepare(\n `INSERT INTO tasks (trace_id, job_id, assignee_id, todo_description, deadline, payload, status, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`\n )\n .run(\n task.trace_id,\n task.job_id,\n task.assignee_id,\n task.todo_description,\n task.deadline,\n JSON.stringify(task.payload),\n task.status,\n now,\n now\n );\n return { ...task, result_data: null, created_at: now, updated_at: now };\n}\n\nexport function getTask(\n traceId: string,\n db?: Database.Database\n): HumanTask | undefined {\n const conn = db ?? getDb();\n const row = conn\n .prepare('SELECT * FROM tasks WHERE trace_id = ?')\n .get(traceId) as TaskRow | undefined;\n return row ? rowToTask(row) : undefined;\n}\n\nexport function listTasksByJob(\n jobId: string,\n db?: Database.Database\n): HumanTask[] {\n const conn = db ?? getDb();\n const rows = conn\n .prepare('SELECT * FROM tasks WHERE job_id = ? ORDER BY created_at')\n .all(jobId) as TaskRow[];\n return rows.map(rowToTask);\n}\n\nexport function listTasksByAssignee(\n assigneeId: string,\n db?: Database.Database\n): HumanTask[] {\n const conn = db ?? getDb();\n const rows = conn\n .prepare('SELECT * FROM tasks WHERE assignee_id = ? ORDER BY created_at')\n .all(assigneeId) as TaskRow[];\n return rows.map(rowToTask);\n}\n\nexport function updateTaskStatus(\n traceId: string,\n status: TaskStatus,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n const result = conn\n .prepare('UPDATE tasks SET status = ?, updated_at = ? WHERE trace_id = ?')\n .run(status, now, traceId);\n return result.changes > 0;\n}\n\nexport function resolveTask(\n traceId: string,\n resultData: unknown,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n const result = conn\n .prepare(\n `UPDATE tasks\n SET status = 'RESOLVED', result_data = ?, updated_at = ?\n WHERE trace_id = ? AND status IN ('DISPATCHED', 'OVERDUE')`\n )\n .run(JSON.stringify(resultData), now, traceId);\n return result.changes > 0;\n}\n\nexport function markOverdueTasks(db?: Database.Database): number {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n const result = conn\n .prepare(\n `UPDATE tasks\n SET status = 'OVERDUE', updated_at = ?\n WHERE status = 'DISPATCHED' AND deadline < ?`\n )\n .run(now, now);\n return result.changes;\n}\n\nexport function resetTaskDeadline(\n traceId: string,\n newDeadline: string,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n const result = conn\n .prepare(\n `UPDATE tasks\n SET status = 'DISPATCHED', deadline = ?, result_data = NULL, updated_at = ?\n WHERE trace_id = ?`\n )\n .run(newDeadline, now, traceId);\n return result.changes > 0;\n}\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport type { OrchestrationJob, JobRow, JobWithTasks } from './types.js';\nimport { listTasksByJob } from './task.js';\n\nexport function createJob(\n job: OrchestrationJob,\n db?: Database.Database\n): OrchestrationJob {\n const conn = db ?? getDb();\n conn\n .prepare(\n `INSERT INTO jobs (job_id, original_prompt, openclaw_callback, created_at)\n VALUES (?, ?, ?, ?)`\n )\n .run(job.job_id, job.original_prompt, job.openclaw_callback, job.created_at);\n return job;\n}\n\nexport function getJob(\n jobId: string,\n db?: Database.Database\n): OrchestrationJob | undefined {\n const conn = db ?? getDb();\n return conn\n .prepare('SELECT * FROM jobs WHERE job_id = ?')\n .get(jobId) as JobRow | undefined;\n}\n\nexport function getJobWithTasks(\n jobId: string,\n db?: Database.Database\n): JobWithTasks | undefined {\n const conn = db ?? getDb();\n const job = conn\n .prepare('SELECT * FROM jobs WHERE job_id = ?')\n .get(jobId) as JobRow | undefined;\n if (!job) return undefined;\n\n const tasks = listTasksByJob(jobId, conn);\n return { ...job, tasks };\n}\n\nexport function listActiveJobs(db?: Database.Database): JobWithTasks[] {\n const conn = db ?? getDb();\n const jobs = conn\n .prepare(\n `SELECT DISTINCT j.*\n FROM jobs j\n INNER JOIN tasks t ON j.job_id = t.job_id\n WHERE t.status != 'RESOLVED'\n ORDER BY j.created_at DESC`\n )\n .all() as JobRow[];\n\n // Also include jobs where all tasks are resolved but not yet synced\n const allJobs = conn\n .prepare('SELECT * FROM jobs ORDER BY created_at DESC')\n .all() as JobRow[];\n\n const activeJobIds = new Set(jobs.map(j => j.job_id));\n const result: JobWithTasks[] = [];\n\n for (const job of allJobs) {\n const tasks = listTasksByJob(job.job_id, conn);\n if (tasks.length > 0) {\n result.push({ ...job, tasks });\n }\n }\n\n return result;\n}\n\nexport function isJobComplete(\n jobId: string,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const row = conn\n .prepare(\n `SELECT COUNT(*) as total,\n SUM(CASE WHEN status = 'RESOLVED' THEN 1 ELSE 0 END) as resolved\n FROM tasks WHERE job_id = ?`\n )\n .get(jobId) as { total: number; resolved: number };\n return row.total > 0 && row.total === row.resolved;\n}\n\nexport function deleteJob(\n jobId: string,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const result = conn\n .prepare('DELETE FROM jobs WHERE job_id = ?')\n .run(jobId);\n return result.changes > 0;\n}\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport { createJob } from '../models/job.js';\nimport { createTask } from '../models/task.js';\nimport { getAgent, updateAgentStatus } from '../models/agent.js';\nimport { generateTraceId, generateId } from '../utils/trace-id.js';\nimport type { CreateJobRequest, JobWithTasks } from '../models/types.js';\n\nexport function dispatchJob(\n request: CreateJobRequest,\n db?: Database.Database\n): JobWithTasks {\n const conn = db ?? getDb();\n const jobId = generateId('job');\n const now = new Date().toISOString();\n\n // Validate all assignees exist\n for (const taskReq of request.tasks) {\n const agent = getAgent(taskReq.assignee_id, conn);\n if (!agent) {\n throw new Error(`Agent not found: ${taskReq.assignee_id}`);\n }\n if (agent.status === 'OFFLINE') {\n throw new Error(`Agent is offline: ${taskReq.assignee_id} (${agent.name})`);\n }\n }\n\n // Create the job\n const job = createJob(\n {\n job_id: jobId,\n original_prompt: request.original_prompt,\n openclaw_callback: request.openclaw_callback,\n created_at: now,\n },\n conn\n );\n\n // Create and dispatch all tasks\n const tasks = request.tasks.map(taskReq => {\n const traceId = generateTraceId();\n const task = createTask(\n {\n trace_id: traceId,\n job_id: jobId,\n assignee_id: taskReq.assignee_id,\n todo_description: taskReq.todo_description,\n deadline: taskReq.deadline,\n payload: taskReq.payload ?? {},\n status: 'DISPATCHED',\n },\n conn\n );\n\n // Mark the agent as busy\n updateAgentStatus(taskReq.assignee_id, 'BUSY', conn);\n\n return task;\n });\n\n return { ...job, tasks };\n}\n","import { Router } from 'express';\nimport { resumeTask, rejectTask } from '../services/resume.js';\n\nconst router = Router();\n\n// POST /api/v1/tasks/resume - Submit result for a task\nrouter.post('/resume', (req, res) => {\n const { trace_id, result_data } = req.body;\n\n if (!trace_id) {\n res.status(400).json({ error: 'trace_id is required' });\n return;\n }\n\n if (result_data === undefined) {\n res.status(400).json({ error: 'result_data is required' });\n return;\n }\n\n try {\n const result = resumeTask(trace_id, result_data);\n res.json({\n task: result.task,\n job_complete: result.jobComplete,\n job: result.job ?? null,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n res.status(400).json({ error: message });\n }\n});\n\n// POST /api/v1/tasks/reject - Reject and retry a task\nrouter.post('/reject', (req, res) => {\n const { trace_id, new_deadline } = req.body;\n\n if (!trace_id) {\n res.status(400).json({ error: 'trace_id is required' });\n return;\n }\n\n try {\n const task = rejectTask(trace_id, new_deadline);\n res.json({ task });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n res.status(400).json({ error: message });\n }\n});\n\nexport default router;\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport { getTask, resolveTask, resetTaskDeadline } from '../models/task.js';\nimport { isJobComplete, getJobWithTasks } from '../models/job.js';\nimport { updateAgentStatus } from '../models/agent.js';\nimport { listTasksByAssignee } from '../models/task.js';\nimport type { HumanTask, JobWithTasks } from '../models/types.js';\n\nexport interface ResumeResult {\n task: HumanTask;\n jobComplete: boolean;\n job: JobWithTasks | undefined;\n}\n\nexport function resumeTask(\n traceId: string,\n resultData: unknown,\n db?: Database.Database\n): ResumeResult {\n const conn = db ?? getDb();\n\n // Validate trace_id exists\n const task = getTask(traceId, conn);\n if (!task) {\n throw new Error(`Task not found: ${traceId}`);\n }\n\n if (task.status === 'RESOLVED') {\n throw new Error(`Task already resolved: ${traceId}`);\n }\n\n if (task.status === 'PENDING') {\n throw new Error(`Task not yet dispatched: ${traceId}`);\n }\n\n // Resolve the task\n const updated = resolveTask(traceId, resultData, conn);\n if (!updated) {\n throw new Error(`Failed to resolve task: ${traceId}`);\n }\n\n // Check if the agent has other active tasks; if not, mark IDLE\n const activeTasks = listTasksByAssignee(task.assignee_id, conn).filter(\n t => t.status === 'DISPATCHED' || t.status === 'PENDING'\n );\n if (activeTasks.length === 0) {\n updateAgentStatus(task.assignee_id, 'IDLE', conn);\n }\n\n // Check if the whole job is now complete\n const jobComplete = isJobComplete(task.job_id, conn);\n const job = jobComplete ? getJobWithTasks(task.job_id, conn) : undefined;\n\n const resolvedTask = getTask(traceId, conn)!;\n\n return { task: resolvedTask, jobComplete, job };\n}\n\nexport function rejectTask(\n traceId: string,\n newDeadline?: string,\n db?: Database.Database\n): HumanTask {\n const conn = db ?? getDb();\n\n const task = getTask(traceId, conn);\n if (!task) {\n throw new Error(`Task not found: ${traceId}`);\n }\n\n // Default: extend deadline by 24h from now\n const deadline =\n newDeadline ?? new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();\n\n resetTaskDeadline(traceId, deadline, conn);\n\n return getTask(traceId, conn)!;\n}\n","import { Router } from 'express';\nimport { aggregateJob, syncToOpenClaw } from '../services/aggregation.js';\n\nconst router = Router();\n\n// POST /api/v1/jobs/:job_id/sync - Aggregate and sync to OpenClaw\nrouter.post('/:job_id/sync', async (req, res) => {\n const { job_id } = req.params;\n\n try {\n const aggregation = aggregateJob(job_id);\n const syncResult = await syncToOpenClaw(aggregation);\n\n res.json({\n aggregation,\n sync: syncResult,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n res.status(400).json({ error: message });\n }\n});\n\nexport default router;\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport { getJobWithTasks, isJobComplete } from '../models/job.js';\nimport type { JobWithTasks } from '../models/types.js';\n\nexport interface AggregationResult {\n job_id: string;\n original_prompt: string;\n openclaw_callback: string;\n results: Array<{\n trace_id: string;\n assignee_id: string;\n todo_description: string;\n result_data: unknown;\n }>;\n aggregated_at: string;\n}\n\nexport function aggregateJob(\n jobId: string,\n db?: Database.Database\n): AggregationResult {\n const conn = db ?? getDb();\n\n const job = getJobWithTasks(jobId, conn);\n if (!job) {\n throw new Error(`Job not found: ${jobId}`);\n }\n\n if (!isJobComplete(jobId, conn)) {\n const resolved = job.tasks.filter(t => t.status === 'RESOLVED').length;\n throw new Error(\n `Job not complete: ${resolved}/${job.tasks.length} tasks resolved`\n );\n }\n\n return {\n job_id: job.job_id,\n original_prompt: job.original_prompt,\n openclaw_callback: job.openclaw_callback,\n results: job.tasks.map(t => ({\n trace_id: t.trace_id,\n assignee_id: t.assignee_id,\n todo_description: t.todo_description,\n result_data: t.result_data,\n })),\n aggregated_at: new Date().toISOString(),\n };\n}\n\nexport async function syncToOpenClaw(\n aggregation: AggregationResult\n): Promise<{ success: boolean; message: string }> {\n if (!aggregation.openclaw_callback) {\n return {\n success: true,\n message: 'No OpenClaw callback configured, skipping sync',\n };\n }\n\n try {\n const response = await fetch(aggregation.openclaw_callback, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(aggregation),\n });\n\n if (!response.ok) {\n return {\n success: false,\n message: `OpenClaw sync failed: ${response.status} ${response.statusText}`,\n };\n }\n\n return { success: true, message: 'Synced to OpenClaw successfully' };\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Unknown error';\n return { success: false, message: `OpenClaw sync error: ${message}` };\n }\n}\n","export function getDashboardHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"UTF-8\"/>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"/>\n<title>HumanClaw - 异步物理节点编排</title>\n<style>\n:root{--bg:#0f1117;--surface:#1a1d27;--surface-hover:#242836;--border:#2e3346;--text:#e2e4ed;--text-dim:#7b8196;--accent:#00d4ff;--accent-dim:rgba(0,212,255,.12);--green:#22c55e;--yellow:#eab308;--red:#ef4444;--purple:#a855f7;--font-mono:'SF Mono','JetBrains Mono','Fira Code',monospace;--font-sans:-apple-system,'Inter','Segoe UI',sans-serif;--radius:10px}\n*{margin:0;padding:0;box-sizing:border-box}\nbody{background:var(--bg);color:var(--text);font-family:var(--font-sans);min-height:100vh;line-height:1.5}\nheader{padding:20px 32px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:14px}\nheader h1{font-family:var(--font-mono);font-size:22px;color:var(--accent);letter-spacing:1.5px}\n.subtitle{color:var(--text-dim);font-size:12px;border-left:1px solid var(--border);padding-left:14px}\nnav{display:flex;border-bottom:1px solid var(--border);padding:0 32px;gap:4px}\n.tab{background:none;border:none;color:var(--text-dim);padding:12px 18px;font-size:13px;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s;font-family:var(--font-sans);font-weight:500}\n.tab:hover{color:var(--text);background:var(--surface)}\n.tab.active{color:var(--accent);border-bottom-color:var(--accent)}\nmain{padding:24px 32px;max-width:1200px}\n.panel{display:none}.panel.active{display:block}\n/* ─── Cards ────────────────────────────────── */\n.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;transition:border-color .15s}\n.card:hover{border-color:color-mix(in srgb,var(--accent) 40%,transparent)}\n.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;margin-top:16px}\n/* ─── Agent Card ───────────────────────────── */\n.agent-header{display:flex;align-items:center;gap:10px;margin-bottom:10px}\n.dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}\n.dot.IDLE{background:var(--green);box-shadow:0 0 8px var(--green)}.dot.BUSY{background:var(--yellow);box-shadow:0 0 8px var(--yellow)}.dot.OFFLINE{background:var(--red);box-shadow:0 0 6px var(--red)}.dot.OOM{background:var(--purple);box-shadow:0 0 8px var(--purple)}\n.agent-name{font-weight:600;font-size:15px}\n.agent-id{color:var(--text-dim);font-family:var(--font-mono);font-size:11px;margin-bottom:8px}\n.caps{display:flex;flex-wrap:wrap;gap:5px;margin-top:6px}\n.cap{background:var(--accent-dim);border:1px solid color-mix(in srgb,var(--accent) 25%,transparent);border-radius:4px;padding:1px 8px;font-size:11px;color:var(--accent)}\n.agent-meta{margin-top:10px;font-size:11px;color:var(--text-dim);display:flex;gap:12px}\n.agent-actions{margin-top:12px;display:flex;gap:6px}\n.agent-actions select{background:var(--surface-hover);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:11px;cursor:pointer;outline:none}\n.agent-actions button{background:var(--red);color:#fff;border:none;border-radius:6px;padding:4px 10px;font-size:11px;cursor:pointer}\n.agent-actions button:hover{opacity:.8}\n/* ─── Stats Bar ────────────────────────────── */\n.stats{display:flex;gap:12px;flex-wrap:wrap}\n.stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 22px;text-align:center;min-width:90px}\n.stat-val{font-size:28px;font-weight:700;font-family:var(--font-mono)}\n.stat-lbl{font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:1px;margin-top:2px}\n/* ─── Forms ────────────────────────────────── */\n.form-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px;margin-top:16px;max-width:560px}\n.form-card h3{font-size:15px;color:var(--accent);font-family:var(--font-mono);margin-bottom:16px}\n.fg{margin-bottom:14px}\n.fg label{display:block;font-size:12px;color:var(--text-dim);margin-bottom:5px;font-weight:500}\n.fg input,.fg textarea,.fg select{width:100%;background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:10px 12px;color:var(--text);font-size:13px;font-family:var(--font-sans);outline:none;transition:border-color .15s}\n.fg input:focus,.fg textarea:focus,.fg select:focus{border-color:var(--accent)}\n.fg textarea{min-height:80px;resize:vertical;font-family:var(--font-mono);font-size:12px}\n.fg .hint{font-size:11px;color:var(--text-dim);margin-top:4px}\n.btn{padding:10px 22px;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;transition:all .15s;display:inline-flex;align-items:center;gap:6px}\n.btn:hover{opacity:.85;transform:translateY(-1px)}\n.btn:active{transform:translateY(0)}\n.btn-primary{background:var(--accent);color:var(--bg)}\n.btn-green{background:var(--green);color:#fff}\n.btn-danger{background:var(--red);color:#fff}\n.btn-ghost{background:transparent;color:var(--accent);border:1px solid var(--border)}\n.btn-ghost:hover{background:var(--accent-dim)}\n.btn-sm{padding:6px 14px;font-size:12px}\n.btn-group{display:flex;gap:10px;margin-top:16px}\n/* ─── Section header ───────────────────────── */\n.section-hd{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}\n.section-hd h2{font-size:16px;font-weight:600}\n/* ─── Job card ─────────────────────────────── */\n.job-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-top:14px}\n.job-hd{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}\n.job-title{font-weight:600;font-size:14px}\n.job-id{font-family:var(--font-mono);font-size:11px;color:var(--text-dim)}\n.pbar-wrap{margin:6px 0 14px}\n.pbar-label{font-size:11px;color:var(--text-dim);margin-bottom:4px}\n.pbar{background:var(--surface-hover);border-radius:4px;height:6px;overflow:hidden}\n.pbar-fill{background:var(--accent);height:100%;border-radius:4px;transition:width .3s}\n.kanban{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px}\n.lane{min-height:40px}\n.lane-hd{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px;padding-bottom:4px;border-bottom:2px solid var(--border)}\n.lane-hd.y{color:var(--yellow);border-color:var(--yellow)}.lane-hd.r{color:var(--red);border-color:var(--red)}.lane-hd.g{color:var(--green);border-color:var(--green)}\n.tcard{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:6px;font-size:12px}\n.tcard-trace{font-family:var(--font-mono);font-size:10px;color:var(--accent);margin-bottom:3px}\n.tcard-meta{font-size:10px;color:var(--text-dim);margin-top:3px}\n/* ─── Task Row in Job Form ─────────────────── */\n.task-row{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:14px;margin-bottom:10px;position:relative}\n.task-row .remove-task{position:absolute;top:8px;right:10px;background:none;border:none;color:var(--red);cursor:pointer;font-size:16px;line-height:1}\n.task-row .fg{margin-bottom:10px}\n.task-row .fg:last-child{margin-bottom:0}\n.task-row-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}\n/* ─── Toast ────────────────────────────────── */\n.toast{position:fixed;bottom:24px;right:24px;padding:12px 20px;border-radius:8px;font-size:13px;z-index:9999;animation:slide-up .25s ease;font-weight:500;box-shadow:0 8px 24px rgba(0,0,0,.4)}\n.toast.ok{background:var(--green);color:#fff}.toast.err{background:var(--red);color:#fff}\n@keyframes slide-up{from{transform:translateY(16px);opacity:0}to{transform:translateY(0);opacity:1}}\n/* ─── Empty state ──────────────────────────── */\n.empty-state{text-align:center;padding:48px 20px;color:var(--text-dim)}\n.empty-state .icon{font-size:48px;margin-bottom:12px;opacity:.6}\n.empty-state p{font-size:14px;margin-bottom:16px;max-width:360px;margin-left:auto;margin-right:auto;line-height:1.6}\n/* ─── Modal/Overlay ────────────────────────── */\n.overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:100;display:flex;align-items:flex-start;justify-content:center;padding-top:80px;animation:fade-in .15s ease}\n.overlay .form-card{max-width:520px;width:100%;max-height:80vh;overflow-y:auto;margin:0}\n@keyframes fade-in{from{opacity:0}to{opacity:1}}\n</style>\n</head>\n<body>\n<header>\n <h1>HumanClaw</h1>\n <span class=\"subtitle\">异步物理节点编排框架</span>\n</header>\n<nav>\n <button class=\"tab active\" data-panel=\"fleet\">碳基算力池</button>\n <button class=\"tab\" data-panel=\"pipeline\">编排大盘</button>\n <button class=\"tab\" data-panel=\"terminal\">I/O 终端</button>\n</nav>\n<main>\n <section id=\"fleet\" class=\"panel active\"></section>\n <section id=\"pipeline\" class=\"panel\"></section>\n <section id=\"terminal\" class=\"panel\"></section>\n</main>\n\n<script>\nconst API='/api/v1';\nfunction esc(s){const d=document.createElement('div');d.textContent=s;return d.innerHTML}\nfunction toast(msg,ok){\n document.querySelectorAll('.toast').forEach(t=>t.remove());\n const t=document.createElement('div');t.className='toast '+(ok?'ok':'err');t.textContent=msg;document.body.appendChild(t);\n setTimeout(()=>t.remove(),3500);\n}\nlet cachedAgents=[];\n\n// ═══════════════════════════════════════════════\n// FLEET\n// ═══════════════════════════════════════════════\nasync function loadFleet(el){\n el.innerHTML='<div class=\"empty-state\"><p>加载中...</p></div>';\n try{\n const r=await fetch(API+'/nodes/status');\n const d=await r.json();\n cachedAgents=d.agents||[];\n let h='<div class=\"section-hd\"><h2>碳基算力池</h2><button class=\"btn btn-primary btn-sm\" onclick=\"showAddAgent()\">+ 添加节点</button></div>';\n h+='<div class=\"stats\">';\n h+='<div class=\"stat\"><div class=\"stat-val\">'+d.total+'</div><div class=\"stat-lbl\">总计</div></div>';\n h+='<div class=\"stat\"><div class=\"stat-val\" style=\"color:var(--green)\">'+d.idle+'</div><div class=\"stat-lbl\">空闲</div></div>';\n h+='<div class=\"stat\"><div class=\"stat-val\" style=\"color:var(--yellow)\">'+d.busy+'</div><div class=\"stat-lbl\">忙碌</div></div>';\n h+='<div class=\"stat\"><div class=\"stat-val\" style=\"color:var(--red)\">'+d.offline+'</div><div class=\"stat-lbl\">离线</div></div>';\n h+='<div class=\"stat\"><div class=\"stat-val\" style=\"color:var(--purple)\">'+d.oom+'</div><div class=\"stat-lbl\">崩溃</div></div>';\n h+='</div>';\n if(!d.agents.length){\n h+='<div class=\"empty-state\" style=\"margin-top:32px\"><div class=\"icon\">👤</div><p>还没有物理节点。点击上方「+ 添加节点」注册你的第一个碳基算力单元。</p></div>';\n el.innerHTML=h;return;\n }\n h+='<div class=\"grid\">';\n for(const a of d.agents){\n h+='<div class=\"card\"><div class=\"agent-header\"><span class=\"dot '+a.status+'\"></span><span class=\"agent-name\">'+esc(a.name)+'</span></div>';\n h+='<div class=\"agent-id\">'+a.agent_id+'</div>';\n h+='<div class=\"caps\">';for(const c of a.capabilities)h+='<span class=\"cap\">'+esc(c)+'</span>';h+='</div>';\n h+='<div class=\"agent-meta\"><span>任务: '+a.active_task_count+'</span>';\n if(a.avg_delivery_hours!==null)h+='<span>平均交付: '+a.avg_delivery_hours+'h</span>';\n h+='</div>';\n h+='<div class=\"agent-actions\">';\n h+='<select onchange=\"changeStatus(\\\\''+a.agent_id+'\\\\',this.value)\">';\n for(const s of ['IDLE','BUSY','OFFLINE','OOM'])h+='<option'+(s===a.status?' selected':'')+'>'+s+'</option>';\n h+='</select>';\n h+='<button onclick=\"deleteAgent(\\\\''+a.agent_id+'\\\\')\">删除</button>';\n h+='</div></div>';\n }\n h+='</div>';\n el.innerHTML=h;\n }catch(e){el.innerHTML='<div class=\"empty-state\"><p>加载失败: '+e.message+'</p></div>'}\n}\nwindow.showAddAgent=function(){\n const ov=document.createElement('div');ov.className='overlay';ov.id='overlay';\n ov.addEventListener('click',e=>{if(e.target===ov)ov.remove()});\n ov.innerHTML='<div class=\"form-card\"><h3>+ 添加物理节点</h3>'\n +'<div class=\"fg\"><label>节点名称</label><input id=\"aa-name\" placeholder=\"例: 前端老李\"/></div>'\n +'<div class=\"fg\"><label>技能标签</label><input id=\"aa-caps\" placeholder=\"例: UI/UX, 前端开发, 抗压能力强\"/><div class=\"hint\">多个标签用逗号分隔</div></div>'\n +'<div class=\"btn-group\"><button class=\"btn btn-primary\" onclick=\"submitAgent()\">注册节点</button><button class=\"btn btn-ghost\" onclick=\"document.getElementById(\\\\'overlay\\\\').remove()\">取消</button></div></div>';\n document.body.appendChild(ov);\n document.getElementById('aa-name').focus();\n};\nwindow.submitAgent=async function(){\n const name=document.getElementById('aa-name').value.trim();\n const caps=document.getElementById('aa-caps').value.trim();\n if(!name){toast('请输入节点名称',false);return}\n if(!caps){toast('请输入至少一个技能标签',false);return}\n try{\n const r=await fetch(API+'/nodes',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name,capabilities:caps.split(',').map(s=>s.trim()).filter(Boolean)})});\n const d=await r.json();\n if(!r.ok){toast(d.error||'注册失败',false);return}\n toast('节点 '+d.agent_id+' 注册成功!',true);\n document.getElementById('overlay').remove();\n load('fleet');\n }catch{toast('网络错误',false)}\n};\nwindow.changeStatus=async function(id,status){\n try{\n const r=await fetch(API+'/nodes/'+id+'/status',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify({status})});\n if(!r.ok){const d=await r.json();toast(d.error||'更新失败',false);return}\n toast('状态已更新',true);\n }catch{toast('网络错误',false)}\n};\nwindow.deleteAgent=async function(id){\n if(!confirm('确定删除节点 '+id+'?'))return;\n try{\n const r=await fetch(API+'/nodes/'+id,{method:'DELETE'});\n if(!r.ok){const d=await r.json();toast(d.error||'删除失败',false);return}\n toast('节点已删除',true);load('fleet');\n }catch{toast('网络错误',false)}\n};\n\n// ═══════════════════════════════════════════════\n// PIPELINE\n// ═══════════════════════════════════════════════\nfunction tcard(t){\n return '<div class=\"tcard\"><div class=\"tcard-trace\">'+t.trace_id+'</div><div>'+esc(t.todo_description)+'</div><div class=\"tcard-meta\">'+t.assignee_id+' | '+new Date(t.deadline).toLocaleString()+'</div></div>';\n}\nasync function loadPipeline(el){\n el.innerHTML='<div class=\"empty-state\"><p>加载中...</p></div>';\n try{\n // refresh agents cache\n try{const ar=await fetch(API+'/nodes/status');const ad=await ar.json();cachedAgents=ad.agents||[]}catch{}\n const r=await fetch(API+'/jobs/active');\n const d=await r.json();\n let h='<div class=\"section-hd\"><h2>异步编排大盘</h2><button class=\"btn btn-primary btn-sm\" onclick=\"showCreateJob()\">+ 创建任务</button></div>';\n if(!d.jobs.length){\n h+='<div class=\"empty-state\" style=\"margin-top:32px\"><div class=\"icon\">📋</div><p>暂无进行中的任务。点击上方「+ 创建任务」开始你的第一次物理编排。';\n if(!cachedAgents.length)h+='<br/><br/>⚠️ 需要先在「碳基算力池」中添加物理节点。';\n h+='</p></div>';\n el.innerHTML=h;return;\n }\n for(const j of d.jobs){\n const res=j.tasks.filter(t=>t.status==='RESOLVED').length;\n const tot=j.tasks.length;\n const pct=tot?Math.round(res/tot*100):0;\n const dispatched=j.tasks.filter(t=>t.status==='DISPATCHED'||t.status==='PENDING');\n const overdue=j.tasks.filter(t=>t.status==='OVERDUE');\n const done=j.tasks.filter(t=>t.status==='RESOLVED');\n h+='<div class=\"job-card\"><div class=\"job-hd\"><span class=\"job-title\">'+esc(j.original_prompt)+'</span><span class=\"job-id\">'+j.job_id+'</span></div>';\n h+='<div class=\"pbar-wrap\"><div class=\"pbar-label\">'+res+'/'+tot+' 已完成 ('+pct+'%)</div><div class=\"pbar\"><div class=\"pbar-fill\" style=\"width:'+pct+'%\"></div></div></div>';\n if(pct===100)h+='<div style=\"margin-bottom:12px\"><button class=\"btn btn-green btn-sm\" onclick=\"syncJob(\\\\''+j.job_id+'\\\\')\">聚合并同步到 OpenClaw</button></div>';\n h+='<div class=\"kanban\"><div class=\"lane\"><div class=\"lane-hd y\">已分发 ('+dispatched.length+')</div>'+dispatched.map(tcard).join('')+'</div>';\n h+='<div class=\"lane\"><div class=\"lane-hd r\">已超时 ('+overdue.length+')</div>'+overdue.map(tcard).join('')+'</div>';\n h+='<div class=\"lane\"><div class=\"lane-hd g\">已交付 ('+done.length+')</div>'+done.map(tcard).join('')+'</div></div></div>';\n }\n el.innerHTML=h;\n }catch(e){el.innerHTML='<div class=\"empty-state\"><p>加载失败: '+e.message+'</p></div>'}\n}\nwindow.showCreateJob=function(){\n if(!cachedAgents.length){toast('请先在「碳基算力池」中添加至少一个物理节点',false);return}\n const ov=document.createElement('div');ov.className='overlay';ov.id='overlay';\n ov.addEventListener('click',e=>{if(e.target===ov)ov.remove()});\n const optionsHtml=cachedAgents.map(a=>'<option value=\"'+a.agent_id+'\">'+esc(a.name)+' ('+a.agent_id+')</option>').join('');\n const tomorrow=new Date(Date.now()+86400000).toISOString().slice(0,16);\n ov.innerHTML='<div class=\"form-card\"><h3>+ 创建编排任务</h3>'\n +'<div class=\"fg\"><label>任务描述 (原始需求)</label><input id=\"cj-prompt\" placeholder=\"例: 完成首页改版\"/></div>'\n +'<div class=\"fg\"><label>OpenClaw 回调地址 (可选)</label><input id=\"cj-callback\" placeholder=\"https://...\"/></div>'\n +'<div style=\"margin-bottom:10px;display:flex;justify-content:space-between;align-items:center\"><label style=\"font-size:13px;font-weight:600;color:var(--text)\">子任务列表</label><button class=\"btn btn-ghost btn-sm\" onclick=\"addTaskRow()\">+ 添加子任务</button></div>'\n +'<div id=\"task-rows\"><div class=\"task-row\" data-idx=\"0\"><div class=\"fg\"><label>指派节点</label><select class=\"tr-agent\">'+optionsHtml+'</select></div><div class=\"fg\"><label>任务描述</label><input class=\"tr-desc\" placeholder=\"具体要做什么...\"/></div><div class=\"fg\"><label>截止时间</label><input class=\"tr-deadline\" type=\"datetime-local\" value=\"'+tomorrow+'\"/></div></div></div>'\n +'<div class=\"btn-group\"><button class=\"btn btn-primary\" onclick=\"submitJob()\">创建并分发</button><button class=\"btn btn-ghost\" onclick=\"document.getElementById(\\\\'overlay\\\\').remove()\">取消</button></div></div>';\n document.body.appendChild(ov);\n};\nwindow.addTaskRow=function(){\n const optionsHtml=cachedAgents.map(a=>'<option value=\"'+a.agent_id+'\">'+esc(a.name)+' ('+a.agent_id+')</option>').join('');\n const tomorrow=new Date(Date.now()+86400000).toISOString().slice(0,16);\n const row=document.createElement('div');row.className='task-row';\n row.innerHTML='<button class=\"remove-task\" onclick=\"this.parentElement.remove()\">×</button>'\n +'<div class=\"fg\"><label>指派节点</label><select class=\"tr-agent\">'+optionsHtml+'</select></div>'\n +'<div class=\"fg\"><label>任务描述</label><input class=\"tr-desc\" placeholder=\"具体要做什么...\"/></div>'\n +'<div class=\"fg\"><label>截止时间</label><input class=\"tr-deadline\" type=\"datetime-local\" value=\"'+tomorrow+'\"/></div>';\n document.getElementById('task-rows').appendChild(row);\n};\nwindow.submitJob=async function(){\n const prompt=document.getElementById('cj-prompt').value.trim();\n const callback=document.getElementById('cj-callback').value.trim();\n if(!prompt){toast('请输入任务描述',false);return}\n const rows=document.querySelectorAll('.task-row');\n const tasks=[];\n for(const row of rows){\n const aid=row.querySelector('.tr-agent').value;\n const desc=row.querySelector('.tr-desc').value.trim();\n const dl=row.querySelector('.tr-deadline').value;\n if(!desc){toast('每个子任务都需要填写描述',false);return}\n tasks.push({assignee_id:aid,todo_description:desc,deadline:new Date(dl).toISOString()});\n }\n if(!tasks.length){toast('至少添加一个子任务',false);return}\n try{\n const r=await fetch(API+'/jobs/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({original_prompt:prompt,openclaw_callback:callback,tasks})});\n const d=await r.json();\n if(!r.ok){toast(d.error||'创建失败',false);return}\n toast('任务已创建并分发!Job: '+d.job_id,true);\n document.getElementById('overlay').remove();\n load('pipeline');\n }catch{toast('网络错误',false)}\n};\nwindow.syncJob=async function(jobId){\n try{\n const r=await fetch(API+'/jobs/'+jobId+'/sync',{method:'POST'});\n const d=await r.json();\n if(!r.ok){toast(d.error||'同步失败',false);return}\n toast('聚合完成!'+d.sync.message,true);\n }catch{toast('网络错误',false)}\n};\n\n// ═══════════════════════════════════════════════\n// TERMINAL\n// ═══════════════════════════════════════════════\nfunction loadTerminal(el){\n el.innerHTML='<div class=\"section-hd\"><h2>I/O 交付终端</h2></div>'\n +'<div class=\"form-card\" style=\"margin-top:12px\"><h3>> 提交物理节点产出</h3>'\n +'<div class=\"fg\"><label>Trace ID (追踪码)</label><input id=\"t-tid\" placeholder=\"例: TK-9527\"/></div>'\n +'<div class=\"fg\"><label>交付载荷</label><textarea id=\"t-payload\" placeholder=\"粘贴交付物内容、工作汇报或代码...\"></textarea></div>'\n +'<div class=\"btn-group\"><button class=\"btn btn-primary\" onclick=\"doResume()\">提交并恢复 (Resume)</button><button class=\"btn btn-danger\" onclick=\"doReject()\">打回重做 (Reject)</button></div></div>';\n}\nwindow.doResume=async function(){\n const tid=document.getElementById('t-tid').value.trim();\n const payload=document.getElementById('t-payload').value.trim();\n if(!tid){toast('请输入 Trace ID',false);return}\n if(!payload){toast('请输入交付载荷',false);return}\n try{\n const r=await fetch(API+'/tasks/resume',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({trace_id:tid,result_data:{text:payload}})});\n const d=await r.json();\n if(!r.ok){toast(d.error||'提交失败',false);return}\n toast(d.job_complete?'任务已交付!Job 已全部完成,可以聚合同步。':'任务 '+tid+' 已交付。',true);\n document.getElementById('t-tid').value='';document.getElementById('t-payload').value='';\n }catch{toast('网络错误',false)}\n};\nwindow.doReject=async function(){\n const tid=document.getElementById('t-tid').value.trim();\n if(!tid){toast('请输入 Trace ID',false);return}\n try{\n const r=await fetch(API+'/tasks/reject',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({trace_id:tid})});\n const d=await r.json();\n if(!r.ok){toast(d.error||'操作失败',false);return}\n toast('任务 '+tid+' 已打回,截止时间已延长 24 小时。',true);\n document.getElementById('t-tid').value='';document.getElementById('t-payload').value='';\n }catch{toast('网络错误',false)}\n};\n\n// ═══════════════════════════════════════════════\n// TABS & INIT\n// ═══════════════════════════════════════════════\nconst tabs=document.querySelectorAll('.tab');\nconst panels=document.querySelectorAll('.panel');\nfunction load(id){\n const el=document.getElementById(id);\n if(id==='fleet')loadFleet(el);\n else if(id==='pipeline')loadPipeline(el);\n else if(id==='terminal')loadTerminal(el);\n}\ntabs.forEach(t=>t.addEventListener('click',()=>{\n tabs.forEach(x=>x.classList.remove('active'));\n panels.forEach(x=>x.classList.remove('active'));\n t.classList.add('active');\n const id=t.dataset.panel;\n document.getElementById(id).classList.add('active');\n load(id);\n}));\nload('fleet');\nsetInterval(()=>{\n const a=document.querySelector('.tab.active');\n if(a&&a.dataset.panel!=='terminal')load(a.dataset.panel);\n},15000);\n</script>\n</body>\n</html>`;\n}\n"],"mappings":"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","names":["db","db","Router","db","db","db","db","db","router","Router","Router","db","router","Router","Router","db","router","Router","db","db"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/db/connection.ts","../src/db/schema.ts","../src/routes/nodes.ts","../src/models/agent.ts","../src/utils/trace-id.ts","../src/routes/jobs.ts","../src/models/task.ts","../src/models/job.ts","../src/services/dispatch.ts","../src/llm/claude.ts","../src/llm/openai.ts","../src/llm/index.ts","../src/services/planner.ts","../src/routes/tasks.ts","../src/services/resume.ts","../src/routes/sync.ts","../src/services/aggregation.ts","../src/dashboard.ts"],"sourcesContent":["import { Command } from 'commander';\nimport * as p from '@clack/prompts';\nimport chalk from 'chalk';\nimport { startServer } from './server.js';\nimport { getDb } from './db/connection.js';\nimport { initSchema } from './db/schema.js';\nimport { createAgent, listAgents } from './models/agent.js';\nimport { listActiveJobs } from './models/job.js';\nimport { markOverdueTasks } from './models/task.js';\nimport { dispatchJob } from './services/dispatch.js';\nimport { planJob } from './services/planner.js';\nimport { generateId } from './utils/trace-id.js';\nimport type { AgentStatus } from './models/types.js';\n\nconst program = new Command();\n\nprogram\n .name('humanclaw')\n .description(\n 'Async physical node orchestration framework - treating humans as distributed worker nodes'\n )\n .version('1.0.0');\n\n// ─── serve ───────────────────────────────────────────────────────────────────\n\nprogram\n .command('serve')\n .description('Start the HumanClaw server')\n .option('-p, --port <port>', 'Server port', '2026')\n .action(opts => {\n const port = parseInt(opts.port, 10);\n startServer(port);\n });\n\n// ─── agent ───────────────────────────────────────────────────────────────────\n\nconst agentCmd = program\n .command('agent')\n .description('Manage physical nodes (HumanAgent)');\n\nagentCmd\n .command('add')\n .description('Register a new physical node')\n .action(async () => {\n const db = getDb();\n initSchema(db);\n\n p.intro(chalk.bgCyan(' Register New Physical Node '));\n\n const name = await p.text({\n message: 'Node alias (name):',\n placeholder: 'e.g. Frontend Lao Li',\n validate: v => (!v ? 'Name is required' : undefined),\n });\n\n if (p.isCancel(name)) {\n p.cancel('Cancelled.');\n process.exit(0);\n }\n\n const capInput = await p.text({\n message: 'Capabilities (comma-separated):',\n placeholder: 'e.g. UI/UX, Frontend Dev, Stress Resistant',\n validate: v => (!v ? 'At least one capability required' : undefined),\n });\n\n if (p.isCancel(capInput)) {\n p.cancel('Cancelled.');\n process.exit(0);\n }\n\n const capabilities = (capInput as string)\n .split(',')\n .map(s => s.trim())\n .filter(Boolean);\n\n const agent = createAgent({\n agent_id: generateId('emp'),\n name: name as string,\n capabilities,\n status: 'IDLE',\n });\n\n p.outro(\n `${chalk.green('Node registered!')} ID: ${chalk.bold(agent.agent_id)}`\n );\n });\n\nagentCmd\n .command('list')\n .description('Show fleet status')\n .action(() => {\n const db = getDb();\n initSchema(db);\n\n const agents = listAgents();\n if (agents.length === 0) {\n console.log(chalk.dim(' No physical nodes registered.'));\n return;\n }\n\n const statusIcon: Record<AgentStatus, string> = {\n IDLE: '🟢',\n BUSY: '🟡',\n OFFLINE: '🔴',\n OOM: '🟣',\n };\n\n console.log(chalk.bold('\\n Carbon Compute Pool\\n'));\n\n for (const agent of agents) {\n const icon = statusIcon[agent.status];\n const caps = agent.capabilities.join(', ');\n console.log(\n ` ${icon} ${chalk.bold(agent.name)} (${chalk.dim(agent.agent_id)})`\n );\n console.log(` Capabilities: ${chalk.cyan(caps)}`);\n console.log(` Status: ${agent.status}\\n`);\n }\n });\n\n// ─── status ──────────────────────────────────────────────────────────────────\n\nprogram\n .command('status')\n .description('Show active jobs overview')\n .action(() => {\n const db = getDb();\n initSchema(db);\n\n markOverdueTasks();\n const jobs = listActiveJobs();\n\n if (jobs.length === 0) {\n console.log(chalk.dim('\\n No active jobs.\\n'));\n return;\n }\n\n console.log(chalk.bold('\\n Async Orchestration Dashboard\\n'));\n\n for (const job of jobs) {\n const resolved = job.tasks.filter(t => t.status === 'RESOLVED').length;\n const total = job.tasks.length;\n const pct = Math.round((resolved / total) * 100);\n const bar = '█'.repeat(Math.round(pct / 5)) + '░'.repeat(20 - Math.round(pct / 5));\n\n console.log(` ${chalk.bold(job.job_id)} - ${job.original_prompt}`);\n console.log(` Progress: [${bar}] ${resolved}/${total} (${pct}%)`);\n\n for (const task of job.tasks) {\n const statusColor =\n task.status === 'RESOLVED'\n ? chalk.green\n : task.status === 'OVERDUE'\n ? chalk.red\n : chalk.yellow;\n console.log(\n ` ${statusColor(task.status.padEnd(10))} ${task.trace_id} -> ${chalk.dim(task.assignee_id)}`\n );\n console.log(` ${task.todo_description}`);\n }\n console.log();\n }\n });\n\n// ─── plan ───────────────────────────────────────────────────────────────────\n\nprogram\n .command('plan')\n .description('AI-powered task planning from natural language')\n .argument('[prompt]', 'What you want to get done')\n .option('--agents <ids>', 'Comma-separated agent IDs (default: all IDLE)')\n .option('--dispatch', 'Automatically dispatch after planning')\n .action(async (promptArg?: string, opts?: { agents?: string; dispatch?: boolean }) => {\n const db = getDb();\n initSchema(db);\n\n p.intro(chalk.bgCyan(' AI 智能规划 '));\n\n let prompt = promptArg;\n if (!prompt) {\n const input = await p.text({\n message: '输入你的需求:',\n placeholder: '例: 完成首页重构,包括导航栏和内容区的响应式改版',\n validate: v => (!v ? '需求描述不能为空' : undefined),\n });\n if (p.isCancel(input)) {\n p.cancel('已取消');\n process.exit(0);\n }\n prompt = input as string;\n }\n\n const agentIds = opts?.agents?.split(',').map(s => s.trim()).filter(Boolean);\n\n const spin = p.spinner();\n spin.start('AI 正在分析需求、匹配节点、生成话术...');\n\n try {\n const plan = await planJob({ prompt, agent_ids: agentIds }, undefined, db);\n spin.stop('规划完成!');\n\n console.log(chalk.bold(`\\n 需求: ${chalk.white(plan.original_prompt)}\\n`));\n\n for (const task of plan.planned_tasks) {\n console.log(chalk.cyan(` ┌─ ${chalk.bold(task.assignee_name)} (${chalk.dim(task.assignee_id)})`));\n console.log(chalk.cyan(' │') + ` 任务: ${task.todo_description}`);\n console.log(chalk.cyan(' │') + ` 话术: ${chalk.italic(task.briefing)}`);\n console.log(chalk.cyan(' │') + ` 截止: ${new Date(task.deadline).toLocaleString()}`);\n console.log(chalk.cyan(' └─'));\n console.log();\n }\n\n if (opts?.dispatch) {\n const tasks = plan.planned_tasks.map(t => ({\n assignee_id: t.assignee_id,\n todo_description: t.todo_description,\n deadline: t.deadline,\n }));\n const job = dispatchJob({\n original_prompt: plan.original_prompt,\n openclaw_callback: '',\n tasks,\n }, db);\n p.outro(`${chalk.green('已分发!')} Job: ${chalk.bold(job.job_id)}`);\n } else {\n const confirm = await p.confirm({\n message: '确认分发这些任务?',\n });\n if (p.isCancel(confirm) || !confirm) {\n p.outro(chalk.dim('已取消分发'));\n process.exit(0);\n }\n const tasks = plan.planned_tasks.map(t => ({\n assignee_id: t.assignee_id,\n todo_description: t.todo_description,\n deadline: t.deadline,\n }));\n const job = dispatchJob({\n original_prompt: plan.original_prompt,\n openclaw_callback: '',\n tasks,\n }, db);\n p.outro(`${chalk.green('已分发!')} Job: ${chalk.bold(job.job_id)}`);\n }\n } catch (error) {\n spin.stop('规划失败');\n const message = error instanceof Error ? error.message : 'Unknown error';\n p.outro(chalk.red(message));\n process.exit(1);\n }\n });\n\nprogram.parse();\n","import express from 'express';\nimport cors from 'cors';\nimport { getDb } from './db/connection.js';\nimport { initSchema } from './db/schema.js';\nimport nodesRouter from './routes/nodes.js';\nimport jobsRouter from './routes/jobs.js';\nimport tasksRouter from './routes/tasks.js';\nimport syncRouter from './routes/sync.js';\nimport { getDashboardHtml } from './dashboard.js';\n\nexport function createServer(port = 2026) {\n // Initialize database\n const db = getDb();\n initSchema(db);\n\n const app = express();\n\n // Middleware\n app.use(cors());\n app.use(express.json({ limit: '10mb' }));\n\n // API routes\n app.use('/api/v1/nodes', nodesRouter);\n app.use('/api/v1/jobs', jobsRouter);\n app.use('/api/v1/tasks', tasksRouter);\n app.use('/api/v1/jobs', syncRouter);\n\n // Serve dashboard as inline HTML (no build step needed)\n const dashboardHtml = getDashboardHtml();\n app.get('/', (_req, res) => {\n res.type('html').send(dashboardHtml);\n });\n\n // Error handler\n app.use(\n (\n err: Error,\n _req: express.Request,\n res: express.Response,\n _next: express.NextFunction\n ) => {\n console.error('Server error:', err.message);\n res.status(500).json({ error: 'Internal server error' });\n }\n );\n\n return { app, port };\n}\n\nexport function startServer(port = 2026) {\n const { app } = createServer(port);\n\n app.listen(port, () => {\n console.log(`\\n HumanClaw server running at http://localhost:${port}`);\n console.log(` Dashboard: http://localhost:${port}`);\n console.log(` API base: http://localhost:${port}/api/v1\\n`);\n });\n}\n","import Database from 'better-sqlite3';\nimport path from 'node:path';\nimport fs from 'node:fs';\n\nlet db: Database.Database | null = null;\n\nconst DEFAULT_DB_PATH = path.join(\n process.env.HUMANCLAW_DATA_DIR ?? process.cwd(),\n 'humanclaw.db'\n);\n\nexport function getDb(dbPath?: string): Database.Database {\n if (db) return db;\n\n const resolvedPath = dbPath ?? DEFAULT_DB_PATH;\n const dir = path.dirname(resolvedPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n db = new Database(resolvedPath);\n db.pragma('journal_mode = WAL');\n db.pragma('foreign_keys = ON');\n\n return db;\n}\n\nexport function createInMemoryDb(): Database.Database {\n const memDb = new Database(':memory:');\n memDb.pragma('foreign_keys = ON');\n return memDb;\n}\n\nexport function closeDb(): void {\n if (db) {\n db.close();\n db = null;\n }\n}\n\nexport function setDb(newDb: Database.Database): void {\n db = newDb;\n}\n","import type Database from 'better-sqlite3';\n\nexport function initSchema(db: Database.Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS agents (\n agent_id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n capabilities TEXT NOT NULL DEFAULT '[]',\n status TEXT NOT NULL DEFAULT 'IDLE'\n CHECK (status IN ('IDLE', 'BUSY', 'OFFLINE', 'OOM')),\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE TABLE IF NOT EXISTS jobs (\n job_id TEXT PRIMARY KEY,\n original_prompt TEXT NOT NULL,\n openclaw_callback TEXT NOT NULL DEFAULT '',\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE TABLE IF NOT EXISTS tasks (\n trace_id TEXT PRIMARY KEY,\n job_id TEXT NOT NULL REFERENCES jobs(job_id) ON DELETE CASCADE,\n assignee_id TEXT NOT NULL REFERENCES agents(agent_id),\n todo_description TEXT NOT NULL,\n deadline TEXT NOT NULL,\n payload TEXT NOT NULL DEFAULT '{}',\n status TEXT NOT NULL DEFAULT 'PENDING'\n CHECK (status IN ('PENDING', 'DISPATCHED', 'RESOLVED', 'OVERDUE')),\n result_data TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE INDEX IF NOT EXISTS idx_tasks_job_id ON tasks(job_id);\n CREATE INDEX IF NOT EXISTS idx_tasks_assignee_id ON tasks(assignee_id);\n CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);\n `);\n}\n","import { Router } from 'express';\nimport {\n listAgentsWithMetrics,\n createAgent,\n updateAgentStatus,\n deleteAgent,\n} from '../models/agent.js';\nimport { generateId } from '../utils/trace-id.js';\nimport type { AgentStatus } from '../models/types.js';\n\nconst router = Router();\n\n// GET /api/v1/nodes/status - Fleet status with metrics\nrouter.get('/status', (_req, res) => {\n const agents = listAgentsWithMetrics();\n res.json({\n total: agents.length,\n idle: agents.filter(a => a.status === 'IDLE').length,\n busy: agents.filter(a => a.status === 'BUSY').length,\n offline: agents.filter(a => a.status === 'OFFLINE').length,\n oom: agents.filter(a => a.status === 'OOM').length,\n agents,\n });\n});\n\n// POST /api/v1/nodes - Register a new agent\nrouter.post('/', (req, res) => {\n const { name, capabilities, status } = req.body;\n\n if (!name || !Array.isArray(capabilities)) {\n res.status(400).json({ error: 'name and capabilities[] are required' });\n return;\n }\n\n const agent = createAgent({\n agent_id: generateId('emp'),\n name,\n capabilities,\n status: (status as AgentStatus) ?? 'IDLE',\n });\n\n res.status(201).json(agent);\n});\n\n// PATCH /api/v1/nodes/:agent_id/status - Update agent status\nrouter.patch('/:agent_id/status', (req, res) => {\n const { agent_id } = req.params;\n const { status } = req.body;\n\n const validStatuses: AgentStatus[] = ['IDLE', 'BUSY', 'OFFLINE', 'OOM'];\n if (!validStatuses.includes(status)) {\n res.status(400).json({\n error: `Invalid status. Must be one of: ${validStatuses.join(', ')}`,\n });\n return;\n }\n\n const updated = updateAgentStatus(agent_id, status);\n if (!updated) {\n res.status(404).json({ error: `Agent not found: ${agent_id}` });\n return;\n }\n\n res.json({ agent_id, status });\n});\n\n// DELETE /api/v1/nodes/:agent_id\nrouter.delete('/:agent_id', (req, res) => {\n const { agent_id } = req.params;\n const deleted = deleteAgent(agent_id);\n if (!deleted) {\n res.status(404).json({ error: `Agent not found: ${agent_id}` });\n return;\n }\n res.json({ deleted: agent_id });\n});\n\nexport default router;\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport type {\n HumanAgent,\n AgentRow,\n AgentStatus,\n AgentWithMetrics,\n} from './types.js';\n\nfunction rowToAgent(row: AgentRow): HumanAgent {\n return {\n ...row,\n capabilities: JSON.parse(row.capabilities) as string[],\n };\n}\n\nexport function createAgent(\n agent: Omit<HumanAgent, 'created_at'>,\n db?: Database.Database\n): HumanAgent {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n conn\n .prepare(\n `INSERT INTO agents (agent_id, name, capabilities, status, created_at)\n VALUES (?, ?, ?, ?, ?)`\n )\n .run(\n agent.agent_id,\n agent.name,\n JSON.stringify(agent.capabilities),\n agent.status,\n now\n );\n return { ...agent, created_at: now };\n}\n\nexport function getAgent(\n agentId: string,\n db?: Database.Database\n): HumanAgent | undefined {\n const conn = db ?? getDb();\n const row = conn\n .prepare('SELECT * FROM agents WHERE agent_id = ?')\n .get(agentId) as AgentRow | undefined;\n return row ? rowToAgent(row) : undefined;\n}\n\nexport function listAgents(db?: Database.Database): HumanAgent[] {\n const conn = db ?? getDb();\n const rows = conn.prepare('SELECT * FROM agents ORDER BY created_at').all() as AgentRow[];\n return rows.map(rowToAgent);\n}\n\nexport function updateAgentStatus(\n agentId: string,\n status: AgentStatus,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const result = conn\n .prepare('UPDATE agents SET status = ? WHERE agent_id = ?')\n .run(status, agentId);\n return result.changes > 0;\n}\n\nexport function deleteAgent(\n agentId: string,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const result = conn\n .prepare('DELETE FROM agents WHERE agent_id = ?')\n .run(agentId);\n return result.changes > 0;\n}\n\nexport function listAgentsWithMetrics(\n db?: Database.Database\n): AgentWithMetrics[] {\n const conn = db ?? getDb();\n const rows = conn\n .prepare(\n `SELECT\n a.*,\n COUNT(CASE WHEN t.status IN ('DISPATCHED', 'PENDING') THEN 1 END) AS active_task_count,\n AVG(\n CASE WHEN t.status = 'RESOLVED'\n THEN (julianday(t.updated_at) - julianday(t.created_at)) * 24\n END\n ) AS avg_delivery_hours\n FROM agents a\n LEFT JOIN tasks t ON a.agent_id = t.assignee_id\n GROUP BY a.agent_id\n ORDER BY a.created_at`\n )\n .all() as (AgentRow & { active_task_count: number; avg_delivery_hours: number | null })[];\n\n return rows.map(row => ({\n ...rowToAgent(row),\n active_task_count: row.active_task_count,\n avg_delivery_hours: row.avg_delivery_hours\n ? Math.round(row.avg_delivery_hours * 100) / 100\n : null,\n }));\n}\n","import { nanoid } from 'nanoid';\n\nexport function generateTraceId(): string {\n const num = Math.floor(Math.random() * 10000)\n .toString()\n .padStart(4, '0');\n return `TK-${num}`;\n}\n\nexport function generateId(prefix: string): string {\n return `${prefix}_${nanoid(8)}`;\n}\n","import { Router } from 'express';\nimport { dispatchJob } from '../services/dispatch.js';\nimport { planJob } from '../services/planner.js';\nimport { listActiveJobs, getJobWithTasks } from '../models/job.js';\nimport { markOverdueTasks } from '../models/task.js';\nimport type { CreateJobRequest, PlanRequest } from '../models/types.js';\n\nconst router = Router();\n\n// POST /api/v1/jobs/create - Create and dispatch a new job\nrouter.post('/create', (req, res) => {\n const body = req.body as CreateJobRequest;\n\n if (!body.original_prompt || !Array.isArray(body.tasks) || body.tasks.length === 0) {\n res.status(400).json({\n error: 'original_prompt and non-empty tasks[] are required',\n });\n return;\n }\n\n for (const task of body.tasks) {\n if (!task.assignee_id || !task.todo_description || !task.deadline) {\n res.status(400).json({\n error: 'Each task requires assignee_id, todo_description, and deadline',\n });\n return;\n }\n }\n\n try {\n const job = dispatchJob(body);\n res.status(201).json(job);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n res.status(400).json({ error: message });\n }\n});\n\n// GET /api/v1/jobs/active - List active jobs for kanban\nrouter.get('/active', (_req, res) => {\n // Mark overdue tasks before returning\n markOverdueTasks();\n const jobs = listActiveJobs();\n res.json({ jobs });\n});\n\n// POST /api/v1/jobs/plan - AI-powered task planning (does NOT dispatch)\nrouter.post('/plan', async (req, res) => {\n const body = req.body as PlanRequest;\n\n if (!body.prompt || typeof body.prompt !== 'string') {\n res.status(400).json({ error: 'prompt is required' });\n return;\n }\n\n try {\n const plan = await planJob(body);\n res.json(plan);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n const status = message.includes('API Key') || message.includes('API key') ? 503 : 400;\n res.status(status).json({ error: message });\n }\n});\n\n// GET /api/v1/jobs/:job_id - Get a single job with tasks\nrouter.get('/:job_id', (req, res) => {\n const { job_id } = req.params;\n const job = getJobWithTasks(job_id);\n if (!job) {\n res.status(404).json({ error: `Job not found: ${job_id}` });\n return;\n }\n res.json(job);\n});\n\nexport default router;\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport type { HumanTask, TaskRow, TaskStatus } from './types.js';\n\nfunction rowToTask(row: TaskRow): HumanTask {\n return {\n ...row,\n payload: JSON.parse(row.payload) as Record<string, unknown>,\n result_data: row.result_data ? JSON.parse(row.result_data) : null,\n };\n}\n\nexport function createTask(\n task: Omit<HumanTask, 'created_at' | 'updated_at' | 'result_data'>,\n db?: Database.Database\n): HumanTask {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n conn\n .prepare(\n `INSERT INTO tasks (trace_id, job_id, assignee_id, todo_description, deadline, payload, status, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`\n )\n .run(\n task.trace_id,\n task.job_id,\n task.assignee_id,\n task.todo_description,\n task.deadline,\n JSON.stringify(task.payload),\n task.status,\n now,\n now\n );\n return { ...task, result_data: null, created_at: now, updated_at: now };\n}\n\nexport function getTask(\n traceId: string,\n db?: Database.Database\n): HumanTask | undefined {\n const conn = db ?? getDb();\n const row = conn\n .prepare('SELECT * FROM tasks WHERE trace_id = ?')\n .get(traceId) as TaskRow | undefined;\n return row ? rowToTask(row) : undefined;\n}\n\nexport function listTasksByJob(\n jobId: string,\n db?: Database.Database\n): HumanTask[] {\n const conn = db ?? getDb();\n const rows = conn\n .prepare('SELECT * FROM tasks WHERE job_id = ? ORDER BY created_at')\n .all(jobId) as TaskRow[];\n return rows.map(rowToTask);\n}\n\nexport function listTasksByAssignee(\n assigneeId: string,\n db?: Database.Database\n): HumanTask[] {\n const conn = db ?? getDb();\n const rows = conn\n .prepare('SELECT * FROM tasks WHERE assignee_id = ? ORDER BY created_at')\n .all(assigneeId) as TaskRow[];\n return rows.map(rowToTask);\n}\n\nexport function updateTaskStatus(\n traceId: string,\n status: TaskStatus,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n const result = conn\n .prepare('UPDATE tasks SET status = ?, updated_at = ? WHERE trace_id = ?')\n .run(status, now, traceId);\n return result.changes > 0;\n}\n\nexport function resolveTask(\n traceId: string,\n resultData: unknown,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n const result = conn\n .prepare(\n `UPDATE tasks\n SET status = 'RESOLVED', result_data = ?, updated_at = ?\n WHERE trace_id = ? AND status IN ('DISPATCHED', 'OVERDUE')`\n )\n .run(JSON.stringify(resultData), now, traceId);\n return result.changes > 0;\n}\n\nexport function markOverdueTasks(db?: Database.Database): number {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n const result = conn\n .prepare(\n `UPDATE tasks\n SET status = 'OVERDUE', updated_at = ?\n WHERE status = 'DISPATCHED' AND deadline < ?`\n )\n .run(now, now);\n return result.changes;\n}\n\nexport function resetTaskDeadline(\n traceId: string,\n newDeadline: string,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n const result = conn\n .prepare(\n `UPDATE tasks\n SET status = 'DISPATCHED', deadline = ?, result_data = NULL, updated_at = ?\n WHERE trace_id = ?`\n )\n .run(newDeadline, now, traceId);\n return result.changes > 0;\n}\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport type { OrchestrationJob, JobRow, JobWithTasks } from './types.js';\nimport { listTasksByJob } from './task.js';\n\nexport function createJob(\n job: OrchestrationJob,\n db?: Database.Database\n): OrchestrationJob {\n const conn = db ?? getDb();\n conn\n .prepare(\n `INSERT INTO jobs (job_id, original_prompt, openclaw_callback, created_at)\n VALUES (?, ?, ?, ?)`\n )\n .run(job.job_id, job.original_prompt, job.openclaw_callback, job.created_at);\n return job;\n}\n\nexport function getJob(\n jobId: string,\n db?: Database.Database\n): OrchestrationJob | undefined {\n const conn = db ?? getDb();\n return conn\n .prepare('SELECT * FROM jobs WHERE job_id = ?')\n .get(jobId) as JobRow | undefined;\n}\n\nexport function getJobWithTasks(\n jobId: string,\n db?: Database.Database\n): JobWithTasks | undefined {\n const conn = db ?? getDb();\n const job = conn\n .prepare('SELECT * FROM jobs WHERE job_id = ?')\n .get(jobId) as JobRow | undefined;\n if (!job) return undefined;\n\n const tasks = listTasksByJob(jobId, conn);\n return { ...job, tasks };\n}\n\nexport function listActiveJobs(db?: Database.Database): JobWithTasks[] {\n const conn = db ?? getDb();\n const jobs = conn\n .prepare(\n `SELECT DISTINCT j.*\n FROM jobs j\n INNER JOIN tasks t ON j.job_id = t.job_id\n WHERE t.status != 'RESOLVED'\n ORDER BY j.created_at DESC`\n )\n .all() as JobRow[];\n\n // Also include jobs where all tasks are resolved but not yet synced\n const allJobs = conn\n .prepare('SELECT * FROM jobs ORDER BY created_at DESC')\n .all() as JobRow[];\n\n const activeJobIds = new Set(jobs.map(j => j.job_id));\n const result: JobWithTasks[] = [];\n\n for (const job of allJobs) {\n const tasks = listTasksByJob(job.job_id, conn);\n if (tasks.length > 0) {\n result.push({ ...job, tasks });\n }\n }\n\n return result;\n}\n\nexport function isJobComplete(\n jobId: string,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const row = conn\n .prepare(\n `SELECT COUNT(*) as total,\n SUM(CASE WHEN status = 'RESOLVED' THEN 1 ELSE 0 END) as resolved\n FROM tasks WHERE job_id = ?`\n )\n .get(jobId) as { total: number; resolved: number };\n return row.total > 0 && row.total === row.resolved;\n}\n\nexport function deleteJob(\n jobId: string,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const result = conn\n .prepare('DELETE FROM jobs WHERE job_id = ?')\n .run(jobId);\n return result.changes > 0;\n}\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport { createJob } from '../models/job.js';\nimport { createTask } from '../models/task.js';\nimport { getAgent, updateAgentStatus } from '../models/agent.js';\nimport { generateTraceId, generateId } from '../utils/trace-id.js';\nimport type { CreateJobRequest, JobWithTasks } from '../models/types.js';\n\nexport function dispatchJob(\n request: CreateJobRequest,\n db?: Database.Database\n): JobWithTasks {\n const conn = db ?? getDb();\n const jobId = generateId('job');\n const now = new Date().toISOString();\n\n // Validate all assignees exist\n for (const taskReq of request.tasks) {\n const agent = getAgent(taskReq.assignee_id, conn);\n if (!agent) {\n throw new Error(`Agent not found: ${taskReq.assignee_id}`);\n }\n if (agent.status === 'OFFLINE') {\n throw new Error(`Agent is offline: ${taskReq.assignee_id} (${agent.name})`);\n }\n }\n\n // Create the job\n const job = createJob(\n {\n job_id: jobId,\n original_prompt: request.original_prompt,\n openclaw_callback: request.openclaw_callback,\n created_at: now,\n },\n conn\n );\n\n // Create and dispatch all tasks\n const tasks = request.tasks.map(taskReq => {\n const traceId = generateTraceId();\n const task = createTask(\n {\n trace_id: traceId,\n job_id: jobId,\n assignee_id: taskReq.assignee_id,\n todo_description: taskReq.todo_description,\n deadline: taskReq.deadline,\n payload: taskReq.payload ?? {},\n status: 'DISPATCHED',\n },\n conn\n );\n\n // Mark the agent as busy\n updateAgentStatus(taskReq.assignee_id, 'BUSY', conn);\n\n return task;\n });\n\n return { ...job, tasks };\n}\n","import type { LlmProvider, LlmCompletionRequest, LlmCompletionResponse } from './types.js';\n\nexport class ClaudeProvider implements LlmProvider {\n private apiKey: string;\n private model: string;\n\n constructor(apiKey: string, model?: string) {\n this.apiKey = apiKey;\n this.model = model || 'claude-sonnet-4-20250514';\n }\n\n async complete(request: LlmCompletionRequest): Promise<LlmCompletionResponse> {\n const systemMessages = request.messages.filter(m => m.role === 'system');\n const nonSystemMessages = request.messages.filter(m => m.role !== 'system');\n\n const body: Record<string, unknown> = {\n model: this.model,\n max_tokens: request.max_tokens || 4096,\n messages: nonSystemMessages.map(m => ({ role: m.role, content: m.content })),\n };\n\n if (request.temperature !== undefined) {\n body.temperature = request.temperature;\n }\n\n if (systemMessages.length > 0) {\n body.system = systemMessages.map(m => m.content).join('\\n\\n');\n }\n\n const response = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': this.apiKey,\n 'anthropic-version': '2023-06-01',\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Claude API error (${response.status}): ${errorText}`);\n }\n\n const data = await response.json() as { content: Array<{ type: string; text: string }> };\n const textBlock = data.content.find(c => c.type === 'text');\n\n if (!textBlock) {\n throw new Error('Claude API returned no text content');\n }\n\n return { content: textBlock.text };\n }\n}\n","import type { LlmProvider, LlmCompletionRequest, LlmCompletionResponse } from './types.js';\n\nexport class OpenAIProvider implements LlmProvider {\n private apiKey: string;\n private model: string;\n\n constructor(apiKey: string, model?: string) {\n this.apiKey = apiKey;\n this.model = model || 'gpt-4o';\n }\n\n async complete(request: LlmCompletionRequest): Promise<LlmCompletionResponse> {\n const body: Record<string, unknown> = {\n model: this.model,\n max_tokens: request.max_tokens || 4096,\n messages: request.messages.map(m => ({ role: m.role, content: m.content })),\n };\n\n if (request.temperature !== undefined) {\n body.temperature = request.temperature;\n }\n\n const response = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`OpenAI API error (${response.status}): ${errorText}`);\n }\n\n const data = await response.json() as {\n choices: Array<{ message: { content: string } }>;\n };\n\n const content = data.choices[0]?.message?.content;\n if (!content) {\n throw new Error('OpenAI API returned no content');\n }\n\n return { content };\n }\n}\n","import type { LlmProvider, LlmProviderName } from './types.js';\nimport { ClaudeProvider } from './claude.js';\nimport { OpenAIProvider } from './openai.js';\n\nexport interface LlmConfig {\n provider: LlmProviderName;\n apiKey: string;\n model?: string;\n}\n\nexport function getLlmConfig(): LlmConfig {\n const provider = (process.env.HUMANCLAW_LLM_PROVIDER || 'claude') as LlmProviderName;\n const apiKey = process.env.HUMANCLAW_LLM_API_KEY || '';\n const model = process.env.HUMANCLAW_LLM_MODEL;\n\n if (!apiKey) {\n throw new Error(\n '未配置 LLM API Key。请设置环境变量 HUMANCLAW_LLM_API_KEY。\\n' +\n '例如: export HUMANCLAW_LLM_API_KEY=sk-...'\n );\n }\n\n if (provider !== 'claude' && provider !== 'openai') {\n throw new Error(`不支持的 LLM 提供商: ${provider}。支持: claude, openai`);\n }\n\n return { provider, apiKey, model };\n}\n\nexport function createLlmProvider(config?: LlmConfig): LlmProvider {\n const cfg = config || getLlmConfig();\n\n switch (cfg.provider) {\n case 'claude':\n return new ClaudeProvider(cfg.apiKey, cfg.model);\n case 'openai':\n return new OpenAIProvider(cfg.apiKey, cfg.model);\n }\n}\n\nexport type { LlmProvider, LlmProviderName } from './types.js';\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport { listAgentsWithMetrics, getAgent } from '../models/agent.js';\nimport { createLlmProvider } from '../llm/index.js';\nimport type { PlanRequest, PlanResponse, PlannedTask, AgentWithMetrics } from '../models/types.js';\nimport type { LlmProvider } from '../llm/types.js';\n\nfunction buildSystemPrompt(): string {\n return `你是 HumanClaw 任务编排规划器。你的工作是将用户的需求拆解为可以分发给物理节点(真实人类)执行的独立子任务。\n\n规则:\n1. 将需求拆解为扁平的、无依赖的子任务列表(每个任务可以独立执行)\n2. 根据每个 Agent 的技能(capabilities)和当前负载来匹配分配\n3. 为每个任务生成一段「话术」—— 这是直接发给该人类执行者的任务说明,语气自然、清晰、专业,包含具体的交付物要求\n4. 根据任务复杂度设置合理的截止时间(相对于当前时间)\n5. 每个 Agent 最多分配一个任务(除非人手不够)\n\n你必须严格输出以下 JSON 格式(不要输出任何其他内容):\n\n\\`\\`\\`json\n[\n {\n \"assignee_id\": \"agent的ID\",\n \"assignee_name\": \"agent的名字\",\n \"todo_description\": \"简短的任务标题/描述\",\n \"briefing\": \"话术:详细的任务说明,包括目标、交付物要求、注意事项。语气像一个靠谱的项目经理在给组员分配任务。\",\n \"deadline\": \"ISO 8601 时间\"\n }\n]\n\\`\\`\\``;\n}\n\nfunction buildUserPrompt(prompt: string, agents: AgentWithMetrics[]): string {\n const now = new Date();\n const agentList = agents.map(a => {\n const load = a.active_task_count > 0\n ? `当前有 ${a.active_task_count} 个进行中的任务`\n : '当前空闲';\n const speed = a.avg_delivery_hours !== null\n ? `平均交付时间 ${a.avg_delivery_hours}h`\n : '暂无历史数据';\n return `- ${a.name} (ID: ${a.agent_id}) 技能: [${a.capabilities.join(', ')}] ${load} ${speed}`;\n }).join('\\n');\n\n return `当前时间: ${now.toISOString()}\n\n可用的物理节点:\n${agentList}\n\n需求:\n${prompt}\n\n请根据以上信息拆解任务并分配。输出 JSON 数组。`;\n}\n\nfunction extractJson(raw: string): string {\n // Try to extract JSON from markdown code block\n const codeBlockMatch = raw.match(/```(?:json)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```/);\n if (codeBlockMatch) {\n return codeBlockMatch[1].trim();\n }\n // Try to find a JSON array directly\n const arrayMatch = raw.match(/\\[[\\s\\S]*\\]/);\n if (arrayMatch) {\n return arrayMatch[0];\n }\n return raw.trim();\n}\n\nfunction validatePlannedTasks(\n parsed: unknown,\n validAgentIds: Set<string>,\n agents: AgentWithMetrics[]\n): PlannedTask[] {\n if (!Array.isArray(parsed)) {\n throw new Error('LLM 返回的不是有效的任务数组');\n }\n\n if (parsed.length === 0) {\n throw new Error('LLM 未生成任何任务');\n }\n\n return parsed.map((item: Record<string, unknown>, i: number) => {\n if (!item.todo_description || typeof item.todo_description !== 'string') {\n throw new Error(`任务 ${i + 1} 缺少 todo_description`);\n }\n if (!item.briefing || typeof item.briefing !== 'string') {\n throw new Error(`任务 ${i + 1} 缺少 briefing (话术)`);\n }\n\n // Validate or fallback assignee_id\n let assigneeId = String(item.assignee_id || '');\n let assigneeName = String(item.assignee_name || '');\n\n if (!validAgentIds.has(assigneeId)) {\n // Fallback to first available agent\n const fallback = agents[0];\n if (fallback) {\n assigneeId = fallback.agent_id;\n assigneeName = fallback.name;\n }\n }\n\n // Validate deadline or generate a default\n let deadline = String(item.deadline || '');\n if (!deadline || isNaN(Date.parse(deadline))) {\n // Default: 24 hours from now\n deadline = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();\n }\n\n return {\n assignee_id: assigneeId,\n assignee_name: assigneeName,\n todo_description: String(item.todo_description),\n briefing: String(item.briefing),\n deadline,\n };\n });\n}\n\nexport async function planJob(\n request: PlanRequest,\n provider?: LlmProvider,\n db?: Database.Database\n): Promise<PlanResponse> {\n const conn = db ?? getDb();\n\n // Get available agents\n const allAgents = listAgentsWithMetrics(conn);\n let agents: AgentWithMetrics[];\n\n if (request.agent_ids && request.agent_ids.length > 0) {\n agents = allAgents.filter(a => request.agent_ids!.includes(a.agent_id));\n if (agents.length === 0) {\n throw new Error('指定的 Agent 均不存在');\n }\n } else {\n // Default: only IDLE agents\n agents = allAgents.filter(a => a.status === 'IDLE');\n if (agents.length === 0) {\n // Fallback: include BUSY agents too (but not OFFLINE/OOM)\n agents = allAgents.filter(a => a.status !== 'OFFLINE' && a.status !== 'OOM');\n }\n if (agents.length === 0) {\n throw new Error('没有可用的物理节点。请先在碳基算力池中添加节点。');\n }\n }\n\n // Get LLM provider\n const llm = provider ?? createLlmProvider();\n\n const systemPrompt = buildSystemPrompt();\n const userPrompt = buildUserPrompt(request.prompt, agents);\n\n const response = await llm.complete({\n messages: [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: userPrompt },\n ],\n temperature: 0.3,\n max_tokens: 4096,\n });\n\n // Parse LLM response\n const jsonStr = extractJson(response.content);\n let parsed: unknown;\n try {\n parsed = JSON.parse(jsonStr);\n } catch {\n throw new Error('AI 返回的内容无法解析为 JSON,请重试');\n }\n\n const validIds = new Set(agents.map(a => a.agent_id));\n const plannedTasks = validatePlannedTasks(parsed, validIds, agents);\n\n return {\n original_prompt: request.prompt,\n planned_tasks: plannedTasks,\n };\n}\n","import { Router } from 'express';\nimport { resumeTask, rejectTask } from '../services/resume.js';\n\nconst router = Router();\n\n// POST /api/v1/tasks/resume - Submit result for a task\nrouter.post('/resume', (req, res) => {\n const { trace_id, result_data } = req.body;\n\n if (!trace_id) {\n res.status(400).json({ error: 'trace_id is required' });\n return;\n }\n\n if (result_data === undefined) {\n res.status(400).json({ error: 'result_data is required' });\n return;\n }\n\n try {\n const result = resumeTask(trace_id, result_data);\n res.json({\n task: result.task,\n job_complete: result.jobComplete,\n job: result.job ?? null,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n res.status(400).json({ error: message });\n }\n});\n\n// POST /api/v1/tasks/reject - Reject and retry a task\nrouter.post('/reject', (req, res) => {\n const { trace_id, new_deadline } = req.body;\n\n if (!trace_id) {\n res.status(400).json({ error: 'trace_id is required' });\n return;\n }\n\n try {\n const task = rejectTask(trace_id, new_deadline);\n res.json({ task });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n res.status(400).json({ error: message });\n }\n});\n\nexport default router;\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport { getTask, resolveTask, resetTaskDeadline } from '../models/task.js';\nimport { isJobComplete, getJobWithTasks } from '../models/job.js';\nimport { updateAgentStatus } from '../models/agent.js';\nimport { listTasksByAssignee } from '../models/task.js';\nimport type { HumanTask, JobWithTasks } from '../models/types.js';\n\nexport interface ResumeResult {\n task: HumanTask;\n jobComplete: boolean;\n job: JobWithTasks | undefined;\n}\n\nexport function resumeTask(\n traceId: string,\n resultData: unknown,\n db?: Database.Database\n): ResumeResult {\n const conn = db ?? getDb();\n\n // Validate trace_id exists\n const task = getTask(traceId, conn);\n if (!task) {\n throw new Error(`Task not found: ${traceId}`);\n }\n\n if (task.status === 'RESOLVED') {\n throw new Error(`Task already resolved: ${traceId}`);\n }\n\n if (task.status === 'PENDING') {\n throw new Error(`Task not yet dispatched: ${traceId}`);\n }\n\n // Resolve the task\n const updated = resolveTask(traceId, resultData, conn);\n if (!updated) {\n throw new Error(`Failed to resolve task: ${traceId}`);\n }\n\n // Check if the agent has other active tasks; if not, mark IDLE\n const activeTasks = listTasksByAssignee(task.assignee_id, conn).filter(\n t => t.status === 'DISPATCHED' || t.status === 'PENDING'\n );\n if (activeTasks.length === 0) {\n updateAgentStatus(task.assignee_id, 'IDLE', conn);\n }\n\n // Check if the whole job is now complete\n const jobComplete = isJobComplete(task.job_id, conn);\n const job = jobComplete ? getJobWithTasks(task.job_id, conn) : undefined;\n\n const resolvedTask = getTask(traceId, conn)!;\n\n return { task: resolvedTask, jobComplete, job };\n}\n\nexport function rejectTask(\n traceId: string,\n newDeadline?: string,\n db?: Database.Database\n): HumanTask {\n const conn = db ?? getDb();\n\n const task = getTask(traceId, conn);\n if (!task) {\n throw new Error(`Task not found: ${traceId}`);\n }\n\n // Default: extend deadline by 24h from now\n const deadline =\n newDeadline ?? new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();\n\n resetTaskDeadline(traceId, deadline, conn);\n\n return getTask(traceId, conn)!;\n}\n","import { Router } from 'express';\nimport { aggregateJob, syncToOpenClaw } from '../services/aggregation.js';\n\nconst router = Router();\n\n// POST /api/v1/jobs/:job_id/sync - Aggregate and sync to OpenClaw\nrouter.post('/:job_id/sync', async (req, res) => {\n const { job_id } = req.params;\n\n try {\n const aggregation = aggregateJob(job_id);\n const syncResult = await syncToOpenClaw(aggregation);\n\n res.json({\n aggregation,\n sync: syncResult,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n res.status(400).json({ error: message });\n }\n});\n\nexport default router;\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport { getJobWithTasks, isJobComplete } from '../models/job.js';\nimport type { JobWithTasks } from '../models/types.js';\n\nexport interface AggregationResult {\n job_id: string;\n original_prompt: string;\n openclaw_callback: string;\n results: Array<{\n trace_id: string;\n assignee_id: string;\n todo_description: string;\n result_data: unknown;\n }>;\n aggregated_at: string;\n}\n\nexport function aggregateJob(\n jobId: string,\n db?: Database.Database\n): AggregationResult {\n const conn = db ?? getDb();\n\n const job = getJobWithTasks(jobId, conn);\n if (!job) {\n throw new Error(`Job not found: ${jobId}`);\n }\n\n if (!isJobComplete(jobId, conn)) {\n const resolved = job.tasks.filter(t => t.status === 'RESOLVED').length;\n throw new Error(\n `Job not complete: ${resolved}/${job.tasks.length} tasks resolved`\n );\n }\n\n return {\n job_id: job.job_id,\n original_prompt: job.original_prompt,\n openclaw_callback: job.openclaw_callback,\n results: job.tasks.map(t => ({\n trace_id: t.trace_id,\n assignee_id: t.assignee_id,\n todo_description: t.todo_description,\n result_data: t.result_data,\n })),\n aggregated_at: new Date().toISOString(),\n };\n}\n\nexport async function syncToOpenClaw(\n aggregation: AggregationResult\n): Promise<{ success: boolean; message: string }> {\n if (!aggregation.openclaw_callback) {\n return {\n success: true,\n message: 'No OpenClaw callback configured, skipping sync',\n };\n }\n\n try {\n const response = await fetch(aggregation.openclaw_callback, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(aggregation),\n });\n\n if (!response.ok) {\n return {\n success: false,\n message: `OpenClaw sync failed: ${response.status} ${response.statusText}`,\n };\n }\n\n return { success: true, message: 'Synced to OpenClaw successfully' };\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Unknown error';\n return { success: false, message: `OpenClaw sync error: ${message}` };\n }\n}\n","export function getDashboardHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"UTF-8\"/>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"/>\n<title>HumanClaw - 异步物理节点编排</title>\n<style>\n:root{--bg:#0f1117;--surface:#1a1d27;--surface-hover:#242836;--border:#2e3346;--text:#e2e4ed;--text-dim:#7b8196;--accent:#00d4ff;--accent-dim:rgba(0,212,255,.12);--green:#22c55e;--yellow:#eab308;--red:#ef4444;--purple:#a855f7;--font-mono:'SF Mono','JetBrains Mono','Fira Code',monospace;--font-sans:-apple-system,'Inter','Segoe UI',sans-serif;--radius:10px}\n*{margin:0;padding:0;box-sizing:border-box}\nbody{background:var(--bg);color:var(--text);font-family:var(--font-sans);min-height:100vh;line-height:1.5}\nheader{padding:20px 32px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:14px}\nheader h1{font-family:var(--font-mono);font-size:22px;color:var(--accent);letter-spacing:1.5px}\n.subtitle{color:var(--text-dim);font-size:12px;border-left:1px solid var(--border);padding-left:14px}\nnav{display:flex;border-bottom:1px solid var(--border);padding:0 32px;gap:4px}\n.tab{background:none;border:none;color:var(--text-dim);padding:12px 18px;font-size:13px;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s;font-family:var(--font-sans);font-weight:500}\n.tab:hover{color:var(--text);background:var(--surface)}\n.tab.active{color:var(--accent);border-bottom-color:var(--accent)}\nmain{padding:24px 32px;max-width:1200px}\n.panel{display:none}.panel.active{display:block}\n/* Cards */\n.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;transition:border-color .15s}\n.card:hover{border-color:color-mix(in srgb,var(--accent) 40%,transparent)}\n.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;margin-top:16px}\n/* Agent Card */\n.agent-header{display:flex;align-items:center;gap:10px;margin-bottom:10px}\n.dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}\n.dot.IDLE{background:var(--green);box-shadow:0 0 8px var(--green)}.dot.BUSY{background:var(--yellow);box-shadow:0 0 8px var(--yellow)}.dot.OFFLINE{background:var(--red);box-shadow:0 0 6px var(--red)}.dot.OOM{background:var(--purple);box-shadow:0 0 8px var(--purple)}\n.agent-name{font-weight:600;font-size:15px}\n.agent-id{color:var(--text-dim);font-family:var(--font-mono);font-size:11px;margin-bottom:8px}\n.caps{display:flex;flex-wrap:wrap;gap:5px;margin-top:6px}\n.cap{background:var(--accent-dim);border:1px solid color-mix(in srgb,var(--accent) 25%,transparent);border-radius:4px;padding:1px 8px;font-size:11px;color:var(--accent)}\n.agent-meta{margin-top:10px;font-size:11px;color:var(--text-dim);display:flex;gap:12px}\n.agent-actions{margin-top:12px;display:flex;gap:6px}\n.agent-actions select{background:var(--surface-hover);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:11px;cursor:pointer;outline:none}\n.agent-actions button{background:var(--red);color:#fff;border:none;border-radius:6px;padding:4px 10px;font-size:11px;cursor:pointer}\n.agent-actions button:hover{opacity:.8}\n/* Stats Bar */\n.stats{display:flex;gap:12px;flex-wrap:wrap}\n.stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 22px;text-align:center;min-width:90px}\n.stat-val{font-size:28px;font-weight:700;font-family:var(--font-mono)}\n.stat-lbl{font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:1px;margin-top:2px}\n/* Forms */\n.form-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px;margin-top:16px;max-width:620px}\n.form-card h3{font-size:15px;color:var(--accent);font-family:var(--font-mono);margin-bottom:16px}\n.fg{margin-bottom:14px}\n.fg label{display:block;font-size:12px;color:var(--text-dim);margin-bottom:5px;font-weight:500}\n.fg input,.fg textarea,.fg select{width:100%;background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:10px 12px;color:var(--text);font-size:13px;font-family:var(--font-sans);outline:none;transition:border-color .15s}\n.fg input:focus,.fg textarea:focus,.fg select:focus{border-color:var(--accent)}\n.fg textarea{min-height:80px;resize:vertical;font-family:var(--font-mono);font-size:12px}\n.fg .hint{font-size:11px;color:var(--text-dim);margin-top:4px}\n.btn{padding:10px 22px;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;transition:all .15s;display:inline-flex;align-items:center;gap:6px}\n.btn:hover{opacity:.85;transform:translateY(-1px)}\n.btn:active{transform:translateY(0)}\n.btn:disabled{opacity:.5;cursor:not-allowed;transform:none}\n.btn-primary{background:var(--accent);color:var(--bg)}\n.btn-green{background:var(--green);color:#fff}\n.btn-danger{background:var(--red);color:#fff}\n.btn-ghost{background:transparent;color:var(--accent);border:1px solid var(--border)}\n.btn-ghost:hover{background:var(--accent-dim)}\n.btn-sm{padding:6px 14px;font-size:12px}\n.btn-group{display:flex;gap:10px;margin-top:16px;flex-wrap:wrap}\n/* Section header */\n.section-hd{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}\n.section-hd h2{font-size:16px;font-weight:600}\n/* Job card */\n.job-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-top:14px}\n.job-hd{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}\n.job-title{font-weight:600;font-size:14px}\n.job-id{font-family:var(--font-mono);font-size:11px;color:var(--text-dim)}\n.pbar-wrap{margin:6px 0 14px}\n.pbar-label{font-size:11px;color:var(--text-dim);margin-bottom:4px}\n.pbar{background:var(--surface-hover);border-radius:4px;height:6px;overflow:hidden}\n.pbar-fill{background:var(--accent);height:100%;border-radius:4px;transition:width .3s}\n.kanban{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px}\n.lane{min-height:40px}\n.lane-hd{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px;padding-bottom:4px;border-bottom:2px solid var(--border)}\n.lane-hd.y{color:var(--yellow);border-color:var(--yellow)}.lane-hd.r{color:var(--red);border-color:var(--red)}.lane-hd.g{color:var(--green);border-color:var(--green)}\n.tcard{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:6px;font-size:12px}\n.tcard-trace{font-family:var(--font-mono);font-size:10px;color:var(--accent);margin-bottom:3px}\n.tcard-meta{font-size:10px;color:var(--text-dim);margin-top:3px}\n/* Toast */\n.toast{position:fixed;bottom:24px;right:24px;padding:12px 20px;border-radius:8px;font-size:13px;z-index:9999;animation:slide-up .25s ease;font-weight:500;box-shadow:0 8px 24px rgba(0,0,0,.4)}\n.toast.ok{background:var(--green);color:#fff}.toast.err{background:var(--red);color:#fff}\n@keyframes slide-up{from{transform:translateY(16px);opacity:0}to{transform:translateY(0);opacity:1}}\n/* Empty state */\n.empty-state{text-align:center;padding:48px 20px;color:var(--text-dim)}\n.empty-state .icon{font-size:48px;margin-bottom:12px;opacity:.6}\n.empty-state p{font-size:14px;margin-bottom:16px;max-width:360px;margin-left:auto;margin-right:auto;line-height:1.6}\n/* Modal/Overlay */\n.overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:100;display:flex;align-items:flex-start;justify-content:center;padding-top:60px;animation:fade-in .15s ease}\n.overlay .form-card{max-width:620px;width:100%;max-height:85vh;overflow-y:auto;margin:0}\n@keyframes fade-in{from{opacity:0}to{opacity:1}}\n/* Agent chips for AI planning */\n.chip-bar{display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap}\n.chip-filter{background:var(--surface-hover);color:var(--text-dim);border:1px solid var(--border);border-radius:14px;padding:3px 12px;font-size:11px;cursor:pointer;transition:all .15s}\n.chip-filter.active{background:var(--accent-dim);color:var(--accent);border-color:var(--accent)}\n.agent-chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}\n.achip{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:6px;user-select:none}\n.achip:hover{border-color:var(--text-dim)}\n.achip.selected{border-color:var(--accent);background:var(--accent-dim)}\n.achip .adot{width:8px;height:8px;border-radius:50%;flex-shrink:0}\n.achip .adot.IDLE{background:var(--green)}.achip .adot.BUSY{background:var(--yellow)}.achip .adot.OFFLINE{background:var(--red)}.achip .adot.OOM{background:var(--purple)}\n/* Plan preview cards */\n.plan-card{background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:16px;margin-bottom:12px}\n.plan-card-hd{display:flex;align-items:center;gap:8px;margin-bottom:8px}\n.plan-card-hd .adot{width:8px;height:8px;border-radius:50%}\n.plan-card-agent{font-weight:600;font-size:13px}\n.plan-card-agentid{font-family:var(--font-mono);font-size:10px;color:var(--text-dim)}\n.plan-card-desc{font-size:13px;margin-bottom:10px;font-weight:500}\n.briefing-box{background:var(--surface);border-left:3px solid var(--accent);border-radius:0 6px 6px 0;padding:10px 14px;font-size:12px;line-height:1.6;color:var(--text);margin-bottom:8px;position:relative}\n.briefing-label{font-size:10px;color:var(--accent);font-weight:600;margin-bottom:4px;font-family:var(--font-mono)}\n.briefing-copy{position:absolute;top:8px;right:8px;background:var(--surface-hover);border:1px solid var(--border);color:var(--text-dim);border-radius:4px;padding:2px 8px;font-size:10px;cursor:pointer}\n.briefing-copy:hover{color:var(--accent);border-color:var(--accent)}\n.plan-card-dl{font-size:11px;color:var(--text-dim);font-family:var(--font-mono)}\n/* Spinner */\n.spinner-wrap{text-align:center;padding:40px 20px}\n.spinner{display:inline-block;width:28px;height:28px;border:3px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .6s linear infinite}\n@keyframes spin{to{transform:rotate(360deg)}}\n.spinner-text{color:var(--text-dim);font-size:13px;margin-top:12px}\n/* Manual task row */\n.task-row{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:14px;margin-bottom:10px;position:relative}\n.task-row .remove-task{position:absolute;top:8px;right:10px;background:none;border:none;color:var(--red);cursor:pointer;font-size:16px;line-height:1}\n.task-row .fg{margin-bottom:10px}\n.task-row .fg:last-child{margin-bottom:0}\n</style>\n</head>\n<body>\n<header>\n <h1>HumanClaw</h1>\n <span class=\"subtitle\">异步物理节点编排框架</span>\n</header>\n<nav>\n <button class=\"tab active\" data-panel=\"fleet\">碳基算力池</button>\n <button class=\"tab\" data-panel=\"pipeline\">编排大盘</button>\n <button class=\"tab\" data-panel=\"terminal\">I/O 终端</button>\n</nav>\n<main>\n <section id=\"fleet\" class=\"panel active\"></section>\n <section id=\"pipeline\" class=\"panel\"></section>\n <section id=\"terminal\" class=\"panel\"></section>\n</main>\n\n<script>\nconst API='/api/v1';\nfunction esc(s){const d=document.createElement('div');d.textContent=s;return d.innerHTML}\nfunction toast(msg,ok){\n document.querySelectorAll('.toast').forEach(t=>t.remove());\n const t=document.createElement('div');t.className='toast '+(ok?'ok':'err');t.textContent=msg;document.body.appendChild(t);\n setTimeout(()=>t.remove(),3500);\n}\nlet cachedAgents=[];\nlet currentPlan=null;\nlet selectedAgentIds=new Set();\n\n// ═══════════════════════════════════════════════\n// FLEET\n// ═══════════════════════════════════════════════\nasync function loadFleet(el){\n el.innerHTML='<div class=\"empty-state\"><p>加载中...</p></div>';\n try{\n const r=await fetch(API+'/nodes/status');\n const d=await r.json();\n cachedAgents=d.agents||[];\n let h='<div class=\"section-hd\"><h2>碳基算力池</h2><button class=\"btn btn-primary btn-sm\" onclick=\"showAddAgent()\">+ 添加节点</button></div>';\n h+='<div class=\"stats\">';\n h+='<div class=\"stat\"><div class=\"stat-val\">'+d.total+'</div><div class=\"stat-lbl\">总计</div></div>';\n h+='<div class=\"stat\"><div class=\"stat-val\" style=\"color:var(--green)\">'+d.idle+'</div><div class=\"stat-lbl\">空闲</div></div>';\n h+='<div class=\"stat\"><div class=\"stat-val\" style=\"color:var(--yellow)\">'+d.busy+'</div><div class=\"stat-lbl\">忙碌</div></div>';\n h+='<div class=\"stat\"><div class=\"stat-val\" style=\"color:var(--red)\">'+d.offline+'</div><div class=\"stat-lbl\">离线</div></div>';\n h+='<div class=\"stat\"><div class=\"stat-val\" style=\"color:var(--purple)\">'+d.oom+'</div><div class=\"stat-lbl\">崩溃</div></div>';\n h+='</div>';\n if(!d.agents.length){\n h+='<div class=\"empty-state\" style=\"margin-top:32px\"><div class=\"icon\">👤</div><p>还没有物理节点。点击上方「+ 添加节点」注册你的第一个碳基算力单元。</p></div>';\n el.innerHTML=h;return;\n }\n h+='<div class=\"grid\">';\n for(const a of d.agents){\n h+='<div class=\"card\"><div class=\"agent-header\"><span class=\"dot '+a.status+'\"></span><span class=\"agent-name\">'+esc(a.name)+'</span></div>';\n h+='<div class=\"agent-id\">'+a.agent_id+'</div>';\n h+='<div class=\"caps\">';for(const c of a.capabilities)h+='<span class=\"cap\">'+esc(c)+'</span>';h+='</div>';\n h+='<div class=\"agent-meta\"><span>任务: '+a.active_task_count+'</span>';\n if(a.avg_delivery_hours!==null)h+='<span>平均交付: '+a.avg_delivery_hours+'h</span>';\n h+='</div>';\n h+='<div class=\"agent-actions\">';\n h+='<select onchange=\"changeStatus(\\\\''+a.agent_id+'\\\\',this.value)\">';\n for(const s of ['IDLE','BUSY','OFFLINE','OOM'])h+='<option'+(s===a.status?' selected':'')+'>'+s+'</option>';\n h+='</select>';\n h+='<button onclick=\"deleteAgent(\\\\''+a.agent_id+'\\\\')\">删除</button>';\n h+='</div></div>';\n }\n h+='</div>';\n el.innerHTML=h;\n }catch(e){el.innerHTML='<div class=\"empty-state\"><p>加载失败: '+e.message+'</p></div>'}\n}\nwindow.showAddAgent=function(){\n const ov=document.createElement('div');ov.className='overlay';ov.id='overlay';\n ov.addEventListener('click',e=>{if(e.target===ov)ov.remove()});\n ov.innerHTML='<div class=\"form-card\"><h3>+ 添加物理节点</h3>'\n +'<div class=\"fg\"><label>节点名称</label><input id=\"aa-name\" placeholder=\"例: 前端老李\"/></div>'\n +'<div class=\"fg\"><label>技能标签</label><input id=\"aa-caps\" placeholder=\"例: UI/UX, 前端开发, 抗压能力强\"/><div class=\"hint\">多个标签用逗号分隔</div></div>'\n +'<div class=\"btn-group\"><button class=\"btn btn-primary\" onclick=\"submitAgent()\">注册节点</button><button class=\"btn btn-ghost\" onclick=\"document.getElementById(\\\\'overlay\\\\').remove()\">取消</button></div></div>';\n document.body.appendChild(ov);\n document.getElementById('aa-name').focus();\n};\nwindow.submitAgent=async function(){\n const name=document.getElementById('aa-name').value.trim();\n const caps=document.getElementById('aa-caps').value.trim();\n if(!name){toast('请输入节点名称',false);return}\n if(!caps){toast('请输入至少一个技能标签',false);return}\n try{\n const r=await fetch(API+'/nodes',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name,capabilities:caps.split(',').map(s=>s.trim()).filter(Boolean)})});\n const d=await r.json();\n if(!r.ok){toast(d.error||'注册失败',false);return}\n toast('节点 '+d.agent_id+' 注册成功!',true);\n document.getElementById('overlay').remove();\n load('fleet');\n }catch{toast('网络错误',false)}\n};\nwindow.changeStatus=async function(id,status){\n try{\n const r=await fetch(API+'/nodes/'+id+'/status',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify({status})});\n if(!r.ok){const d=await r.json();toast(d.error||'更新失败',false);return}\n toast('状态已更新',true);\n }catch{toast('网络错误',false)}\n};\nwindow.deleteAgent=async function(id){\n if(!confirm('确定删除节点 '+id+'?'))return;\n try{\n const r=await fetch(API+'/nodes/'+id,{method:'DELETE'});\n if(!r.ok){const d=await r.json();toast(d.error||'删除失败',false);return}\n toast('节点已删除',true);load('fleet');\n }catch{toast('网络错误',false)}\n};\n\n// ═══════════════════════════════════════════════\n// PIPELINE\n// ═══════════════════════════════════════════════\nfunction tcard(t){\n return '<div class=\"tcard\"><div class=\"tcard-trace\">'+t.trace_id+'</div><div>'+esc(t.todo_description)+'</div><div class=\"tcard-meta\">'+t.assignee_id+' | '+new Date(t.deadline).toLocaleString()+'</div></div>';\n}\nasync function loadPipeline(el){\n el.innerHTML='<div class=\"empty-state\"><p>加载中...</p></div>';\n try{\n try{const ar=await fetch(API+'/nodes/status');const ad=await ar.json();cachedAgents=ad.agents||[]}catch{}\n const r=await fetch(API+'/jobs/active');\n const d=await r.json();\n let h='<div class=\"section-hd\"><h2>异步编排大盘</h2><button class=\"btn btn-primary btn-sm\" onclick=\"showCreateJob()\">+ 创建任务</button></div>';\n if(!d.jobs.length){\n h+='<div class=\"empty-state\" style=\"margin-top:32px\"><div class=\"icon\">📋</div><p>暂无进行中的任务。点击上方「+ 创建任务」,输入需求后 AI 自动规划分发。';\n if(!cachedAgents.length)h+='<br/><br/>⚠️ 需要先在「碳基算力池」中添加物理节点。';\n h+='</p></div>';\n el.innerHTML=h;return;\n }\n for(const j of d.jobs){\n const res=j.tasks.filter(t=>t.status==='RESOLVED').length;\n const tot=j.tasks.length;\n const pct=tot?Math.round(res/tot*100):0;\n const dispatched=j.tasks.filter(t=>t.status==='DISPATCHED'||t.status==='PENDING');\n const overdue=j.tasks.filter(t=>t.status==='OVERDUE');\n const done=j.tasks.filter(t=>t.status==='RESOLVED');\n h+='<div class=\"job-card\"><div class=\"job-hd\"><span class=\"job-title\">'+esc(j.original_prompt)+'</span><span class=\"job-id\">'+j.job_id+'</span></div>';\n h+='<div class=\"pbar-wrap\"><div class=\"pbar-label\">'+res+'/'+tot+' 已完成 ('+pct+'%)</div><div class=\"pbar\"><div class=\"pbar-fill\" style=\"width:'+pct+'%\"></div></div></div>';\n if(pct===100)h+='<div style=\"margin-bottom:12px\"><button class=\"btn btn-green btn-sm\" onclick=\"syncJob(\\\\''+j.job_id+'\\\\')\">聚合并同步到 OpenClaw</button></div>';\n h+='<div class=\"kanban\"><div class=\"lane\"><div class=\"lane-hd y\">已分发 ('+dispatched.length+')</div>'+dispatched.map(tcard).join('')+'</div>';\n h+='<div class=\"lane\"><div class=\"lane-hd r\">已超时 ('+overdue.length+')</div>'+overdue.map(tcard).join('')+'</div>';\n h+='<div class=\"lane\"><div class=\"lane-hd g\">已交付 ('+done.length+')</div>'+done.map(tcard).join('')+'</div></div></div>';\n }\n el.innerHTML=h;\n }catch(e){el.innerHTML='<div class=\"empty-state\"><p>加载失败: '+e.message+'</p></div>'}\n}\n\n// ─── AI Planning Flow ────────────────────────\nwindow.showCreateJob=function(){\n if(!cachedAgents.length){toast('请先在「碳基算力池」中添加至少一个物理节点',false);return}\n currentPlan=null;\n // Pre-select all IDLE agents\n selectedAgentIds=new Set(cachedAgents.filter(a=>a.status==='IDLE').map(a=>a.agent_id));\n const ov=document.createElement('div');ov.className='overlay';ov.id='overlay';\n ov.addEventListener('click',e=>{if(e.target===ov)ov.remove()});\n renderPlanStep1(ov);\n document.body.appendChild(ov);\n};\n\nfunction renderPlanStep1(ov){\n let chipFilter='all';\n function renderChips(filter){\n chipFilter=filter;\n const filtered=filter==='idle'?cachedAgents.filter(a=>a.status==='IDLE'):cachedAgents;\n let ch='<div class=\"chip-bar\">';\n ch+='<span class=\"chip-filter'+(filter==='all'?' active':'')+'\" onclick=\"window._filterChips(\\\\'all\\\\')\">全部</span>';\n ch+='<span class=\"chip-filter'+(filter==='idle'?' active':'')+'\" onclick=\"window._filterChips(\\\\'idle\\\\')\">仅空闲</span>';\n ch+='</div>';\n ch+='<div class=\"agent-chips\">';\n for(const a of filtered){\n const sel=selectedAgentIds.has(a.agent_id);\n ch+='<div class=\"achip'+(sel?' selected':'')+'\" onclick=\"window._toggleChip(\\\\''+a.agent_id+'\\\\')\">';\n ch+='<span class=\"adot '+a.status+'\"></span>';\n ch+=esc(a.name);\n ch+='</div>';\n }\n ch+='</div>';\n return ch;\n }\n window._filterChips=function(f){\n chipFilter=f;\n const el=document.getElementById('agent-chip-area');\n if(el)el.innerHTML=renderChips(f);\n };\n window._toggleChip=function(id){\n if(selectedAgentIds.has(id))selectedAgentIds.delete(id);\n else selectedAgentIds.add(id);\n const el=document.getElementById('agent-chip-area');\n if(el)el.innerHTML=renderChips(chipFilter);\n };\n\n ov.innerHTML='<div class=\"form-card\"><h3>AI 智能规划</h3>'\n +'<div class=\"fg\"><label>输入你的需求</label><textarea id=\"plan-prompt\" rows=\"3\" placeholder=\"例: 完成首页重构,包括导航栏、内容区和页脚的响应式改版\" style=\"font-family:var(--font-sans);font-size:13px\"></textarea></div>'\n +'<div class=\"fg\"><label>选择参与的物理节点 <span style=\"color:var(--text-dim);font-weight:400\">(默认选中空闲节点)</span></label>'\n +'<div id=\"agent-chip-area\">'+renderChips('all')+'</div></div>'\n +'<div class=\"fg\"><label>OpenClaw 回调地址 <span style=\"color:var(--text-dim);font-weight:400\">(可选)</span></label><input id=\"plan-callback\" placeholder=\"https://...\"/></div>'\n +'<div class=\"btn-group\">'\n +'<button class=\"btn btn-primary\" onclick=\"planWithAI()\">AI 规划</button>'\n +'<button class=\"btn btn-ghost\" onclick=\"showManualCreate()\">手动创建</button>'\n +'<button class=\"btn btn-ghost\" onclick=\"document.getElementById(\\\\'overlay\\\\').remove()\">取消</button>'\n +'</div></div>';\n setTimeout(()=>{const ta=document.getElementById('plan-prompt');if(ta)ta.focus()},50);\n};\n\nwindow.planWithAI=async function(){\n const prompt=document.getElementById('plan-prompt').value.trim();\n const callback=document.getElementById('plan-callback')?.value.trim()||'';\n if(!prompt){toast('请输入需求描述',false);return}\n if(selectedAgentIds.size===0){toast('请至少选择一个物理节点',false);return}\n\n const ov=document.getElementById('overlay');\n const fc=ov.querySelector('.form-card');\n fc.innerHTML='<h3>AI 智能规划</h3><div class=\"spinner-wrap\"><div class=\"spinner\"></div><div class=\"spinner-text\">AI 正在分析需求、匹配节点、生成话术...</div></div>';\n\n try{\n const r=await fetch(API+'/jobs/plan',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({prompt,agent_ids:Array.from(selectedAgentIds)})});\n const d=await r.json();\n if(!r.ok){\n toast(d.error||'规划失败',false);\n renderPlanStep1(ov);\n return;\n }\n currentPlan={...d,openclaw_callback:callback};\n renderPlanStep2(ov);\n }catch(e){\n toast('网络错误: '+e.message,false);\n renderPlanStep1(ov);\n }\n};\n\nfunction renderPlanStep2(ov){\n const p=currentPlan;\n let h='<h3>规划预览</h3>';\n h+='<div style=\"font-size:12px;color:var(--text-dim);margin-bottom:14px\">需求: '+esc(p.original_prompt)+'</div>';\n for(let i=0;i<p.planned_tasks.length;i++){\n const t=p.planned_tasks[i];\n const agent=cachedAgents.find(a=>a.agent_id===t.assignee_id);\n const status=agent?agent.status:'IDLE';\n h+='<div class=\"plan-card\">';\n h+='<div class=\"plan-card-hd\"><span class=\"adot '+status+'\"></span><span class=\"plan-card-agent\">'+esc(t.assignee_name)+'</span><span class=\"plan-card-agentid\">'+t.assignee_id+'</span></div>';\n h+='<div class=\"plan-card-desc\">'+esc(t.todo_description)+'</div>';\n h+='<div class=\"briefing-box\"><div class=\"briefing-label\">话术 (可直接发给 TA)</div><button class=\"briefing-copy\" onclick=\"window._copyBriefing('+i+')\">复制</button>'+esc(t.briefing)+'</div>';\n h+='<div class=\"plan-card-dl\">截止: '+new Date(t.deadline).toLocaleString()+'</div>';\n h+='</div>';\n }\n h+='<div class=\"btn-group\">';\n h+='<button class=\"btn btn-green\" onclick=\"dispatchPlan()\">确认分发</button>';\n h+='<button class=\"btn btn-ghost\" onclick=\"renderPlanStep1(document.getElementById(\\\\'overlay\\\\'))\">重新规划</button>';\n h+='<button class=\"btn btn-ghost\" onclick=\"document.getElementById(\\\\'overlay\\\\').remove()\">取消</button>';\n h+='</div>';\n ov.querySelector('.form-card').innerHTML=h;\n}\nwindow.renderPlanStep1=renderPlanStep1;\n\nwindow._copyBriefing=function(i){\n const text=currentPlan.planned_tasks[i].briefing;\n navigator.clipboard.writeText(text).then(()=>toast('话术已复制',true)).catch(()=>toast('复制失败',false));\n};\n\nwindow.dispatchPlan=async function(){\n const p=currentPlan;\n const tasks=p.planned_tasks.map(t=>({assignee_id:t.assignee_id,todo_description:t.todo_description,deadline:t.deadline}));\n try{\n const r=await fetch(API+'/jobs/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({original_prompt:p.original_prompt,openclaw_callback:p.openclaw_callback||'',tasks})});\n const d=await r.json();\n if(!r.ok){toast(d.error||'分发失败',false);return}\n toast('任务已分发!Job: '+d.job_id,true);\n document.getElementById('overlay').remove();\n load('pipeline');\n }catch{toast('网络错误',false)}\n};\n\n// ─── Manual creation (fallback) ──────────────\nwindow.showManualCreate=function(){\n const ov=document.getElementById('overlay');\n const optionsHtml=cachedAgents.map(a=>'<option value=\"'+a.agent_id+'\">'+esc(a.name)+' ('+a.agent_id+')</option>').join('');\n const tomorrow=new Date(Date.now()+86400000).toISOString().slice(0,16);\n ov.querySelector('.form-card').innerHTML='<h3>手动创建任务</h3>'\n +'<div class=\"fg\"><label>任务描述 (原始需求)</label><input id=\"cj-prompt\" placeholder=\"例: 完成首页改版\"/></div>'\n +'<div class=\"fg\"><label>OpenClaw 回调地址 (可选)</label><input id=\"cj-callback\" placeholder=\"https://...\"/></div>'\n +'<div style=\"margin-bottom:10px;display:flex;justify-content:space-between;align-items:center\"><label style=\"font-size:13px;font-weight:600;color:var(--text)\">子任务列表</label><button class=\"btn btn-ghost btn-sm\" onclick=\"addTaskRow()\">+ 添加子任务</button></div>'\n +'<div id=\"task-rows\"><div class=\"task-row\"><div class=\"fg\"><label>指派节点</label><select class=\"tr-agent\">'+optionsHtml+'</select></div><div class=\"fg\"><label>任务描述</label><input class=\"tr-desc\" placeholder=\"具体要做什么...\"/></div><div class=\"fg\"><label>截止时间</label><input class=\"tr-deadline\" type=\"datetime-local\" value=\"'+tomorrow+'\"/></div></div></div>'\n +'<div class=\"btn-group\"><button class=\"btn btn-primary\" onclick=\"submitJob()\">创建并分发</button><button class=\"btn btn-ghost\" onclick=\"renderPlanStep1(document.getElementById(\\\\'overlay\\\\'))\">返回 AI 规划</button><button class=\"btn btn-ghost\" onclick=\"document.getElementById(\\\\'overlay\\\\').remove()\">取消</button></div>';\n};\nwindow.addTaskRow=function(){\n const optionsHtml=cachedAgents.map(a=>'<option value=\"'+a.agent_id+'\">'+esc(a.name)+' ('+a.agent_id+')</option>').join('');\n const tomorrow=new Date(Date.now()+86400000).toISOString().slice(0,16);\n const row=document.createElement('div');row.className='task-row';\n row.innerHTML='<button class=\"remove-task\" onclick=\"this.parentElement.remove()\">×</button>'\n +'<div class=\"fg\"><label>指派节点</label><select class=\"tr-agent\">'+optionsHtml+'</select></div>'\n +'<div class=\"fg\"><label>任务描述</label><input class=\"tr-desc\" placeholder=\"具体要做什么...\"/></div>'\n +'<div class=\"fg\"><label>截止时间</label><input class=\"tr-deadline\" type=\"datetime-local\" value=\"'+tomorrow+'\"/></div>';\n document.getElementById('task-rows').appendChild(row);\n};\nwindow.submitJob=async function(){\n const prompt=document.getElementById('cj-prompt').value.trim();\n const callback=document.getElementById('cj-callback').value.trim();\n if(!prompt){toast('请输入任务描述',false);return}\n const rows=document.querySelectorAll('.task-row');\n const tasks=[];\n for(const row of rows){\n const aid=row.querySelector('.tr-agent').value;\n const desc=row.querySelector('.tr-desc').value.trim();\n const dl=row.querySelector('.tr-deadline').value;\n if(!desc){toast('每个子任务都需要填写描述',false);return}\n tasks.push({assignee_id:aid,todo_description:desc,deadline:new Date(dl).toISOString()});\n }\n if(!tasks.length){toast('至少添加一个子任务',false);return}\n try{\n const r=await fetch(API+'/jobs/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({original_prompt:prompt,openclaw_callback:callback,tasks})});\n const d=await r.json();\n if(!r.ok){toast(d.error||'创建失败',false);return}\n toast('任务已创建并分发!Job: '+d.job_id,true);\n document.getElementById('overlay').remove();\n load('pipeline');\n }catch{toast('网络错误',false)}\n};\nwindow.syncJob=async function(jobId){\n try{\n const r=await fetch(API+'/jobs/'+jobId+'/sync',{method:'POST'});\n const d=await r.json();\n if(!r.ok){toast(d.error||'同步失败',false);return}\n toast('聚合完成!'+d.sync.message,true);\n }catch{toast('网络错误',false)}\n};\n\n// ═══════════════════════════════════════════════\n// TERMINAL\n// ═══════════════════════════════════════════════\nfunction loadTerminal(el){\n el.innerHTML='<div class=\"section-hd\"><h2>I/O 交付终端</h2></div>'\n +'<div class=\"form-card\" style=\"margin-top:12px\"><h3>> 提交物理节点产出</h3>'\n +'<div class=\"fg\"><label>Trace ID (追踪码)</label><input id=\"t-tid\" placeholder=\"例: TK-9527\"/></div>'\n +'<div class=\"fg\"><label>交付载荷</label><textarea id=\"t-payload\" placeholder=\"粘贴交付物内容、工作汇报或代码...\"></textarea></div>'\n +'<div class=\"btn-group\"><button class=\"btn btn-primary\" onclick=\"doResume()\">提交并恢复 (Resume)</button><button class=\"btn btn-danger\" onclick=\"doReject()\">打回重做 (Reject)</button></div></div>';\n}\nwindow.doResume=async function(){\n const tid=document.getElementById('t-tid').value.trim();\n const payload=document.getElementById('t-payload').value.trim();\n if(!tid){toast('请输入 Trace ID',false);return}\n if(!payload){toast('请输入交付载荷',false);return}\n try{\n const r=await fetch(API+'/tasks/resume',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({trace_id:tid,result_data:{text:payload}})});\n const d=await r.json();\n if(!r.ok){toast(d.error||'提交失败',false);return}\n toast(d.job_complete?'任务已交付!Job 已全部完成,可以聚合同步。':'任务 '+tid+' 已交付。',true);\n document.getElementById('t-tid').value='';document.getElementById('t-payload').value='';\n }catch{toast('网络错误',false)}\n};\nwindow.doReject=async function(){\n const tid=document.getElementById('t-tid').value.trim();\n if(!tid){toast('请输入 Trace ID',false);return}\n try{\n const r=await fetch(API+'/tasks/reject',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({trace_id:tid})});\n const d=await r.json();\n if(!r.ok){toast(d.error||'操作失败',false);return}\n toast('任务 '+tid+' 已打回,截止时间已延长 24 小时。',true);\n document.getElementById('t-tid').value='';document.getElementById('t-payload').value='';\n }catch{toast('网络错误',false)}\n};\n\n// ═══════════════════════════════════════════════\n// TABS & INIT\n// ═══════════════════════════════════════════════\nconst tabs=document.querySelectorAll('.tab');\nconst panels=document.querySelectorAll('.panel');\nfunction load(id){\n const el=document.getElementById(id);\n if(id==='fleet')loadFleet(el);\n else if(id==='pipeline')loadPipeline(el);\n else if(id==='terminal')loadTerminal(el);\n}\ntabs.forEach(t=>t.addEventListener('click',()=>{\n tabs.forEach(x=>x.classList.remove('active'));\n panels.forEach(x=>x.classList.remove('active'));\n t.classList.add('active');\n const id=t.dataset.panel;\n document.getElementById(id).classList.add('active');\n load(id);\n}));\nload('fleet');\nsetInterval(()=>{\n const a=document.querySelector('.tab.active');\n if(a&&a.dataset.panel!=='terminal')load(a.dataset.panel);\n},15000);\n</script>\n</body>\n</html>`;\n}\n"],"mappings":"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vBR,SAAS,mBAA2B;AACzC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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","names":["db","db","Router","db","db","db","db","db","db","router","Router","Router","db","router","Router","Router","db","router","Router","db","db","confirm"]}
|