evolclaw 3.1.11 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +27 -2
  3. package/dist/agents/{resolve.js → baseagent.js} +34 -5
  4. package/dist/agents/claude-runner.js +120 -27
  5. package/dist/agents/codex-app-server-client.js +364 -0
  6. package/dist/agents/codex-runner.js +1069 -141
  7. package/dist/agents/gemini-runner.js +2 -2
  8. package/dist/agents/runner-types.js +28 -0
  9. package/dist/aun/aid/control-aid.js +67 -0
  10. package/dist/aun/aid/identity.js +20 -7
  11. package/dist/aun/aid/store.js +2 -2
  12. package/dist/aun/storage/download.js +1 -1
  13. package/dist/aun/storage/upload.js +13 -1
  14. package/dist/channels/aun.js +538 -325
  15. package/dist/channels/dingtalk.js +77 -140
  16. package/dist/channels/feishu.js +98 -151
  17. package/dist/channels/qqbot.js +75 -138
  18. package/dist/channels/wechat.js +75 -136
  19. package/dist/channels/wecom.js +75 -138
  20. package/dist/cli/agent.js +44 -13
  21. package/dist/cli/index.js +207 -46
  22. package/dist/cli/init-channel.js +38 -148
  23. package/dist/cli/init.js +192 -85
  24. package/dist/cli/model.js +1 -1
  25. package/dist/cli/stats.js +558 -0
  26. package/dist/cli/version.js +87 -0
  27. package/dist/cli/watch-msg.js +5 -2
  28. package/dist/config-store.js +48 -11
  29. package/dist/core/channel-loader.js +84 -82
  30. package/dist/core/command-handler.js +754 -172
  31. package/dist/core/daemon-file-cache.js +216 -0
  32. package/dist/core/evolagent-registry.js +4 -0
  33. package/dist/core/evolagent.js +28 -23
  34. package/dist/core/interaction-router.js +8 -0
  35. package/dist/core/message/command-handler-agent-control.js +215 -0
  36. package/dist/core/message/create-status.js +67 -0
  37. package/dist/core/message/im-renderer.js +35 -13
  38. package/dist/core/message/items-formatter.js +9 -1
  39. package/dist/core/message/message-bridge.js +52 -22
  40. package/dist/core/message/message-log.js +1 -0
  41. package/dist/core/message/message-processor.js +336 -68
  42. package/dist/core/message/message-queue.js +15 -8
  43. package/dist/core/message/pending-hints.js +232 -0
  44. package/dist/core/message/response-depth.js +56 -0
  45. package/dist/core/model/model-catalog.js +1 -1
  46. package/dist/core/model/model-scope.js +40 -7
  47. package/dist/core/permission.js +9 -12
  48. package/dist/core/relation/peer-identity.js +16 -1
  49. package/dist/core/session/adapters/claude-session-file-adapter.js +48 -5
  50. package/dist/core/session/adapters/codex-session-file-adapter.js +4 -2
  51. package/dist/core/session/session-manager.js +27 -13
  52. package/dist/core/session/session-title.js +26 -0
  53. package/dist/core/stats/billing.js +151 -0
  54. package/dist/core/stats/budget.js +93 -0
  55. package/dist/core/stats/db.js +314 -0
  56. package/dist/core/stats/eck-vars.js +84 -0
  57. package/dist/core/stats/index.js +10 -0
  58. package/dist/core/stats/normalizer.js +78 -0
  59. package/dist/core/stats/query.js +760 -0
  60. package/dist/core/stats/writer.js +115 -0
  61. package/dist/core/trigger/manager.js +34 -0
  62. package/dist/core/trigger/parser.js +9 -3
  63. package/dist/core/trigger/scheduler.js +20 -17
  64. package/dist/{agents → eck}/kit-renderer.js +5 -1
  65. package/dist/{agents → eck}/manifest-engine.js +127 -35
  66. package/dist/{agents → eck}/message-renderer.js +26 -1
  67. package/dist/index.js +185 -8
  68. package/dist/ipc.js +22 -0
  69. package/dist/paths.js +7 -3
  70. package/dist/utils/cross-platform.js +23 -5
  71. package/dist/utils/ecweb-pair.js +20 -0
  72. package/dist/utils/stats.js +14 -0
  73. package/kits/docs/evolclaw/INDEX.md +3 -1
  74. package/kits/docs/evolclaw/fs-architecture.md +1215 -0
  75. package/kits/docs/evolclaw/fs.md +131 -0
  76. package/kits/docs/evolclaw/group-fs.md +209 -0
  77. package/kits/docs/evolclaw/stats.md +70 -0
  78. package/kits/docs/venues/aun-group.md +29 -6
  79. package/kits/docs/venues/group.md +5 -4
  80. package/kits/eck_manifest.json +12 -0
  81. package/kits/eck_message_manifest.json +30 -3
  82. package/kits/rules/05-venue.md +1 -1
  83. package/kits/templates/message-fragments/inject-default.md +2 -0
  84. package/kits/templates/message-fragments/item.md +1 -1
  85. package/kits/templates/system-fragments/response-depth.md +16 -0
  86. package/package.json +4 -4
  87. package/dist/agents/baseagent-normalize.js +0 -19
  88. package/dist/core/relation/peer-key.js +0 -16
  89. package/dist/utils/channel-helpers.js +0 -46
@@ -1,7 +1,7 @@
1
1
  import crypto from 'node:crypto';
2
2
  import { logger } from '../utils/logger.js';
3
3
  import { requireOptional } from '../utils/npm-ops.js';
4
- import { normalizeChannelInstances, getChannelShowActivities } from '../utils/channel-helpers.js';
4
+ import { resolveShowActivities, showActivitiesPolicy } from '../core/channel-loader.js';
5
5
  import { formatItemsAsText } from '../core/message/items-formatter.js';
6
6
  // ── WecomChannel ───────────────────────────────────────────────────────────────
7
7
  export class WecomChannel {
@@ -465,143 +465,80 @@ function isValidCredential(value) {
465
465
  }
466
466
  export class WecomChannelPlugin {
467
467
  name = 'wecom';
468
- isEnabled(config) {
469
- const raw = config.channels?.wecom;
470
- if (!raw)
471
- return false;
472
- if (Array.isArray(raw)) {
473
- return raw.some(inst => inst.enabled !== false && isValidCredential(inst.botId) && isValidCredential(inst.secret));
474
- }
475
- if (raw.enabled === false)
476
- return false;
477
- return isValidCredential(raw.botId) && isValidCredential(raw.secret);
478
- }
479
- async createChannels(config) {
480
- const instances = normalizeChannelInstances(config.channels?.wecom, 'wecom');
481
- const result = [];
482
- for (const inst of instances) {
483
- if (inst.enabled === false)
484
- continue;
485
- if (!isValidCredential(inst.botId) || !isValidCredential(inst.secret))
486
- continue;
487
- const channel = new WecomChannel({
488
- botId: inst.botId,
489
- secret: inst.secret,
490
- });
491
- const adapter = {
492
- channelName: inst.name,
493
- channelKey: inst.name,
494
- capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false, thread: false },
495
- send: async (envelope, payload) => {
496
- const ctx = envelope.replyContext;
497
- const channelId = envelope.channelId;
498
- switch (payload.kind) {
499
- case 'result.text':
500
- case 'command.result':
501
- case 'command.error':
502
- case 'system.notice':
503
- case 'system.error':
504
- case 'result.error':
505
- await channel.sendMessage(channelId, payload.text);
506
- return;
507
- case 'result.file':
508
- await channel.sendFile(channelId, payload.filePath);
509
- return;
510
- case 'result.image':
511
- await channel.sendImage(channelId, payload.data);
512
- return;
513
- case 'activity.batch': {
514
- const filtered = payload.items.filter((i) => !(i.kind === 'tool_result' && i.ok));
515
- const text = formatItemsAsText(filtered);
516
- if (text)
517
- await channel.sendMessage(channelId, text);
518
- return;
519
- }
520
- case 'interaction':
521
- if (payload.fallbackText)
522
- await channel.sendMessage(channelId, payload.fallbackText);
523
- return;
524
- case 'status.started':
525
- case 'status.completed':
526
- case 'status.interrupted':
527
- case 'status.error':
528
- case 'status.timeout':
529
- case 'status.progress':
530
- case 'custom':
531
- return;
532
- default:
533
- logger.warn(`[WeCom] Unhandled payload kind: ${payload.kind}`);
468
+ async createInstance(inst, ctx) {
469
+ if (inst.enabled === false)
470
+ return null;
471
+ if (!isValidCredential(inst.botId) || !isValidCredential(inst.secret))
472
+ return null;
473
+ const channel = new WecomChannel({
474
+ botId: inst.botId,
475
+ secret: inst.secret,
476
+ });
477
+ const mode = resolveShowActivities(inst);
478
+ const adapter = {
479
+ channelName: inst.name,
480
+ channelKey: inst.name,
481
+ capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false, thread: false },
482
+ send: async (envelope, payload) => {
483
+ const channelId = envelope.channelId;
484
+ switch (payload.kind) {
485
+ case 'result.text':
486
+ case 'command.result':
487
+ case 'command.error':
488
+ case 'system.notice':
489
+ case 'system.error':
490
+ case 'result.error':
491
+ await channel.sendMessage(channelId, payload.text);
492
+ return;
493
+ case 'result.file':
494
+ await channel.sendFile(channelId, payload.filePath);
495
+ return;
496
+ case 'result.image':
497
+ await channel.sendImage(channelId, payload.data);
498
+ return;
499
+ case 'activity.batch': {
500
+ const filtered = payload.items.filter((i) => !(i.kind === 'tool_result' && i.ok));
501
+ const text = formatItemsAsText(filtered);
502
+ if (text)
503
+ await channel.sendMessage(channelId, text);
504
+ return;
534
505
  }
535
- },
536
- };
537
- const policy = {
538
- canSwitchProject: (_chatType, identity) => identity === 'owner' || identity === 'admin',
539
- canListProjects: (_chatType, identity) => identity === 'owner' || identity === 'admin',
540
- canCreateSession: () => true,
541
- canDeleteSession: () => true,
542
- canImportCliSession: (_chatType, identity) => identity === 'owner' || identity === 'admin',
543
- messagePrefix: (chatType, peerName) => (chatType === 'group' && peerName) ? `[${peerName}] ` : '',
544
- showMiddleResult: (chatType, identity) => {
545
- const mode = getChannelShowActivities(config, inst.name);
546
- if (mode === 'none')
547
- return false;
548
- if (mode === 'dm-only')
549
- return chatType === 'private';
550
- if (mode === 'owner-dm-only')
551
- return chatType === 'private' && identity === 'owner';
552
- return true;
553
- },
554
- showIdleMonitor: (chatType, identity) => {
555
- const mode = getChannelShowActivities(config, inst.name);
556
- if (mode === 'none')
557
- return false;
558
- if (mode === 'dm-only')
559
- return chatType === 'private';
560
- if (mode === 'owner-dm-only')
561
- return chatType === 'private' && identity === 'owner';
562
- return true;
563
- },
564
- accumulateErrors: () => true,
565
- };
566
- const options = {
567
- fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g,
568
- supportsImages: true,
569
- flushDelay: inst.flushDelay,
570
- };
571
- result.push({
572
- channelType: 'wecom',
573
- adapter,
574
- channel,
575
- policy,
576
- options,
577
- connect: () => channel.connect(),
578
- disconnect: () => channel.disconnect(),
579
- onProjectPathRequest: () => Promise.resolve(config.projects?.defaultPath || process.cwd()),
580
- registerBridge(bridge, channelType) {
581
- bridge.register(adapter.channelName, (handler) => channel.onMessage(async (event) => {
582
- handler({
583
- channel: adapter.channelName,
584
- channelType,
585
- channelId: event.channelId,
586
- selfAID: inst.agentName,
587
- content: event.content,
588
- images: event.images,
589
- chatType: event.chatType || 'private',
590
- peerId: event.peerId || '',
591
- peerName: event.peerName,
592
- messageId: event.messageId,
593
- });
594
- }), (channelId, text) => channel.sendMessage(channelId, text), adapter, channelType);
595
- },
596
- });
597
- }
598
- return result;
599
- }
600
- async createChannel(config) {
601
- const instances = await this.createChannels(config);
602
- if (instances.length === 0) {
603
- throw new Error('WeCom config missing or invalid');
604
- }
605
- return instances[0];
506
+ case 'interaction':
507
+ if (payload.fallbackText)
508
+ await channel.sendMessage(channelId, payload.fallbackText);
509
+ return;
510
+ default: return;
511
+ }
512
+ },
513
+ };
514
+ const policy = {
515
+ canSwitchProject: (_, identity) => identity === 'owner' || identity === 'admin',
516
+ canListProjects: (_, identity) => identity === 'owner' || identity === 'admin',
517
+ canCreateSession: () => true,
518
+ canDeleteSession: () => true,
519
+ canImportCliSession: (_, identity) => identity === 'owner' || identity === 'admin',
520
+ messagePrefix: (chatType, peerName) => (chatType === 'group' && peerName) ? `[${peerName}] ` : '',
521
+ showMiddleResult: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
522
+ showIdleMonitor: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
523
+ accumulateErrors: () => true,
524
+ };
525
+ return {
526
+ channelType: 'wecom', adapter, channel,
527
+ policy,
528
+ options: { fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g, supportsImages: true, flushDelay: inst.flushDelay },
529
+ connect: () => channel.connect(),
530
+ disconnect: () => channel.disconnect(),
531
+ onProjectPathRequest: () => Promise.resolve(ctx.defaultProjectPath),
532
+ registerBridge(bridge, channelType) {
533
+ bridge.register(adapter.channelName, (handler) => channel.onMessage(async (event) => {
534
+ handler({
535
+ channel: adapter.channelName, channelType, channelId: event.channelId,
536
+ selfAID: ctx.agentName, content: event.content, images: event.images,
537
+ chatType: event.chatType || 'private', peerId: event.peerId || '',
538
+ peerName: event.peerName, messageId: event.messageId,
539
+ });
540
+ }), (channelId, text) => channel.sendMessage(channelId, text), adapter, channelType);
541
+ },
542
+ };
606
543
  }
607
544
  }
package/dist/cli/agent.js CHANGED
@@ -7,12 +7,12 @@ import { ipcQuery } from '../ipc.js';
7
7
  import { CONFIG_SCHEMA_VERSION } from '../types.js';
8
8
  import { isValidChannelName } from '../core/channel-loader.js';
9
9
  import { commandExists } from '../utils/cross-platform.js';
10
- import { isCodexSdkAvailable } from '../agents/codex-runner.js';
10
+ import { getCodexAppServerAvailability, isCodexAppServerAvailable } from '../agents/codex-runner.js';
11
11
  // ==================== Helpers ====================
12
12
  const BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini'];
13
13
  function isBaseagentAvailable(baseagent) {
14
14
  if (baseagent === 'codex')
15
- return isCodexSdkAvailable();
15
+ return isCodexAppServerAvailable();
16
16
  return commandExists(baseagent);
17
17
  }
18
18
  function detectAvailableBaseagents() {
@@ -313,7 +313,7 @@ export async function agentCreateInteractive(opts = {}) {
313
313
  // Baseagent
314
314
  const available = detectAvailableBaseagents();
315
315
  if (available.length === 0) {
316
- return { ok: false, error: `No usable baseagent detected. Install claude/gemini CLI or optional dependency @openai/codex-sdk.` };
316
+ return { ok: false, error: `No usable baseagent detected. Install claude/gemini CLI or codex CLI with app-server.` };
317
317
  }
318
318
  const defaultBa = pickDefaultBaseagent(available);
319
319
  let baseagent;
@@ -450,26 +450,35 @@ export async function agentCreateInteractive(opts = {}) {
450
450
  export async function agentCreateNonInteractive(opts) {
451
451
  const p = resolvePaths();
452
452
  const { isValidAid, aidCreate } = await import('../aun/aid/index.js');
453
+ opts.onPhase?.('validating', 'begin');
454
+ /** 校验失败:透出 failed 进度并返回原结构(控制流不变)。 */
455
+ const failValidating = (error) => {
456
+ opts.onPhase?.('validating', 'failed', error);
457
+ return { ok: false, error };
458
+ };
453
459
  if (!isValidAid(opts.aid)) {
454
- return { ok: false, error: `Invalid AID "${opts.aid}": must be a valid multi-level domain (e.g. mybot.agentid.pub)` };
460
+ return failValidating(`Invalid AID "${opts.aid}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`);
455
461
  }
456
462
  const agentDirPath = path.join(p.agentsDir, opts.aid);
457
463
  const configExists = fs.existsSync(path.join(agentDirPath, 'config.json'));
458
464
  if (configExists && !opts.force) {
459
- return { ok: false, error: `Agent "${opts.aid}" already exists: ${agentDirPath}/config.json (use --force to overwrite)` };
465
+ return failValidating(`Agent "${opts.aid}" already exists: ${agentDirPath}/config.json (use --force to overwrite)`);
460
466
  }
461
467
  // Baseagent
462
468
  const available = detectAvailableBaseagents();
463
469
  if (available.length === 0) {
464
- return { ok: false, error: `No usable baseagent detected. Install claude/gemini CLI or optional dependency @openai/codex-sdk.` };
470
+ return failValidating(`No usable baseagent detected. Install claude/gemini CLI or codex CLI with app-server.`);
465
471
  }
466
472
  let baseagent;
467
473
  if (opts.baseagent) {
468
474
  if (!BASEAGENT_CANDIDATES.includes(opts.baseagent)) {
469
- return { ok: false, error: `Invalid baseagent: ${opts.baseagent} (options: ${BASEAGENT_CANDIDATES.join('/')})` };
475
+ return failValidating(`Invalid baseagent: ${opts.baseagent} (options: ${BASEAGENT_CANDIDATES.join('/')})`);
470
476
  }
471
477
  if (!available.includes(opts.baseagent)) {
472
- return { ok: false, error: `${opts.baseagent} is not available in the current environment (available: ${available.join('/')})` };
478
+ const reason = opts.baseagent === 'codex'
479
+ ? getCodexAppServerAvailability().reason
480
+ : undefined;
481
+ return failValidating(reason || `${opts.baseagent} is not available in the current environment (available: ${available.join('/')})`);
473
482
  }
474
483
  baseagent = opts.baseagent;
475
484
  }
@@ -477,20 +486,22 @@ export async function agentCreateNonInteractive(opts) {
477
486
  baseagent = pickDefaultBaseagent(available);
478
487
  }
479
488
  if (!path.isAbsolute(opts.project)) {
480
- return { ok: false, error: `--project must be absolute: ${opts.project}` };
489
+ return failValidating(`--project must be absolute: ${opts.project}`);
481
490
  }
482
491
  if (!fs.existsSync(opts.project)) {
483
492
  try {
484
493
  fs.mkdirSync(opts.project, { recursive: true });
485
494
  }
486
495
  catch (e) {
487
- return { ok: false, error: `Failed to create ${opts.project}: ${e?.message || e}` };
496
+ return failValidating(`Failed to create ${opts.project}: ${e?.message || e}`);
488
497
  }
489
498
  }
490
499
  if (opts.owner && !isValidAid(opts.owner)) {
491
- return { ok: false, error: `Invalid owner: ${opts.owner}` };
500
+ return failValidating(`Invalid owner: ${opts.owner}`);
492
501
  }
502
+ opts.onPhase?.('validating', 'done');
493
503
  // Register AID
504
+ opts.onPhase?.('registering_aid', 'begin');
494
505
  let aidCreated = false;
495
506
  try {
496
507
  const result = await aidCreate(opts.aid);
@@ -499,9 +510,12 @@ export async function agentCreateNonInteractive(opts) {
499
510
  }
500
511
  catch { /* ignore */ }
501
512
  aidCreated = !result.alreadyExisted;
513
+ opts.onPhase?.('registering_aid', 'done', aidCreated ? 'created' : 'existed');
502
514
  }
503
515
  catch (e) {
504
- return { ok: false, error: `AID creation failed: ${e?.message || e}` };
516
+ const error = `AID creation failed: ${e?.message || e}`;
517
+ opts.onPhase?.('registering_aid', 'failed', error);
518
+ return { ok: false, error };
505
519
  }
506
520
  // Force 模式下若 agent 已存在且已 initialized,保留该状态(避免重复发欢迎)
507
521
  let preservedInitialized = false;
@@ -526,9 +540,12 @@ export async function agentCreateNonInteractive(opts) {
526
540
  chatmode: { ...DEFAULT_CHATMODE },
527
541
  dispatch: DEFAULT_DISPATCH,
528
542
  };
543
+ opts.onPhase?.('config_saved', 'begin');
529
544
  saveAgent(agentConfig);
530
545
  ensureAgentDirSkeleton(opts.aid);
546
+ opts.onPhase?.('config_saved', 'done');
531
547
  // Generate and upload agent.md
548
+ opts.onPhase?.('uploading_agentmd', 'begin');
532
549
  let agentmdUploaded = false;
533
550
  try {
534
551
  const { buildInitialAgentMd, agentmdPut } = await import('../aun/aid/index.js');
@@ -550,6 +567,7 @@ export async function agentCreateNonInteractive(opts) {
550
567
  await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
551
568
  await agentmdPut(content, { aid: opts.aid, aunPath });
552
569
  agentmdUploaded = true;
570
+ opts.onPhase?.('uploading_agentmd', 'done');
553
571
  break;
554
572
  }
555
573
  catch (e) {
@@ -559,11 +577,13 @@ export async function agentCreateNonInteractive(opts) {
559
577
  if (!agentmdUploaded) {
560
578
  console.warn(`⚠ agent.md upload failed: ${lastError?.message || lastError}`);
561
579
  console.warn(` Retry later with: evolclaw aid agentmd put ${opts.aid}`);
580
+ opts.onPhase?.('uploading_agentmd', 'warn', `upload failed: ${lastError?.message || lastError}`);
562
581
  }
563
582
  await new Promise(r => setTimeout(r, 0));
564
583
  }
565
584
  catch (e) {
566
585
  console.warn(`⚠ agent.md generation failed: ${e?.message || e}`);
586
+ opts.onPhase?.('uploading_agentmd', 'warn', `generation failed: ${e?.message || e}`);
567
587
  }
568
588
  // Attempt hot-load via IPC (if daemon is running).
569
589
  // Cold-starting a new agent (connecting AUN WebSocket) routinely takes
@@ -571,16 +591,24 @@ export async function agentCreateNonInteractive(opts) {
571
591
  // report while the daemon actually finishes bringing the agent online.
572
592
  let hotLoaded = false;
573
593
  let hotLoadError;
594
+ opts.onPhase?.('hot_loading', 'begin');
574
595
  try {
575
596
  const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid: opts.aid }, 30_000);
576
597
  if (ipcResult?.ok) {
577
598
  hotLoaded = true;
599
+ opts.onPhase?.('hot_loading', 'done');
578
600
  }
579
601
  else if (ipcResult) {
580
602
  hotLoadError = ipcResult.error;
603
+ opts.onPhase?.('hot_loading', 'warn', hotLoadError);
604
+ }
605
+ else {
606
+ opts.onPhase?.('hot_loading', 'warn', 'daemon not running');
581
607
  }
582
608
  }
583
- catch { /* daemon not running */ }
609
+ catch {
610
+ opts.onPhase?.('hot_loading', 'warn', 'daemon not running'); /* daemon not running */
611
+ }
584
612
  return {
585
613
  ok: true,
586
614
  aid: opts.aid,
@@ -858,6 +886,9 @@ export async function agentDelete(aid, purge = false) {
858
886
  }
859
887
  else {
860
888
  fs.unlinkSync(configPath);
889
+ // 清理构建进度文件(非 purge 删除只移除 config.json,需显式清理 create-status.json)
890
+ const { removeCreateStatus } = await import('../core/message/create-status.js');
891
+ removeCreateStatus(agentDir);
861
892
  }
862
893
  // Trigger resync so daemon drops the agent
863
894
  try {