@wagemule/daemon 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,10 +4,10 @@ Wage Mule local daemon connects local agent runtimes to a Workspace Server.
4
4
 
5
5
  ## Install and start
6
6
 
7
- Use the command shown in the Wage Mule Web machine page. It pins the daemon version and includes the pairing token for the current space:
7
+ Use the command shown in the Wage Mule Web machine page. It uses the latest daemon package and includes the pairing token for the current space:
8
8
 
9
9
  ```bash
10
- npx -y @wagemule/daemon@0.1.3 start --server-url "https://your-server" --api-key "daemon_xxx"
10
+ npx -y @wagemule/daemon@latest start --server-url "https://your-server" --api-key "daemon_xxx"
11
11
  ```
12
12
 
13
13
  The v0.1 daemon runs in the foreground. Keep the terminal open while the machine should stay online.
@@ -26,7 +26,7 @@ wm-daemon start --server-url "https://your-server" --api-key "daemon_xxx"
26
26
 
27
27
  ## Feishu delegation
28
28
 
29
- The npm package depends on the official `@larksuite/cli`, so `npx -y @wagemule/daemon@0.1.3 ...` also installs the `lark-cli` binary. When a platform human enables Feishu delegation for an agent, the daemon injects the official Lark/Feishu CLI skills into that agent workspace and writes a local `.wm/lark-cli` wrapper.
29
+ The npm package depends on the official `@larksuite/cli`, so `npx -y @wagemule/daemon@latest ...` also installs the `lark-cli` binary. When a platform human enables Feishu delegation for an agent, the daemon injects the official Lark/Feishu CLI skills into that agent workspace and writes a local `.wm/lark-cli` wrapper.
30
30
 
31
31
  The wrapper reads the delegated user token from `.wm/feishu/token` at runtime and sets `LARKSUITE_CLI_APP_ID`, `LARKSUITE_CLI_USER_ACCESS_TOKEN`, `LARKSUITE_CLI_DEFAULT_AS=user`, `LARKSUITE_CLI_STRICT_MODE=user`, and `LARKSUITE_CLI_BRAND=feishu`. The token is not embedded in the wrapper. If the token is missing or cleared, `lark-cli` exits with `Feishu token unavailable`.
32
32
 
package/dist/main.cjs CHANGED
@@ -612,7 +612,18 @@ ${userPrompt}`;
612
612
  }
613
613
  async handleServerRequest(request) {
614
614
  if (request.method === "session/request_permission") {
615
- return permissionOutcome(selectPermissionOption(request.params, this.activePermissionMode === "full_access"));
615
+ const runInput = this.activePrompt?.runInput ?? this.startInput;
616
+ if (this.activePermissionMode === "full_access") {
617
+ return permissionOutcome(selectPermissionOption(request.params, true));
618
+ }
619
+ if (!runInput?.requestPermission) return permissionOutcome(selectPermissionOption(request.params, false));
620
+ const decision = await runInput.requestPermission({
621
+ kind: "session",
622
+ title: "Runtime permission requested",
623
+ description: summarizeAcpPermissionParams(request.params),
624
+ metadata: { method: request.method, params: request.params }
625
+ });
626
+ return permissionOutcome(selectPermissionOption(request.params, decision.action === "approve"));
616
627
  }
617
628
  return {};
618
629
  }
@@ -720,6 +731,14 @@ function emit(events, input, event) {
720
731
  events.push(event);
721
732
  input.onEvent?.(event);
722
733
  }
734
+ function summarizeAcpPermissionParams(params) {
735
+ if (!params || typeof params !== "object") return "Runtime requested permission.";
736
+ const record = params;
737
+ const reason = typeof record.reason === "string" ? record.reason : void 0;
738
+ const title = typeof record.title === "string" ? record.title : void 0;
739
+ const command = typeof record.command === "string" ? record.command : void 0;
740
+ return [title, command, reason].filter(Boolean).join("\n") || JSON.stringify(record).slice(0, 500);
741
+ }
723
742
  function modelArgs(args, model, enabled = true) {
724
743
  if (!enabled || !model || model === "default") return args;
725
744
  return [...args, "--model", model];
@@ -1509,15 +1528,41 @@ var CodexAdapter = class {
1509
1528
  }
1510
1529
  }
1511
1530
  async handleServerRequest(request) {
1512
- if (request.method === "item/fileChange/requestApproval") {
1531
+ const runInput = this.activeTurn?.runInput ?? (this.startInput ? this.startInputAsRunInput("") : void 0);
1532
+ if (runInput?.permissionMode === "full_access" || !runInput?.requestPermission) {
1533
+ if (request.method === "item/fileChange/requestApproval") return { decision: "accept" };
1534
+ if (request.method === "item/permissions/requestApproval") return { permissions: {}, scope: "turn" };
1513
1535
  return { decision: "accept" };
1514
1536
  }
1537
+ if (request.method === "item/fileChange/requestApproval") {
1538
+ const decision = await runInput.requestPermission({
1539
+ kind: "file_change",
1540
+ title: "File change approval requested",
1541
+ description: summarizePermissionParams(request.params),
1542
+ metadata: { method: request.method, params: request.params }
1543
+ });
1544
+ return { decision: decision.action === "approve" ? "accept" : "reject" };
1545
+ }
1515
1546
  if (request.method === "item/permissions/requestApproval") {
1516
- return { permissions: {}, scope: "turn" };
1547
+ const decision = await runInput.requestPermission({
1548
+ kind: "permission",
1549
+ title: "Command permission requested",
1550
+ description: summarizePermissionParams(request.params),
1551
+ metadata: { method: request.method, params: request.params }
1552
+ });
1553
+ return decision.action === "approve" ? { permissions: {}, scope: "turn" } : { permissions: null, scope: "turn" };
1517
1554
  }
1518
1555
  return { decision: "accept" };
1519
1556
  }
1520
1557
  };
1558
+ function summarizePermissionParams(params) {
1559
+ if (!params || typeof params !== "object") return "Runtime requested permission.";
1560
+ const record = params;
1561
+ const command = typeof record.command === "string" ? record.command : void 0;
1562
+ const path14 = typeof record.path === "string" ? record.path : void 0;
1563
+ const reason = typeof record.reason === "string" ? record.reason : void 0;
1564
+ return [command, path14, reason].filter(Boolean).join("\n") || JSON.stringify(record).slice(0, 500);
1565
+ }
1521
1566
  function codexPermissionOptions(permissionMode) {
1522
1567
  if (permissionMode === "full_access") {
1523
1568
  return { approvalPolicy: "never", sandbox: "danger-full-access" };
@@ -2608,7 +2653,7 @@ if (fs.existsSync(envPath)) {
2608
2653
 
2609
2654
  const args = process.argv.slice(2);
2610
2655
  const [group, command] = args;
2611
- const help = "Available commands: wm auth whoami; wm message send/check/read/search; wm dm send; wm server info; wm channel members; wm member find; wm task list/claim/unclaim/update/create; wm attachment upload/view; wm profile show/update; wm reminder schedule/list/snooze/update/cancel";
2656
+ const help = "Available commands: wm auth whoami; wm message send/check/read/search; wm dm send; wm server info; wm channel members; wm member find; wm task list/claim/unclaim/update/create; wm attachment upload/view; wm profile show/update; wm reminder schedule/list/snooze/update/cancel; wm inbox feishu-draft create/sent/failed";
2612
2657
 
2613
2658
  function valueAfter(name) {
2614
2659
  const index = args.indexOf(name);
@@ -2620,7 +2665,7 @@ function hasFlag(name) {
2620
2665
  }
2621
2666
 
2622
2667
  function positionalText() {
2623
- const knownOptions = new Set(["--target", "--channel", "--text", "--content", "--attachment-ids", "--query", "--q", "--around", "--status", "--id", "--task-id", "--title", "--description", "--file", "--at", "--every", "--role", "--instructions", "--body", "--to-agent-id", "--timeout", "--feishu-open-id", "--open-id"]);
2668
+ const knownOptions = new Set(["--target", "--channel", "--text", "--content", "--attachment-ids", "--query", "--q", "--around", "--status", "--id", "--task-id", "--title", "--description", "--file", "--at", "--every", "--role", "--instructions", "--body", "--to-agent-id", "--timeout", "--feishu-open-id", "--open-id", "--delegation-id", "--human-id", "--feishu-message-id", "--chat-id", "--sender-name", "--sender-open-id", "--original-text", "--draft-text", "--risk", "--message-id", "--error"]);
2624
2669
  const values = [];
2625
2670
  for (let index = 2; index < args.length; index++) {
2626
2671
  const arg = args[index];
@@ -2755,6 +2800,11 @@ function formatMemberFind(payload) {
2755
2800
  return lines.join("\\n");
2756
2801
  }
2757
2802
 
2803
+ function formatInboxDraft(payload, fallback) {
2804
+ const item = payload.item || {};
2805
+ return \`\${fallback}: \${item.id || "unknown"} status=\${item.status || "unknown"}\`;
2806
+ }
2807
+
2758
2808
  async function main() {
2759
2809
  const agentId = process.env.WM_AGENT_ID || process.env.WM_DAEMON_AGENT_ID;
2760
2810
  if (group === "auth" && command === "whoami") {
@@ -2845,6 +2895,43 @@ async function main() {
2845
2895
  printResult(payload, formatMemberFind(payload));
2846
2896
  return;
2847
2897
  }
2898
+ if (group === "inbox" && command === "feishu-draft") {
2899
+ const action = args[2];
2900
+ if (action === "create") {
2901
+ const draftText = (valueAfter("--draft-text") || await stdin()).trim();
2902
+ const payload = await request("POST", \`/internal/agent/\${agentId}/inbox/feishu-draft\`, {
2903
+ delegation_id: valueAfter("--delegation-id"),
2904
+ human_id: valueAfter("--human-id"),
2905
+ feishu_message_id: valueAfter("--feishu-message-id"),
2906
+ chat_id: valueAfter("--chat-id"),
2907
+ sender_name: valueAfter("--sender-name"),
2908
+ sender_open_id: valueAfter("--sender-open-id"),
2909
+ original_text: valueAfter("--original-text"),
2910
+ draft_text: draftText,
2911
+ risk: valueAfter("--risk"),
2912
+ });
2913
+ printResult(payload, formatInboxDraft(payload, "Feishu draft created"));
2914
+ return;
2915
+ }
2916
+ if (action === "sent") {
2917
+ const inboxId = valueAfter("--id");
2918
+ if (!inboxId) throw new Error("--id is required");
2919
+ const payload = await request("POST", \`/internal/agent/\${agentId}/inbox/feishu-draft/\${encodeURIComponent(inboxId)}/sent\`, {
2920
+ message_id: valueAfter("--message-id"),
2921
+ });
2922
+ printResult(payload, formatInboxDraft(payload, "Feishu draft sent"));
2923
+ return;
2924
+ }
2925
+ if (action === "failed") {
2926
+ const inboxId = valueAfter("--id");
2927
+ if (!inboxId) throw new Error("--id is required");
2928
+ const error = (valueAfter("--error") || await stdin()).trim();
2929
+ if (!error) throw new Error("--error is required");
2930
+ const payload = await request("POST", \`/internal/agent/\${agentId}/inbox/feishu-draft/\${encodeURIComponent(inboxId)}/failed\`, { error });
2931
+ printResult(payload, formatInboxDraft(payload, "Feishu draft failed"));
2932
+ return;
2933
+ }
2934
+ }
2848
2935
  if (group === "task" && command === "list") {
2849
2936
  const status = valueAfter("--status");
2850
2937
  const suffix = status ? \`?status=\${encodeURIComponent(status)}\` : "";
@@ -3801,7 +3888,7 @@ var import_ws = __toESM(require("ws"));
3801
3888
  // package.json
3802
3889
  var package_default = {
3803
3890
  name: "@wagemule/daemon",
3804
- version: "0.1.3",
3891
+ version: "0.1.4",
3805
3892
  private: false,
3806
3893
  description: "Wage Mule local daemon for connecting local agent runtimes to Workspace Server.",
3807
3894
  main: "./dist/main.cjs",
@@ -4891,6 +4978,7 @@ var AgentProcessManager = class {
4891
4978
  stoppedLaunches = /* @__PURE__ */ new Set();
4892
4979
  lastActivities = /* @__PURE__ */ new Map();
4893
4980
  pendingMigrations = /* @__PURE__ */ new Map();
4981
+ pendingPermissions = /* @__PURE__ */ new Map();
4894
4982
  fakeRuntimeChildren = /* @__PURE__ */ new Map();
4895
4983
  feishuDelegationTimers = /* @__PURE__ */ new Map();
4896
4984
  modelDetector;
@@ -4984,6 +5072,8 @@ var AgentProcessManager = class {
4984
5072
  this.reminderCache.snapshot(message.agentId, message.reminders);
4985
5073
  } else if (message.type === "agent:runtime_profile:migration") {
4986
5074
  this.pendingMigrations.set(message.agentId, message.migrationKey);
5075
+ } else if (message.type === "agent:permission_decision") {
5076
+ this.resolvePermissionDecision(message.agentId, message.requestId, message.action, message.metadata);
4987
5077
  }
4988
5078
  }
4989
5079
  startFeishuDelegationSchedule(delegation, config) {
@@ -4994,6 +5084,7 @@ var AgentProcessManager = class {
4994
5084
  });
4995
5085
  if (!delegation.enabled) return;
4996
5086
  const intervalMs = Math.max(6e4, delegation.interval_seconds * 1e3);
5087
+ const lookbackMinutes = Math.min(60, Math.max(10, Math.ceil(delegation.interval_seconds * 2 / 60)));
4997
5088
  const timer = setInterval(() => {
4998
5089
  const wakeMessage = {
4999
5090
  target: "feishu:delegation",
@@ -5006,14 +5097,18 @@ var AgentProcessManager = class {
5006
5097
  "Feishu delegation tick.",
5007
5098
  `Human ID: ${delegation.human_id}`,
5008
5099
  `Delegation ID: ${delegation.id}`,
5009
- "Refresh the Feishu token file if needed, then use the lark skill to check pending Feishu messages.",
5010
- "Classify messages, handle safe requests, and escalate uncertain or high-risk requests through wm dm send --wait-for-reply."
5100
+ `Read Feishu messages from the last ${lookbackMinutes} minutes only. Never send, reply, react, or mutate Feishu during this tick.`,
5101
+ "For each new inbound Feishu message that needs a reply, draft a concise reply and create exactly one inbox item with:",
5102
+ "wm inbox feishu-draft create --delegation-id <delegation_id> --human-id <human_id> --feishu-message-id <message_id> --chat-id <chat_id> --sender-name <name> --sender-open-id <open_id> --original-text <summary> --draft-text <draft>",
5103
+ "Deduplicate by Feishu message_id. If there are no new relevant messages, do nothing.",
5104
+ "Do not call lark-cli im +messages-send or any other Feishu send command unless a later approved inbox task explicitly asks you to send."
5011
5105
  ].join("\n")
5012
5106
  };
5013
5107
  void this.enqueueStart({
5014
5108
  agentId: delegation.delegate_agent_id,
5015
5109
  config: { ...config, feishuDelegationEnabled: true },
5016
- wakeMessage
5110
+ wakeMessage,
5111
+ deliveredMessages: [wakeMessage]
5017
5112
  }).catch((error) => {
5018
5113
  this.options.sendToServer({
5019
5114
  type: "feishu:wake_failed",
@@ -5178,6 +5273,44 @@ var AgentProcessManager = class {
5178
5273
  this.lastActivities.set(msg.agentId, { activity: msg.activity, detail: msg.detail, launchId: msg.launchId, clientSeq: msg.clientSeq });
5179
5274
  this.options.sendToServer({ type: "agent:activity", ...msg });
5180
5275
  }
5276
+ requestRuntimePermission(agentId, launchId, request) {
5277
+ const requestId = `perm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
5278
+ this.log(`permission request agent=${agentId} request=${requestId} kind=${request.kind}`);
5279
+ this.sendActivity({
5280
+ agentId,
5281
+ activity: "working",
5282
+ detail: "Waiting for permission",
5283
+ launchId,
5284
+ entries: [{ kind: "status", text: request.title, timestamp: Date.now() }],
5285
+ pendingInboxCount: this.queuedInboxCount(agentId) + 1
5286
+ });
5287
+ this.options.sendToServer({
5288
+ type: "agent:permission_request",
5289
+ agentId,
5290
+ requestId,
5291
+ launchId,
5292
+ kind: request.kind,
5293
+ title: request.title,
5294
+ description: request.description,
5295
+ metadata: request.metadata
5296
+ });
5297
+ return new Promise((resolve) => {
5298
+ const timeout = setTimeout(() => {
5299
+ this.pendingPermissions.delete(requestId);
5300
+ this.log(`permission timeout agent=${agentId} request=${requestId}`);
5301
+ resolve({ action: "reject", metadata: { reason: "permission timeout" } });
5302
+ }, 10 * 6e4);
5303
+ this.pendingPermissions.set(requestId, { agentId, timeout, resolve });
5304
+ });
5305
+ }
5306
+ resolvePermissionDecision(agentId, requestId, action, metadata) {
5307
+ const pending = this.pendingPermissions.get(requestId);
5308
+ if (!pending || pending.agentId !== agentId) return;
5309
+ clearTimeout(pending.timeout);
5310
+ this.pendingPermissions.delete(requestId);
5311
+ this.log(`permission decision agent=${agentId} request=${requestId} action=${action}`);
5312
+ pending.resolve({ action, metadata });
5313
+ }
5181
5314
  async ensureAgentWorkspace(agentId, config) {
5182
5315
  const running = this.running.get(agentId);
5183
5316
  if (running && !config?.feishuDelegationEnabled) return { workspace: running.workspace };
@@ -5313,6 +5446,7 @@ var AgentProcessManager = class {
5313
5446
 
5314
5447
  [Runtime Profile Migration] Your runtime configuration has been updated. Before handling the above, re-ground yourself in the new context, then run: \`${workspace.wrapperPath} profile migration-ack --key ${migrationKey}\`` : basePrompt;
5315
5448
  const tracker = new ActivityTracker();
5449
+ const requestPermission = (request) => this.requestRuntimePermission(agentId, launchId, request);
5316
5450
  const onEvent = (event) => {
5317
5451
  this.log(`runtime event agent=${agentId} type=${event.type}${event.content ? ` text=${compact(event.content)}` : ""}`);
5318
5452
  const running = this.running.get(agentId);
@@ -5391,7 +5525,8 @@ var AgentProcessManager = class {
5391
5525
  env: runtimeEnv,
5392
5526
  runtimeSessionId: config.sessionId,
5393
5527
  timeoutMs: 18e4,
5394
- onEvent
5528
+ onEvent,
5529
+ requestPermission
5395
5530
  });
5396
5531
  this.log(`runtime spawned agent=${agentId} runtime=${config.runtime} pid=${adapter.pid ?? "-"} persistent=true`);
5397
5532
  } else {
@@ -5418,7 +5553,8 @@ var AgentProcessManager = class {
5418
5553
  runtimeSessionId: config.sessionId,
5419
5554
  timeoutMs: 18e4,
5420
5555
  abortSignal: controller.signal,
5421
- onEvent
5556
+ onEvent,
5557
+ requestPermission
5422
5558
  });
5423
5559
  }
5424
5560
  if (result.runtimeSessionId) {
@@ -5652,6 +5788,7 @@ var AgentProcessManager = class {
5652
5788
  this.idleConfigs.delete(agentId);
5653
5789
  this.startingInboxes.delete(agentId);
5654
5790
  this.runningInboxes.delete(agentId);
5791
+ this.rejectPendingPermissionsForAgent(agentId, "agent stopped");
5655
5792
  const running = this.running.get(agentId);
5656
5793
  if (!running) {
5657
5794
  this.options.sendToServer({ type: "agent:status", agentId, status: "inactive" });
@@ -5679,6 +5816,14 @@ var AgentProcessManager = class {
5679
5816
  new Promise((resolve) => setTimeout(resolve, 5e3))
5680
5817
  ]);
5681
5818
  }
5819
+ rejectPendingPermissionsForAgent(agentId, reason) {
5820
+ for (const [requestId, pending] of this.pendingPermissions) {
5821
+ if (pending.agentId !== agentId) continue;
5822
+ clearTimeout(pending.timeout);
5823
+ this.pendingPermissions.delete(requestId);
5824
+ pending.resolve({ action: "reject", metadata: { reason } });
5825
+ }
5826
+ }
5682
5827
  async runFakeRuntime(input) {
5683
5828
  const { agentId, config, workspace, wakeMessage, abortSignal, launchId } = input;
5684
5829
  let child;