@humanclaw/humanclaw 1.1.4 → 1.2.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 CHANGED
@@ -35,12 +35,13 @@ function getDb(dbPath) {
35
35
  function initSchema(db2) {
36
36
  db2.exec(`
37
37
  CREATE TABLE IF NOT EXISTS agents (
38
- agent_id TEXT PRIMARY KEY,
39
- name TEXT NOT NULL,
38
+ agent_id TEXT PRIMARY KEY,
39
+ name TEXT NOT NULL,
40
40
  capabilities TEXT NOT NULL DEFAULT '[]',
41
- status TEXT NOT NULL DEFAULT 'IDLE'
42
- CHECK (status IN ('IDLE', 'BUSY', 'OFFLINE', 'OOM')),
43
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
41
+ relationship TEXT NOT NULL DEFAULT '',
42
+ status TEXT NOT NULL DEFAULT 'IDLE'
43
+ CHECK (status IN ('IDLE', 'BUSY', 'OFFLINE', 'OOM')),
44
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
44
45
  );
45
46
 
46
47
  CREATE TABLE IF NOT EXISTS jobs (
@@ -73,6 +74,10 @@ function initSchema(db2) {
73
74
  value TEXT NOT NULL
74
75
  );
75
76
  `);
77
+ const cols = db2.prepare("PRAGMA table_info(agents)").all();
78
+ if (!cols.some((c) => c.name === "relationship")) {
79
+ db2.exec(`ALTER TABLE agents ADD COLUMN relationship TEXT NOT NULL DEFAULT ''`);
80
+ }
76
81
  }
77
82
 
78
83
  // src/routes/nodes.ts
@@ -89,12 +94,13 @@ function createAgent(agent, db2) {
89
94
  const conn = db2 ?? getDb();
90
95
  const now = (/* @__PURE__ */ new Date()).toISOString();
91
96
  conn.prepare(
92
- `INSERT INTO agents (agent_id, name, capabilities, status, created_at)
93
- VALUES (?, ?, ?, ?, ?)`
97
+ `INSERT INTO agents (agent_id, name, capabilities, relationship, status, created_at)
98
+ VALUES (?, ?, ?, ?, ?, ?)`
94
99
  ).run(
95
100
  agent.agent_id,
96
101
  agent.name,
97
102
  JSON.stringify(agent.capabilities),
103
+ agent.relationship || "",
98
104
  agent.status,
99
105
  now
100
106
  );
@@ -167,7 +173,7 @@ router.get("/status", (_req, res) => {
167
173
  });
168
174
  });
169
175
  router.post("/", (req, res) => {
170
- const { name, capabilities, status } = req.body;
176
+ const { name, capabilities, relationship, status } = req.body;
171
177
  if (!name || !Array.isArray(capabilities)) {
172
178
  res.status(400).json({ error: "name and capabilities[] are required" });
173
179
  return;
@@ -176,6 +182,7 @@ router.post("/", (req, res) => {
176
182
  agent_id: generateId("emp"),
177
183
  name,
178
184
  capabilities,
185
+ relationship: relationship || "",
179
186
  status: status ?? "IDLE"
180
187
  });
181
188
  res.status(201).json(agent);
@@ -536,7 +543,8 @@ function buildUserPrompt(prompt, agents) {
536
543
  const agentList = agents.map((a) => {
537
544
  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";
538
545
  const speed = a.avg_delivery_hours !== null ? `\u5E73\u5747\u4EA4\u4ED8\u65F6\u95F4 ${a.avg_delivery_hours}h` : "\u6682\u65E0\u5386\u53F2\u6570\u636E";
539
- return `- ${a.name} (ID: ${a.agent_id}) \u6280\u80FD: [${a.capabilities.join(", ")}] ${load} ${speed}`;
546
+ const rel = a.relationship ? ` \u5173\u7CFB: ${a.relationship}` : "";
547
+ return `- ${a.name} (ID: ${a.agent_id}) \u6280\u80FD: [${a.capabilities.join(", ")}]${rel} ${load} ${speed}`;
540
548
  }).join("\n");
541
549
  return `\u5F53\u524D\u65F6\u95F4: ${now.toISOString()}
542
550
 
@@ -738,6 +746,61 @@ function rejectTask(traceId, newDeadline, db2) {
738
746
  return getTask(traceId, conn);
739
747
  }
740
748
 
749
+ // src/services/simulator.ts
750
+ function buildSimulatePrompt(agentName, relationship, capabilities, taskDescription, deadline) {
751
+ return `\u4F60\u73B0\u5728\u626E\u6F14\u4E00\u4E2A\u540D\u53EB\u300C${agentName}\u300D\u7684\u4EBA\uFF0C\u4F60\u7684\u6280\u80FD\u6807\u7B7E\u662F [${capabilities.join(", ")}]\u3002
752
+ ${relationship ? `\u4F60\u548C\u5E03\u7F6E\u4EFB\u52A1\u7684\u4EBA\u7684\u5173\u7CFB\u662F\uFF1A${relationship}\u3002` : ""}
753
+
754
+ \u4F60\u6536\u5230\u4E86\u4E00\u4E2A\u4EFB\u52A1\uFF1A
755
+ ${taskDescription}
756
+
757
+ \u622A\u6B62\u65F6\u95F4\uFF1A${new Date(deadline).toLocaleString("zh-CN")}
758
+
759
+ \u8BF7\u7AD9\u5728\u300C${agentName}\u300D\u7684\u89C6\u89D2\uFF0C\u7528\u8FD9\u4E2A\u4EBA\u7269\u81EA\u7136\u7684\u8BED\u6C14\u548C\u53E3\u543B\uFF0C\u5199\u4E00\u6BB5\u4EFB\u52A1\u4EA4\u4ED8\u6C47\u62A5\u3002
760
+ \u8981\u6C42\uFF1A
761
+ 1. \u5185\u5BB9\u8981\u8D34\u5408\u4EFB\u52A1\u63CF\u8FF0\uFF0C\u4F53\u73B0\u4E13\u4E1A\u80FD\u529B
762
+ 2. \u8BED\u6C14\u7B26\u5408\u4EBA\u7269\u8EAB\u4EFD${relationship ? "\u548C\u4E0E\u4E0A\u7EA7\u7684\u5173\u7CFB" : ""}
763
+ 3. \u6C47\u62A5\u5185\u5BB9\u5177\u4F53\u3001\u6709\u7EC6\u8282\uFF0C\u5305\u542B\u505A\u4E86\u4EC0\u4E48\u3001\u9047\u5230\u4E86\u4EC0\u4E48\u95EE\u9898\u3001\u6700\u7EC8\u7ED3\u679C\u5982\u4F55
764
+ 4. \u5B57\u6570 200-400 \u5B57
765
+ 5. \u76F4\u63A5\u8F93\u51FA\u6C47\u62A5\u5185\u5BB9\uFF0C\u4E0D\u8981\u52A0\u4EFB\u4F55\u683C\u5F0F\u524D\u7F00\u6216\u8BF4\u660E`;
766
+ }
767
+ async function simulateDelivery(traceId, provider, db2) {
768
+ const conn = db2 ?? getDb();
769
+ const task = getTask(traceId, conn);
770
+ if (!task) {
771
+ throw new Error(`Task not found: ${traceId}`);
772
+ }
773
+ const agent = getAgent(task.assignee_id, conn);
774
+ if (!agent) {
775
+ throw new Error(`Agent not found: ${task.assignee_id}`);
776
+ }
777
+ const llm = provider ?? createLlmProvider();
778
+ const response = await llm.complete({
779
+ messages: [
780
+ {
781
+ role: "system",
782
+ content: "\u4F60\u662F\u4E00\u4E2A\u89D2\u8272\u626E\u6F14\u4E13\u5BB6\uFF0C\u64C5\u957F\u6A21\u62DF\u4E0D\u540C\u4EBA\u7269\u7684\u8BED\u6C14\u548C\u6C47\u62A5\u98CE\u683C\u3002"
783
+ },
784
+ {
785
+ role: "user",
786
+ content: buildSimulatePrompt(
787
+ agent.name,
788
+ agent.relationship,
789
+ agent.capabilities,
790
+ task.todo_description,
791
+ task.deadline
792
+ )
793
+ }
794
+ ],
795
+ temperature: 0.7,
796
+ max_tokens: 1024
797
+ });
798
+ return {
799
+ trace_id: traceId,
800
+ simulated_delivery: response.content
801
+ };
802
+ }
803
+
741
804
  // src/routes/tasks.ts
742
805
  var router3 = Router3();
743
806
  router3.post("/resume", (req, res) => {
@@ -776,13 +839,72 @@ router3.post("/reject", (req, res) => {
776
839
  res.status(400).json({ error: message });
777
840
  }
778
841
  });
842
+ router3.post("/simulate", async (req, res) => {
843
+ const { trace_id } = req.body;
844
+ if (!trace_id) {
845
+ res.status(400).json({ error: "trace_id is required" });
846
+ return;
847
+ }
848
+ try {
849
+ const result = await simulateDelivery(trace_id);
850
+ res.json(result);
851
+ } catch (error) {
852
+ const message = error instanceof Error ? error.message : "Unknown error";
853
+ const status = message.includes("API Key") || message.includes("API key") ? 503 : 400;
854
+ res.status(status).json({ error: message });
855
+ }
856
+ });
779
857
  var tasks_default = router3;
780
858
 
781
859
  // src/routes/sync.ts
782
860
  import { Router as Router4 } from "express";
783
861
 
784
- // src/services/aggregation.ts
785
- function aggregateJob(jobId, db2) {
862
+ // src/services/reviewer.ts
863
+ function buildReviewSystemPrompt() {
864
+ return `\u4F60\u662F HumanClaw \u4EA4\u4ED8\u5BA1\u67E5\u5458\u3002\u4F60\u7684\u5DE5\u4F5C\u662F\u5BA1\u67E5\u78B3\u57FA\u8282\u70B9\u63D0\u4EA4\u7684\u6240\u6709\u4EFB\u52A1\u4EA4\u4ED8\u7269\uFF0C\u751F\u6210\u6574\u4F53\u5BA1\u67E5\u62A5\u544A\u3002
865
+
866
+ \u5BA1\u67E5\u8981\u70B9\uFF1A
867
+ 1. \u68C0\u67E5\u6BCF\u4E2A\u4EA4\u4ED8\u7269\u662F\u5426\u6EE1\u8DB3\u539F\u59CB\u4EFB\u52A1\u8981\u6C42
868
+ 2. \u5982\u679C\u4EA4\u4ED8\u7269\u5305\u542B GitHub URL\uFF08PR\u3001commit\u3001issue \u7B49\uFF09\uFF0C\u5206\u6790\u5176\u5185\u5BB9\u548C\u8D28\u91CF
869
+ 3. \u6307\u51FA\u6F5C\u5728\u95EE\u9898\u6216\u6539\u8FDB\u5EFA\u8BAE
870
+ 4. \u7ED9\u51FA\u6574\u4F53\u5B8C\u6210\u5EA6\u8BC4\u5206\uFF081-10\uFF09\u548C\u603B\u7ED3
871
+
872
+ \u8F93\u51FA\u683C\u5F0F\uFF1A
873
+ - \u4F7F\u7528 Markdown \u683C\u5F0F
874
+ - \u5148\u7ED9\u603B\u7ED3\u8BC4\u5206\uFF0C\u518D\u9010\u4E2A\u5206\u6790\u6BCF\u4E2A\u5B50\u4EFB\u52A1\u7684\u4EA4\u4ED8\u8D28\u91CF`;
875
+ }
876
+ function buildReviewUserPrompt(originalPrompt, tasks) {
877
+ let prompt = `\u539F\u59CB\u9700\u6C42: ${originalPrompt}
878
+
879
+ `;
880
+ for (let i = 0; i < tasks.length; i++) {
881
+ const t = tasks[i];
882
+ let resultText = "";
883
+ if (t.result_data) {
884
+ if (typeof t.result_data === "string") {
885
+ resultText = t.result_data;
886
+ } else if (typeof t.result_data === "object") {
887
+ const rd = t.result_data;
888
+ resultText = rd.text || JSON.stringify(rd, null, 2);
889
+ } else {
890
+ resultText = String(t.result_data);
891
+ }
892
+ }
893
+ prompt += `--- \u5B50\u4EFB\u52A1 ${i + 1} ---
894
+ `;
895
+ prompt += `\u6267\u884C\u8005: ${t.assignee_id}
896
+ `;
897
+ prompt += `\u4EFB\u52A1: ${t.todo_description}
898
+ `;
899
+ prompt += `\u4EA4\u4ED8\u7269:
900
+ ${resultText}
901
+
902
+ `;
903
+ }
904
+ prompt += "\u8BF7\u5BA1\u67E5\u4EE5\u4E0A\u6240\u6709\u4EA4\u4ED8\u7269\uFF0C\u751F\u6210\u5BA1\u67E5\u62A5\u544A\u3002";
905
+ return prompt;
906
+ }
907
+ async function reviewJob(jobId, provider, db2) {
786
908
  const conn = db2 ?? getDb();
787
909
  const job = getJobWithTasks(jobId, conn);
788
910
  if (!job) {
@@ -791,62 +913,47 @@ function aggregateJob(jobId, db2) {
791
913
  if (!isJobComplete(jobId, conn)) {
792
914
  const resolved = job.tasks.filter((t) => t.status === "RESOLVED").length;
793
915
  throw new Error(
794
- `Job not complete: ${resolved}/${job.tasks.length} tasks resolved`
916
+ `Job \u5C1A\u672A\u5168\u90E8\u5B8C\u6210: ${resolved}/${job.tasks.length} \u4E2A\u4EFB\u52A1\u5DF2\u4EA4\u4ED8`
795
917
  );
796
918
  }
919
+ const llm = provider ?? createLlmProvider();
920
+ const response = await llm.complete({
921
+ messages: [
922
+ { role: "system", content: buildReviewSystemPrompt() },
923
+ {
924
+ role: "user",
925
+ content: buildReviewUserPrompt(
926
+ job.original_prompt,
927
+ job.tasks.map((t) => ({
928
+ assignee_id: t.assignee_id,
929
+ todo_description: t.todo_description,
930
+ result_data: t.result_data
931
+ }))
932
+ )
933
+ }
934
+ ],
935
+ temperature: 0.3,
936
+ max_tokens: 4096
937
+ });
797
938
  return {
798
- job_id: job.job_id,
939
+ job_id: jobId,
799
940
  original_prompt: job.original_prompt,
800
- openclaw_callback: job.openclaw_callback,
801
- results: job.tasks.map((t) => ({
802
- trace_id: t.trace_id,
803
- assignee_id: t.assignee_id,
804
- todo_description: t.todo_description,
805
- result_data: t.result_data
806
- })),
807
- aggregated_at: (/* @__PURE__ */ new Date()).toISOString()
941
+ review: response.content,
942
+ reviewed_at: (/* @__PURE__ */ new Date()).toISOString()
808
943
  };
809
944
  }
810
- async function syncToOpenClaw(aggregation) {
811
- if (!aggregation.openclaw_callback) {
812
- return {
813
- success: true,
814
- message: "No OpenClaw callback configured, skipping sync"
815
- };
816
- }
817
- try {
818
- const response = await fetch(aggregation.openclaw_callback, {
819
- method: "POST",
820
- headers: { "Content-Type": "application/json" },
821
- body: JSON.stringify(aggregation)
822
- });
823
- if (!response.ok) {
824
- return {
825
- success: false,
826
- message: `OpenClaw sync failed: ${response.status} ${response.statusText}`
827
- };
828
- }
829
- return { success: true, message: "Synced to OpenClaw successfully" };
830
- } catch (error) {
831
- const message = error instanceof Error ? error.message : "Unknown error";
832
- return { success: false, message: `OpenClaw sync error: ${message}` };
833
- }
834
- }
835
945
 
836
946
  // src/routes/sync.ts
837
947
  var router4 = Router4();
838
- router4.post("/:job_id/sync", async (req, res) => {
948
+ router4.post("/:job_id/review", async (req, res) => {
839
949
  const { job_id } = req.params;
840
950
  try {
841
- const aggregation = aggregateJob(job_id);
842
- const syncResult = await syncToOpenClaw(aggregation);
843
- res.json({
844
- aggregation,
845
- sync: syncResult
846
- });
951
+ const result = await reviewJob(job_id);
952
+ res.json(result);
847
953
  } catch (error) {
848
954
  const message = error instanceof Error ? error.message : "Unknown error";
849
- res.status(400).json({ error: message });
955
+ const status = message.includes("API Key") || message.includes("API key") ? 503 : 400;
956
+ res.status(status).json({ error: message });
850
957
  }
851
958
  });
852
959
  var sync_default = router4;
@@ -1104,6 +1211,7 @@ async function loadFleet(el){
1104
1211
  for(const a of d.agents){
1105
1212
  h+='<div class="card"><div class="agent-header"><span class="dot '+a.status+'"></span><span class="agent-name">'+esc(a.name)+'</span></div>';
1106
1213
  h+='<div class="agent-id">'+a.agent_id+'</div>';
1214
+ if(a.relationship)h+='<div style="font-size:11px;color:var(--purple);margin-bottom:4px">&#128101; '+esc(a.relationship)+'</div>';
1107
1215
  h+='<div class="caps">';for(const c of a.capabilities)h+='<span class="cap">'+esc(c)+'</span>';h+='</div>';
1108
1216
  h+='<div class="agent-meta"><span>\u4EFB\u52A1: '+a.active_task_count+'</span>';
1109
1217
  if(a.avg_delivery_hours!==null)h+='<span>\u5E73\u5747\u4EA4\u4ED8: '+a.avg_delivery_hours+'h</span>';
@@ -1125,6 +1233,7 @@ window.showAddAgent=function(){
1125
1233
  ov.innerHTML='<div class="form-card"><h3>+ \u6DFB\u52A0\u78B3\u57FA\u8282\u70B9</h3>'
1126
1234
  +'<div class="fg"><label>\u8282\u70B9\u540D\u79F0</label><input id="aa-name" placeholder="\u4F8B: \u524D\u7AEF\u8001\u674E"/></div>'
1127
1235
  +'<div class="fg"><label>\u6280\u80FD\u6807\u7B7E</label><input id="aa-caps" placeholder="\u4F8B: UI/UX, \u524D\u7AEF\u5F00\u53D1, \u6297\u538B\u80FD\u529B\u5F3A"/><div class="hint">\u591A\u4E2A\u6807\u7B7E\u7528\u9017\u53F7\u5206\u9694</div></div>'
1236
+ +'<div class="fg"><label>\u4E0E\u4F60\u7684\u5173\u7CFB <span style="color:var(--text-dim);font-weight:400">(\u53EF\u9009)</span></label><input id="aa-rel" placeholder="\u4F8B: \u76F4\u5C5E\u4E0B\u5C5E / \u5B9E\u4E60\u751F / \u5916\u5305\u540C\u4E8B / \u4E49\u5F1F"/><div class="hint">\u63CF\u8FF0\u6B64\u4EBA\u4E0E\u4F60\u7684\u5173\u7CFB\uFF0CAI \u89C4\u5212\u548C\u6A21\u62DF\u4EA4\u4ED8\u65F6\u4F1A\u53C2\u8003</div></div>'
1128
1237
  +'<div class="btn-group"><button class="btn btn-primary" onclick="submitAgent()">\u6CE8\u518C\u8282\u70B9</button><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button></div></div>';
1129
1238
  document.body.appendChild(ov);
1130
1239
  document.getElementById('aa-name').focus();
@@ -1132,10 +1241,11 @@ window.showAddAgent=function(){
1132
1241
  window.submitAgent=async function(){
1133
1242
  const name=document.getElementById('aa-name').value.trim();
1134
1243
  const caps=document.getElementById('aa-caps').value.trim();
1244
+ const rel=document.getElementById('aa-rel').value.trim();
1135
1245
  if(!name){toast('\u8BF7\u8F93\u5165\u8282\u70B9\u540D\u79F0',false);return}
1136
1246
  if(!caps){toast('\u8BF7\u8F93\u5165\u81F3\u5C11\u4E00\u4E2A\u6280\u80FD\u6807\u7B7E',false);return}
1137
1247
  try{
1138
- 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)})});
1248
+ 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),relationship:rel})});
1139
1249
  const d=await r.json();
1140
1250
  if(!r.ok){toast(d.error||'\u6CE8\u518C\u5931\u8D25',false);return}
1141
1251
  toast('\u8282\u70B9 '+d.agent_id+' \u6CE8\u518C\u6210\u529F\uFF01',true);
@@ -1190,7 +1300,7 @@ async function loadPipeline(el){
1190
1300
  allTasks.push(...j.tasks);
1191
1301
  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>';
1192
1302
  h+='<div class="pbar-wrap"><div class="pbar-label">'+res+'/'+tot+' \u5DF2\u5B8C\u6210 ('+pct+'%)</div><div class="pbar"><div class="pbar-fill" style="width:'+pct+'%"></div></div></div>';
1193
- if(pct===100)h+='<div style="margin-bottom:12px"><button class="btn btn-green btn-sm" onclick="syncJob(\\''+j.job_id+'\\')">\u805A\u5408\u5E76\u540C\u6B65\u5230 OpenClaw</button></div>';
1303
+ if(pct===100)h+='<div style="margin-bottom:12px"><button class="btn btn-green btn-sm" onclick="reviewJob(\\''+j.job_id+'\\')">AI \u805A\u5408\u5BA1\u67E5</button></div>';
1194
1304
  h+='<div class="kanban"><div class="lane"><div class="lane-hd y">\u5DF2\u5206\u53D1 ('+dispatched.length+')</div>'+dispatched.map(tcard).join('')+'</div>';
1195
1305
  h+='<div class="lane"><div class="lane-hd r">\u5DF2\u8D85\u65F6 ('+overdue.length+')</div>'+overdue.map(tcard).join('')+'</div>';
1196
1306
  h+='<div class="lane"><div class="lane-hd g">\u5DF2\u4EA4\u4ED8 ('+done.length+')</div>'+done.map(tcard).join('')+'</div></div></div>';
@@ -1247,7 +1357,6 @@ function renderPlanStep1(ov){
1247
1357
  +'<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>'
1248
1358
  +'<div class="fg"><label>\u9009\u62E9\u53C2\u4E0E\u7684\u78B3\u57FA\u8282\u70B9 <span style="color:var(--text-dim);font-weight:400">(\u9ED8\u8BA4\u9009\u4E2D\u7A7A\u95F2\u8282\u70B9)</span></label>'
1249
1359
  +'<div id="agent-chip-area">'+renderChips('all')+'</div></div>'
1250
- +'<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>'
1251
1360
  +'<div class="btn-group">'
1252
1361
  +'<button class="btn btn-primary" onclick="planWithAI()">AI \u89C4\u5212</button>'
1253
1362
  +'<button class="btn btn-ghost" onclick="showManualCreate()">\u624B\u52A8\u521B\u5EFA</button>'
@@ -1258,7 +1367,6 @@ function renderPlanStep1(ov){
1258
1367
 
1259
1368
  window.planWithAI=async function(){
1260
1369
  const prompt=document.getElementById('plan-prompt').value.trim();
1261
- const callback=document.getElementById('plan-callback')?.value.trim()||'';
1262
1370
  if(!prompt){toast('\u8BF7\u8F93\u5165\u9700\u6C42\u63CF\u8FF0',false);return}
1263
1371
  if(selectedAgentIds.size===0){toast('\u8BF7\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A\u78B3\u57FA\u8282\u70B9',false);return}
1264
1372
 
@@ -1274,7 +1382,7 @@ window.planWithAI=async function(){
1274
1382
  renderPlanStep1(ov);
1275
1383
  return;
1276
1384
  }
1277
- currentPlan={...d,openclaw_callback:callback};
1385
+ currentPlan={...d};
1278
1386
  renderPlanStep2(ov);
1279
1387
  }catch(e){
1280
1388
  toast('\u7F51\u7EDC\u9519\u8BEF: '+e.message,false);
@@ -1294,7 +1402,8 @@ function renderPlanStep2(ov){
1294
1402
  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>';
1295
1403
  h+='<div class="plan-card-desc">'+esc(t.todo_description)+'</div>';
1296
1404
  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>';
1297
- h+='<div class="plan-card-dl">\u622A\u6B62: '+new Date(t.deadline).toLocaleString()+'</div>';
1405
+ const dlVal=t.deadline.slice(0,16);
1406
+ h+='<div class="plan-card-dl"><label style="font-size:10px;color:var(--text-dim);margin-right:6px">\u622A\u6B62\u65F6\u95F4</label><input type="datetime-local" class="plan-dl-input" data-idx="'+i+'" value="'+dlVal+'" style="background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:4px 8px;color:var(--text);font-family:var(--font-mono);font-size:12px;outline:none"/></div>';
1298
1407
  h+='</div>';
1299
1408
  }
1300
1409
  h+='<div class="btn-group">';
@@ -1313,9 +1422,15 @@ window._copyBriefing=function(i){
1313
1422
 
1314
1423
  window.dispatchPlan=async function(){
1315
1424
  const p=currentPlan;
1316
- const tasks=p.planned_tasks.map(t=>({assignee_id:t.assignee_id,todo_description:t.todo_description,deadline:t.deadline}));
1425
+ // Read adjusted deadlines from inputs
1426
+ const dlInputs=document.querySelectorAll('.plan-dl-input');
1427
+ const tasks=p.planned_tasks.map((t,i)=>{
1428
+ const input=dlInputs[i];
1429
+ const dl=input?new Date(input.value).toISOString():t.deadline;
1430
+ return {assignee_id:t.assignee_id,todo_description:t.todo_description,deadline:dl};
1431
+ });
1317
1432
  try{
1318
- 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})});
1433
+ const r=await fetch(API+'/jobs/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({original_prompt:p.original_prompt,openclaw_callback:'',tasks})});
1319
1434
  const d=await r.json();
1320
1435
  if(!r.ok){toast(d.error||'\u5206\u53D1\u5931\u8D25',false);return}
1321
1436
  toast('\u4EFB\u52A1\u5DF2\u5206\u53D1\uFF01Job: '+d.job_id,true);
@@ -1331,7 +1446,6 @@ window.showManualCreate=function(){
1331
1446
  const tomorrow=new Date(Date.now()+86400000).toISOString().slice(0,16);
1332
1447
  ov.querySelector('.form-card').innerHTML='<h3>\u624B\u52A8\u521B\u5EFA\u4EFB\u52A1</h3>'
1333
1448
  +'<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>'
1334
- +'<div class="fg"><label>OpenClaw \u56DE\u8C03\u5730\u5740 (\u53EF\u9009)</label><input id="cj-callback" placeholder="https://..."/></div>'
1335
1449
  +'<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>'
1336
1450
  +'<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>'
1337
1451
  +'<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>';
@@ -1348,7 +1462,6 @@ window.addTaskRow=function(){
1348
1462
  };
1349
1463
  window.submitJob=async function(){
1350
1464
  const prompt=document.getElementById('cj-prompt').value.trim();
1351
- const callback=document.getElementById('cj-callback').value.trim();
1352
1465
  if(!prompt){toast('\u8BF7\u8F93\u5165\u4EFB\u52A1\u63CF\u8FF0',false);return}
1353
1466
  const rows=document.querySelectorAll('.task-row');
1354
1467
  const tasks=[];
@@ -1361,7 +1474,7 @@ window.submitJob=async function(){
1361
1474
  }
1362
1475
  if(!tasks.length){toast('\u81F3\u5C11\u6DFB\u52A0\u4E00\u4E2A\u5B50\u4EFB\u52A1',false);return}
1363
1476
  try{
1364
- const r=await fetch(API+'/jobs/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({original_prompt:prompt,openclaw_callback:callback,tasks})});
1477
+ const r=await fetch(API+'/jobs/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({original_prompt:prompt,openclaw_callback:'',tasks})});
1365
1478
  const d=await r.json();
1366
1479
  if(!r.ok){toast(d.error||'\u521B\u5EFA\u5931\u8D25',false);return}
1367
1480
  toast('\u4EFB\u52A1\u5DF2\u521B\u5EFA\u5E76\u5206\u53D1\uFF01Job: '+d.job_id,true);
@@ -1369,13 +1482,22 @@ window.submitJob=async function(){
1369
1482
  load('pipeline');
1370
1483
  }catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
1371
1484
  };
1372
- window.syncJob=async function(jobId){
1485
+ window.reviewJob=async function(jobId){
1486
+ const ov=document.createElement('div');ov.className='overlay';ov.id='overlay';
1487
+ ov.addEventListener('click',e=>{if(e.target===ov)ov.remove()});
1488
+ ov.innerHTML='<div class="form-card"><h3>AI \u805A\u5408\u5BA1\u67E5</h3><div class="spinner-wrap"><div class="spinner"></div><div class="spinner-text">AI \u6B63\u5728\u5BA1\u67E5\u6240\u6709\u4EA4\u4ED8\u7269...</div></div></div>';
1489
+ document.body.appendChild(ov);
1373
1490
  try{
1374
- const r=await fetch(API+'/jobs/'+jobId+'/sync',{method:'POST'});
1491
+ const r=await fetch(API+'/jobs/'+jobId+'/review',{method:'POST'});
1375
1492
  const d=await r.json();
1376
- if(!r.ok){toast(d.error||'\u540C\u6B65\u5931\u8D25',false);return}
1377
- toast('\u805A\u5408\u5B8C\u6210\uFF01'+d.sync.message,true);
1378
- }catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
1493
+ if(!r.ok){toast(d.error||'\u5BA1\u67E5\u5931\u8D25',false);ov.remove();return}
1494
+ const fc=ov.querySelector('.form-card');
1495
+ fc.innerHTML='<h3>AI \u805A\u5408\u5BA1\u67E5</h3>'
1496
+ +'<div style="font-size:12px;color:var(--text-dim);margin-bottom:12px">\u9700\u6C42: '+esc(d.original_prompt)+'</div>'
1497
+ +'<div class="result-display" style="max-height:400px">'+esc(d.review).replace(/\\n/g,'<br>')+'</div>'
1498
+ +'<div style="font-size:10px;color:var(--text-dim);margin-top:8px;font-family:var(--font-mono)">\u5BA1\u67E5\u65F6\u95F4: '+new Date(d.reviewed_at).toLocaleString()+'</div>'
1499
+ +'<div class="btn-group"><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u5173\u95ED</button></div>';
1500
+ }catch(e){toast('\u7F51\u7EDC\u9519\u8BEF: '+e.message,false);ov.remove()}
1379
1501
  };
1380
1502
 
1381
1503
  // \u2500\u2500\u2500 Task Detail Modal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
@@ -1400,13 +1522,14 @@ window.showTaskDetail=function(traceId){
1400
1522
  if(t.status==='RESOLVED'&&t.result_data){
1401
1523
  // Show result
1402
1524
  let resultText='';
1403
- try{const rd=JSON.parse(t.result_data);resultText=rd.text||JSON.stringify(rd,null,2)}catch{resultText=t.result_data}
1525
+ if(typeof t.result_data==='object'&&t.result_data){resultText=t.result_data.text||JSON.stringify(t.result_data,null,2)}else{resultText=String(t.result_data||'')}
1404
1526
  h+='<div class="detail-row"><div class="detail-label">\u4EA4\u4ED8\u7ED3\u679C</div><div class="result-display">'+esc(resultText)+'</div></div>';
1405
1527
  h+='<div class="btn-group"><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u5173\u95ED</button></div>';
1406
1528
  }else{
1407
1529
  // Input for resume/reject
1408
- h+='<div class="fg" style="margin-top:16px"><label>\u63D0\u4EA4 Human \u4EA4\u4ED8\u7269</label><textarea id="td-payload" rows="4" placeholder="\u7C98\u8D34\u4EA4\u4ED8\u7269\u5185\u5BB9\u3001\u5DE5\u4F5C\u6C47\u62A5\u6216\u4EE3\u7801..."></textarea></div>';
1530
+ h+='<div class="fg" style="margin-top:16px"><label>\u63D0\u4EA4 Human \u4EA4\u4ED8\u7269</label><textarea id="td-payload" rows="4" placeholder="\u7C98\u8D34\u4EA4\u4ED8\u7269\u5185\u5BB9\u3001GitHub PR/Commit URL\u3001\u5DE5\u4F5C\u6C47\u62A5\u7B49..."></textarea><div class="hint">\u652F\u6301\u8D34 GitHub URL\uFF08PR\u3001Commit\u3001Issue\uFF09\uFF0CAI \u5BA1\u67E5\u65F6\u4F1A\u5206\u6790</div></div>';
1409
1531
  h+='<div class="btn-group">';
1532
+ h+='<button class="btn btn-primary btn-sm" onclick="simulateDelivery(\\''+t.trace_id+'\\')">&#129302; \u6A21\u62DF\u4EA4\u4ED8</button>';
1410
1533
  h+='<button class="btn btn-green" onclick="taskResume(\\''+t.trace_id+'\\')">\u63D0\u4EA4\u4EA4\u4ED8 (Resume)</button>';
1411
1534
  h+='<button class="btn btn-danger" onclick="taskReject(\\''+t.trace_id+'\\')">\u6253\u56DE\u91CD\u505A (Reject)</button>';
1412
1535
  h+='<button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button>';
@@ -1442,6 +1565,21 @@ window.taskReject=async function(traceId){
1442
1565
  }catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
1443
1566
  };
1444
1567
 
1568
+ window.simulateDelivery=async function(traceId){
1569
+ const btn=event.target;
1570
+ const origText=btn.innerHTML;
1571
+ btn.disabled=true;btn.innerHTML='&#8987; AI \u751F\u6210\u4E2D...';
1572
+ try{
1573
+ const r=await fetch(API+'/tasks/simulate',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({trace_id:traceId})});
1574
+ const d=await r.json();
1575
+ if(!r.ok){toast(d.error||'\u6A21\u62DF\u5931\u8D25',false);btn.disabled=false;btn.innerHTML=origText;return}
1576
+ const ta=document.getElementById('td-payload');
1577
+ if(ta)ta.value=d.simulated_delivery;
1578
+ toast('\u5DF2\u751F\u6210\u6A21\u62DF\u4EA4\u4ED8\u5185\u5BB9',true);
1579
+ btn.disabled=false;btn.innerHTML=origText;
1580
+ }catch(e){toast('\u7F51\u7EDC\u9519\u8BEF: '+e.message,false);btn.disabled=false;btn.innerHTML=origText}
1581
+ };
1582
+
1445
1583
  // \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
1446
1584
  // TERMINAL
1447
1585
  // \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
@@ -1449,7 +1587,7 @@ function loadTerminal(el){
1449
1587
  el.innerHTML='<div class="section-hd"><h2>I/O \u4EA4\u4ED8\u7EC8\u7AEF</h2></div>'
1450
1588
  +'<div class="form-card" style="margin-top:12px"><h3>> \u63D0\u4EA4\u78B3\u57FA\u8282\u70B9\u4EA7\u51FA</h3>'
1451
1589
  +'<div class="fg"><label>Trace ID (\u8FFD\u8E2A\u7801)</label><input id="t-tid" placeholder="\u4F8B: TK-9527"/></div>'
1452
- +'<div class="fg"><label>\u4EA4\u4ED8\u8F7D\u8377</label><textarea id="t-payload" placeholder="\u7C98\u8D34\u4EA4\u4ED8\u7269\u5185\u5BB9\u3001\u5DE5\u4F5C\u6C47\u62A5\u6216\u4EE3\u7801..."></textarea></div>'
1590
+ +'<div class="fg"><label>\u4EA4\u4ED8\u8F7D\u8377</label><textarea id="t-payload" placeholder="\u7C98\u8D34\u4EA4\u4ED8\u7269\u5185\u5BB9\u3001GitHub PR/Commit URL\u3001\u5DE5\u4F5C\u6C47\u62A5\u7B49..."></textarea><div class="hint">\u652F\u6301\u8D34 GitHub URL\uFF08PR\u3001Commit\u3001Issue\uFF09\uFF0CAI \u5BA1\u67E5\u65F6\u4F1A\u5206\u6790</div></div>'
1453
1591
  +'<div class="btn-group"><button class="btn btn-primary" onclick="doResume()">\u63D0\u4EA4\u5E76\u6062\u590D (Resume)</button><button class="btn btn-danger" onclick="doReject()">\u6253\u56DE\u91CD\u505A (Reject)</button></div></div>';
1454
1592
  }
1455
1593
  window.doResume=async function(){
@@ -1621,10 +1759,20 @@ agentCmd.command("add").description("Register a new carbon-based node").action(a
1621
1759
  process.exit(0);
1622
1760
  }
1623
1761
  const capabilities = capInput.split(",").map((s) => s.trim()).filter(Boolean);
1762
+ const relInput = await p.text({
1763
+ message: "Relationship with you (optional):",
1764
+ placeholder: "e.g. direct report / intern / contractor",
1765
+ defaultValue: ""
1766
+ });
1767
+ if (p.isCancel(relInput)) {
1768
+ p.cancel("Cancelled.");
1769
+ process.exit(0);
1770
+ }
1624
1771
  const agent = createAgent({
1625
1772
  agent_id: generateId("emp"),
1626
1773
  name,
1627
1774
  capabilities,
1775
+ relationship: relInput || "",
1628
1776
  status: "IDLE"
1629
1777
  });
1630
1778
  p.outro(