@xiashe/cli 0.1.0 → 0.1.4

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
@@ -18,12 +18,14 @@ xiashe skills list
18
18
  xiashe skills install copywriting --connection MaxClaw-1 --watch
19
19
  xiashe agent issue --name "Local WorkBuddy"
20
20
  xiashe agent connect --token "<token>" --name "Local WorkBuddy"
21
- xiashe agent tasks watch --ack
21
+ xiashe agent run --ack
22
22
  xiashe chat send --chat "New Chat" --content "帮我处理这个文件" --file ./report.pdf --agent "Local WorkBuddy"
23
23
  ```
24
24
 
25
25
  Local state is stored in `~/.xiashe/config.json`.
26
26
 
27
+ `xiashe agent run --ack` is the recommended lightweight receiver for shell-capable Agents. It sends cheap heartbeats and only lists/executes work when the backend reports pending tasks or transfers. Default cadence is about 30s while idle, 10s while work is active, and up to 2 minutes after long idle periods.
28
+
27
29
  Production defaults:
28
30
 
29
31
  - Convex: `https://api.xiashe.chat`
package/bin/xiashe.mjs CHANGED
@@ -64,10 +64,13 @@ Usage:
64
64
  ${c} skills claim <one-time-token>
65
65
 
66
66
  ${c} agent issue [--name <name>] [--kind <kind>] [--json]
67
- ${c} agent connect --token <token> [--name <name>] [--kind <kind>]
68
- ${c} agent heartbeat
67
+ ${c} agent connect --token <token> [--name <name>] [--kind <kind>] [--webhook-url <https-url>] [--webhook-token <secret>]
68
+ ${c} agent heartbeat [--webhook-url <https-url>] [--webhook-token <secret>|--disable-webhook]
69
69
  ${c} agent list
70
+ ${c} agent revoke --agent <id-or-name>
71
+ ${c} agent run [--once] [--ack] [--idle-ms <ms>] [--busy-ms <ms>] [--max-idle-ms <ms>]
70
72
  ${c} agent tasks list|watch|ack <taskId>|complete <taskId> [--content <text>]
73
+ ${c} agent transfers list|accept <transferId>|complete <transferId>|fail <transferId>
71
74
  ${c} agent task create --agent <id-or-name> --chat <id-or-title> --content <text>
72
75
 
73
76
  ${c} chat list
@@ -187,6 +190,26 @@ function parseFlags(argv) {
187
190
  return out;
188
191
  }
189
192
 
193
+ function sleep(ms) {
194
+ return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
195
+ }
196
+
197
+ function unwrapResult(payload) {
198
+ return payload && typeof payload === 'object' && payload.result && typeof payload.result === 'object'
199
+ ? payload.result
200
+ : payload;
201
+ }
202
+
203
+ function numericFlag(flags, key, fallback) {
204
+ const value = Number(flags[key] ?? fallback);
205
+ return Number.isFinite(value) && value > 0 ? value : fallback;
206
+ }
207
+
208
+ function jitterMs(ms, ratio = 0.2) {
209
+ const spread = Math.max(0, ms * ratio);
210
+ return Math.round(ms - spread + Math.random() * spread * 2);
211
+ }
212
+
190
213
  function requireValue(key, value) {
191
214
  if (!value || String(value).startsWith('--')) {
192
215
  fail(`Missing value for ${key}.`, EXIT_USAGE);
@@ -622,6 +645,8 @@ async function cmdAgent(sub, args, global) {
622
645
  agentName: flags.name || flags['agent-name'] || 'XiaShe CLI Agent',
623
646
  agentKind: flags.kind || 'cli',
624
647
  agentRef: flags.ref || `${osName()} ${process.cwd()}`,
648
+ webhookUrl: flags['webhook-url'] || undefined,
649
+ webhookAuthToken: flags['webhook-token'] || flags['webhook-auth-token'] || undefined,
625
650
  transport: flags.transport || 'npx'
626
651
  });
627
652
  await saveAgentSession(profileName, claim);
@@ -631,11 +656,20 @@ async function cmdAgent(sub, args, global) {
631
656
  if (sub === 'heartbeat') {
632
657
  const result = await client.agentHeartbeat({
633
658
  status: flags.status || 'online',
659
+ agentName: flags.name || flags['agent-name'] || undefined,
660
+ agentRef: flags.ref || undefined,
661
+ webhookUrl: flags['webhook-url'] || undefined,
662
+ webhookAuthToken: flags['webhook-token'] || flags['webhook-auth-token'] || undefined,
663
+ webhookMode: flags['disable-webhook'] ? 'disabled' : flags['webhook-url'] ? 'notify' : undefined,
634
664
  capabilities: parseCsv(flags.capabilities || 'chat,files,tasks,skills')
635
665
  });
636
666
  print(result, global);
637
667
  return;
638
668
  }
669
+ if (sub === 'run' || sub === 'receiver' || sub === 'loop') {
670
+ await cmdAgentReceiver(flags, client, global);
671
+ return;
672
+ }
639
673
  if (sub === 'list' || sub === 'agents' || !sub) {
640
674
  const rows = await client.listAgents(Number(flags.limit || 50));
641
675
  if (global.json) {
@@ -652,10 +686,22 @@ async function cmdAgent(sub, args, global) {
652
686
  ]);
653
687
  return;
654
688
  }
689
+ if (sub === 'revoke' || sub === 'disconnect' || sub === 'delete') {
690
+ const agent = await resolveAgent(client, requireArg(flags.agent || flags._[0], '--agent'));
691
+ const result = await client.revokeAgent(agent.id, {
692
+ idempotencyKey: flags.idempotencyKey || createIdempotencyKey('cli-agent-revoke', [agent.id])
693
+ });
694
+ print(result, global);
695
+ return;
696
+ }
655
697
  if (sub === 'tasks') {
656
698
  await cmdAgentTasks(flags, client, global);
657
699
  return;
658
700
  }
701
+ if (sub === 'transfers') {
702
+ await cmdAgentTransfers(flags, client, global);
703
+ return;
704
+ }
659
705
  if (sub === 'task') {
660
706
  const action = requireArg(flags._[0], 'agent task action');
661
707
  if (action !== 'create') fail('Usage: xiashe agent task create --agent <id-or-name> --chat <id-or-title> --content <text>', EXIT_USAGE);
@@ -673,6 +719,103 @@ async function cmdAgent(sub, args, global) {
673
719
  fail(`Unknown agent command: ${sub}`, EXIT_USAGE);
674
720
  }
675
721
 
722
+ async function cmdAgentReceiver(flags, client, global) {
723
+ const idleMs = numericFlag(flags, 'idle-ms', 30_000);
724
+ const busyMs = numericFlag(flags, 'busy-ms', 10_000);
725
+ const maxIdleMs = numericFlag(flags, 'max-idle-ms', 120_000);
726
+ const deadline = Date.now() + numericFlag(flags, 'timeout-ms', 24 * 60 * 60_000);
727
+ const capabilities = parseCsv(flags.capabilities || 'chat,files,tasks,skills');
728
+ const seenTasks = new Set();
729
+ const seenTransfers = new Set();
730
+ let emptyCycles = 0;
731
+ let failures = 0;
732
+
733
+ while (Date.now() < deadline) {
734
+ let nextDelayMs = idleMs;
735
+ try {
736
+ const heartbeat = unwrapResult(await client.agentHeartbeat({ capabilities }));
737
+ const pendingTaskCount = Number(heartbeat.pendingTaskCount || 0);
738
+ const pendingTransferCount = Number(heartbeat.pendingTransferCount || 0);
739
+ const pendingCount = pendingTaskCount + pendingTransferCount;
740
+ failures = 0;
741
+
742
+ if (pendingTaskCount > 0) {
743
+ const tasksResult = unwrapResult(await client.agentListTasks({ limit: Number(flags.limit || 20) }));
744
+ const tasks = tasksResult.tasks || tasksResult.items || [];
745
+ for (const task of tasks) {
746
+ const id = task.taskId || task.id;
747
+ if (!id || seenTasks.has(id)) continue;
748
+ seenTasks.add(id);
749
+ console.log(JSON.stringify({ type: 'task', ...task }, null, 2));
750
+ if (flags.ack) await client.agentAckTask(id, 'running');
751
+ }
752
+ }
753
+
754
+ if (pendingTransferCount > 0) {
755
+ const transfersResult = unwrapResult(await client.agentListTransfers({ limit: Number(flags.limit || 20) }));
756
+ const transfers = transfersResult.transfers || transfersResult.items || [];
757
+ for (const transfer of transfers) {
758
+ const id = transfer.transferId || transfer.id;
759
+ if (!id || seenTransfers.has(id)) continue;
760
+ seenTransfers.add(id);
761
+ console.log(JSON.stringify({ type: 'transfer', ...transfer }, null, 2));
762
+ }
763
+ }
764
+
765
+ if (pendingCount > 0) {
766
+ emptyCycles = 0;
767
+ nextDelayMs = Number(heartbeat.recommendedNextHeartbeatMs || busyMs);
768
+ } else {
769
+ emptyCycles += 1;
770
+ const backoffStep = Math.min(3, Math.floor(emptyCycles / 5));
771
+ nextDelayMs = Math.min(maxIdleMs, Number(heartbeat.recommendedNextHeartbeatMs || idleMs) * 2 ** backoffStep);
772
+ }
773
+
774
+ if (global.json) {
775
+ console.log(JSON.stringify({
776
+ ok: true,
777
+ pendingTaskCount,
778
+ pendingTransferCount,
779
+ nextHeartbeatMs: nextDelayMs,
780
+ emptyCycles
781
+ }));
782
+ }
783
+ if (flags.once) return;
784
+ } catch (error) {
785
+ failures += 1;
786
+ nextDelayMs = Math.min(maxIdleMs, idleMs * 2 ** Math.min(5, failures));
787
+ const message = error instanceof Error ? error.message : String(error);
788
+ if (flags.once) throw error;
789
+ console.error(`${PRODUCT_NAME} agent receiver error: ${message}. Retrying in ${Math.round(nextDelayMs / 1000)}s.`);
790
+ }
791
+ await sleep(jitterMs(nextDelayMs, 0.2));
792
+ }
793
+ }
794
+
795
+ async function cmdAgentTransfers(flags, client, global) {
796
+ const action = flags._[0] || 'list';
797
+ if (action === 'list') {
798
+ print(await client.agentListTransfers({ limit: Number(flags.limit || 20) }), global);
799
+ return;
800
+ }
801
+ if (action === 'accept') {
802
+ const transferId = requireArg(flags._[1], 'transfer id');
803
+ print(await client.agentAcceptTransfer(transferId, { idempotencyKey: flags.idempotencyKey || undefined }), global);
804
+ return;
805
+ }
806
+ if (action === 'complete' || action === 'fail' || action === 'cancel') {
807
+ const transferId = requireArg(flags._[1], 'transfer id');
808
+ const status = action === 'fail' ? 'failed' : action === 'cancel' ? 'cancelled' : 'completed';
809
+ print(await client.agentCompleteTransfer(transferId, {
810
+ status,
811
+ error: flags.error || undefined,
812
+ idempotencyKey: flags.idempotencyKey || undefined
813
+ }), global);
814
+ return;
815
+ }
816
+ fail(`Unknown agent transfers command: ${action}`, EXIT_USAGE);
817
+ }
818
+
676
819
  async function cmdAgentTasks(flags, client, global) {
677
820
  const action = flags._[0] || 'list';
678
821
  if (action === 'list') {
@@ -685,7 +828,7 @@ async function cmdAgentTasks(flags, client, global) {
685
828
  const seen = new Set();
686
829
  const deadline = Date.now() + Number(flags['timeout-ms'] || 300_000);
687
830
  while (Date.now() < deadline) {
688
- const result = await client.agentListTasks({ limit: Number(flags.limit || 20) });
831
+ const result = unwrapResult(await client.agentListTasks({ limit: Number(flags.limit || 20) }));
689
832
  const tasks = result.tasks || result.items || [];
690
833
  for (const task of tasks) {
691
834
  const id = task.taskId || task.id;
@@ -694,7 +837,7 @@ async function cmdAgentTasks(flags, client, global) {
694
837
  console.log(JSON.stringify(task, null, 2));
695
838
  if (flags.ack) await client.agentAckTask(id, 'running');
696
839
  }
697
- await new Promise((resolve) => setTimeout(resolve, 2500));
840
+ await sleep(2500);
698
841
  }
699
842
  return;
700
843
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiashe/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "xiashe": "bin/xiashe.mjs"
@@ -10,7 +10,7 @@
10
10
  "README.md"
11
11
  ],
12
12
  "dependencies": {
13
- "@xiashe/sdk": "0.1.0"
13
+ "@xiashe/sdk": "0.1.3"
14
14
  },
15
15
  "engines": {
16
16
  "node": ">=20"