@xfxstudio/claworld 0.2.3 → 0.2.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.
@@ -8,7 +8,7 @@
8
8
  ],
9
9
  "name": "Claworld Persona Relay",
10
10
  "description": "Claworld relay world channel plugin for OpenClaw.",
11
- "version": "0.2.3",
11
+ "version": "0.2.5",
12
12
  "configSchema": {
13
13
  "type": "object",
14
14
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfxstudio/claworld",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Claworld channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -18,6 +18,19 @@ import {
18
18
  runClaworldInstallerUpdate,
19
19
  } from './core.js';
20
20
 
21
+ function resolveCliHomeDir(env = process.env) {
22
+ const envHomeDir = String(
23
+ env.HOME
24
+ || env.USERPROFILE
25
+ || (
26
+ env.HOMEDRIVE && env.HOMEPATH
27
+ ? path.join(env.HOMEDRIVE, env.HOMEPATH)
28
+ : ''
29
+ ),
30
+ ).trim();
31
+ return envHomeDir || os.homedir();
32
+ }
33
+
21
34
  function printHelp() {
22
35
  console.log(`Usage: ${CLAWORLD_INSTALLER_BIN_NAME} <command> [options]
23
36
 
@@ -79,7 +92,7 @@ function nextValue(argv, index) {
79
92
  }
80
93
 
81
94
  export function parseInstallerCliArgs(argv = process.argv.slice(2), env = process.env) {
82
- const homeDir = os.homedir();
95
+ const homeDir = resolveCliHomeDir(env);
83
96
  const options = {
84
97
  command: null,
85
98
  json: false,
@@ -36,6 +36,81 @@ export const DEFAULT_VERIFICATION_DELAY_MS = 1_000;
36
36
  export const seedManagedWorkspace = seedManagedWorkspaceContract;
37
37
  const TRACKED_PLUGIN_UPDATEABLE_SOURCES = new Set(['npm', 'marketplace']);
38
38
 
39
+ function resolveRequireGatewayRunning(env = process.env) {
40
+ const normalized = normalizeText(env?.CLAWORLD_INSTALLER_REQUIRE_GATEWAY_RUNNING, null);
41
+ if (!normalized) return true;
42
+ const lowered = normalized.toLowerCase();
43
+ if (['0', 'false', 'no', 'off'].includes(lowered)) return false;
44
+ if (['1', 'true', 'yes', 'on'].includes(lowered)) return true;
45
+ return true;
46
+ }
47
+
48
+ function normalizeComparablePath(value) {
49
+ if (!value) return null;
50
+ return path.resolve(String(value));
51
+ }
52
+
53
+ async function resolveLocalPluginInstallTarget({
54
+ installMode = 'npm',
55
+ installSource = CLAWORLD_INSTALLER_PACKAGE_NAME,
56
+ repoRoot = null,
57
+ commandRunner = defaultCommandRunner,
58
+ cwd = process.cwd(),
59
+ env = process.env,
60
+ dryRun = false,
61
+ } = {}) {
62
+ if (installMode === 'npm') {
63
+ return {
64
+ installSource,
65
+ managedRepoRoot: repoRoot,
66
+ stagedPackageRoot: null,
67
+ };
68
+ }
69
+
70
+ const resolvedRepoRoot = normalizeComparablePath(repoRoot);
71
+ const resolvedInstallSource = normalizeComparablePath(installSource);
72
+ const derivedRepoRoot = resolvedRepoRoot
73
+ || (
74
+ resolvedInstallSource?.endsWith(`${path.sep}packages${path.sep}openclaw-plugin`)
75
+ ? path.resolve(resolvedInstallSource, '..', '..')
76
+ : null
77
+ );
78
+ const shouldStageRepo = Boolean(
79
+ derivedRepoRoot
80
+ && (
81
+ resolvedInstallSource === derivedRepoRoot
82
+ || resolvedInstallSource === path.join(derivedRepoRoot, 'packages', 'openclaw-plugin')
83
+ || resolvedInstallSource === CLAWORLD_INSTALLER_PACKAGE_NAME
84
+ ),
85
+ );
86
+
87
+ if (!shouldStageRepo) {
88
+ return {
89
+ installSource,
90
+ managedRepoRoot: installMode === 'link' ? installSource : repoRoot,
91
+ stagedPackageRoot: null,
92
+ };
93
+ }
94
+
95
+ const stagedPackageRoot = path.join(derivedRepoRoot, '.tmp', 'openclaw-plugin-package');
96
+ const buildScriptPath = path.join(derivedRepoRoot, 'scripts', 'build-openclaw-plugin-package.mjs');
97
+ if (!dryRun) {
98
+ await executeCommand({
99
+ commandRunner,
100
+ bin: process.execPath,
101
+ args: [buildScriptPath, '--output-dir', stagedPackageRoot],
102
+ cwd,
103
+ env,
104
+ dryRun,
105
+ });
106
+ }
107
+ return {
108
+ installSource: stagedPackageRoot,
109
+ managedRepoRoot: stagedPackageRoot,
110
+ stagedPackageRoot,
111
+ };
112
+ }
113
+
39
114
  function listBindings(config = {}) {
40
115
  return Array.isArray(config.bindings) ? config.bindings : [];
41
116
  }
@@ -829,6 +904,7 @@ export async function ensureClaworldPluginInstalled({
829
904
  action: before.installed ? 'refreshed_plugin_install' : 'installed_plugin',
830
905
  plugin: after,
831
906
  pluginConfig,
907
+ installSource,
832
908
  };
833
909
  } finally {
834
910
  if (bootstrap) {
@@ -1157,6 +1233,7 @@ export async function verifyClaworldInstall({
1157
1233
  dryRun = false,
1158
1234
  attempts = DEFAULT_VERIFICATION_ATTEMPTS,
1159
1235
  delayMs = DEFAULT_VERIFICATION_DELAY_MS,
1236
+ requireGatewayRunning = resolveRequireGatewayRunning(env),
1160
1237
  } = {}) {
1161
1238
  let lastResult = null;
1162
1239
 
@@ -1216,7 +1293,7 @@ export async function verifyClaworldInstall({
1216
1293
  const bindingReady = Boolean(bindingLine);
1217
1294
 
1218
1295
  lastResult = {
1219
- ok: pluginReady && gatewayRunning && channelReady && bindingReady,
1296
+ ok: pluginReady && channelReady && bindingReady && (!requireGatewayRunning || gatewayRunning),
1220
1297
  attempt,
1221
1298
  plugin,
1222
1299
  gatewayStatus,
@@ -1226,6 +1303,7 @@ export async function verifyClaworldInstall({
1226
1303
  gatewayRunning,
1227
1304
  channelReady,
1228
1305
  bindingReady,
1306
+ requireGatewayRunning,
1229
1307
  };
1230
1308
 
1231
1309
  if (lastResult.ok) {
@@ -1498,6 +1576,16 @@ export async function runClaworldInstallerInstall({
1498
1576
  );
1499
1577
  }
1500
1578
 
1579
+ const localPluginInstall = await resolveLocalPluginInstallTarget({
1580
+ installMode: pluginInstallMode,
1581
+ installSource: pluginInstallSource,
1582
+ repoRoot,
1583
+ commandRunner,
1584
+ cwd,
1585
+ env,
1586
+ dryRun,
1587
+ });
1588
+
1501
1589
  const plugin = await ensureClaworldPluginInstalled({
1502
1590
  openclawBin,
1503
1591
  configPath: resolvedConfigPath,
@@ -1508,7 +1596,7 @@ export async function runClaworldInstallerInstall({
1508
1596
  env,
1509
1597
  dryRun,
1510
1598
  installMode: pluginInstallMode,
1511
- installSource: pluginInstallSource,
1599
+ installSource: localPluginInstall.installSource,
1512
1600
  refresh: false,
1513
1601
  });
1514
1602
  const currentConfig = mergePluginMetadata(
@@ -1531,7 +1619,7 @@ export async function runClaworldInstallerInstall({
1531
1619
  toolProfile,
1532
1620
  approvalMode,
1533
1621
  sessionDmScope,
1534
- repoRoot,
1622
+ repoRoot: localPluginInstall.managedRepoRoot,
1535
1623
  fetchImpl,
1536
1624
  commandRunner,
1537
1625
  cwd,
@@ -1488,6 +1488,80 @@ function createDeliveryReplyDispatcher({
1488
1488
  };
1489
1489
  }
1490
1490
 
1491
+ async function runDeliveryReplyDispatch({
1492
+ runtime,
1493
+ currentCfg,
1494
+ relayClient,
1495
+ deliveryId,
1496
+ sessionKey,
1497
+ localAgentId,
1498
+ allowReply,
1499
+ logger,
1500
+ runtimeAccountId,
1501
+ inboundCtx,
1502
+ } = {}) {
1503
+ const { dispatcher, replyOptions, markDispatchIdle, didReply, getRuntimeOutputSummary } = createDeliveryReplyDispatcher({
1504
+ runtime,
1505
+ currentCfg,
1506
+ relayClient,
1507
+ deliveryId,
1508
+ sessionKey,
1509
+ localAgentId,
1510
+ allowReply,
1511
+ logger,
1512
+ runtimeAccountId,
1513
+ });
1514
+
1515
+ const dispatchResult = await runtime.channel.reply.dispatchReplyFromConfig({
1516
+ ctx: inboundCtx,
1517
+ cfg: currentCfg,
1518
+ dispatcher,
1519
+ replyOptions,
1520
+ });
1521
+ await markDispatchIdle();
1522
+
1523
+ return {
1524
+ dispatchResult,
1525
+ replied: didReply(),
1526
+ runtimeOutputSummary: getRuntimeOutputSummary(),
1527
+ };
1528
+ }
1529
+
1530
+ function resolveBoundLocalAgentId({ cfg = {}, runtimeConfig = {}, relayClient } = {}) {
1531
+ const accountId = resolveNormalizedText(runtimeConfig.accountId, null);
1532
+ const bindings = Array.isArray(cfg?.bindings) ? cfg.bindings : [];
1533
+ for (const rawBinding of bindings) {
1534
+ const binding = rawBinding && typeof rawBinding === 'object' && !Array.isArray(rawBinding)
1535
+ ? rawBinding
1536
+ : {};
1537
+ const match = binding.match && typeof binding.match === 'object' && !Array.isArray(binding.match)
1538
+ ? binding.match
1539
+ : {};
1540
+ if (
1541
+ resolveNormalizedText(match.channel, null) === 'claworld'
1542
+ && resolveNormalizedText(match.accountId, null) === accountId
1543
+ && resolveNormalizedText(binding.agentId, null)
1544
+ ) {
1545
+ return resolveNormalizedText(binding.agentId, null);
1546
+ }
1547
+ }
1548
+
1549
+ const agentList = Array.isArray(cfg?.agents?.list) ? cfg.agents.list : [];
1550
+ if (agentList.length === 1) {
1551
+ const onlyAgent = agentList[0] && typeof agentList[0] === 'object' && !Array.isArray(agentList[0])
1552
+ ? agentList[0]
1553
+ : {};
1554
+ const onlyAgentId = resolveNormalizedText(onlyAgent.id, null);
1555
+ if (onlyAgentId) {
1556
+ return onlyAgentId;
1557
+ }
1558
+ }
1559
+
1560
+ return resolveNormalizedText(relayClient?.boundAgentId, null)
1561
+ || resolveNormalizedText(runtimeConfig.agentId, null)
1562
+ || 'main';
1563
+ }
1564
+
1491
1565
  async function maybeBridgeRuntimeDelivery({
1492
1566
  relayClient,
1493
1567
  runtimeConfig,
@@ -1590,10 +1664,35 @@ async function maybeBridgeRuntimeDelivery({
1590
1664
  RelayFromAgentId: fromAgentId,
1591
1665
  UntrustedContext: ContextLines,
1592
1666
  });
1667
+ const localAgentId = resolveBoundLocalAgentId({
1668
+ cfg: currentCfg,
1669
+ runtimeConfig,
1670
+ relayClient,
1671
+ });
1672
+
1673
+ if (runtime?.channel?.session?.recordInboundSession && runtime?.channel?.session?.resolveStorePath && localAgentId) {
1674
+ const storePath = runtime.channel.session.resolveStorePath(currentCfg.session?.store, {
1675
+ agentId: localAgentId,
1676
+ });
1677
+ await runtime.channel.session.recordInboundSession({
1678
+ storePath,
1679
+ sessionKey: inboundCtx.SessionKey || sessionKey,
1680
+ ctx: inboundCtx,
1681
+ onRecordError: (error) => {
1682
+ logger.error?.(`[claworld:${runtimeAccountId}] failed to record inbound session`, {
1683
+ deliveryId,
1684
+ sessionKey,
1685
+ localAgentId,
1686
+ error: error?.message || String(error),
1687
+ });
1688
+ },
1689
+ });
1690
+ }
1593
1691
 
1594
1692
  logger.info?.(`[claworld:${runtimeAccountId}] routing delivery into runtime session`, {
1595
1693
  deliveryId,
1596
1694
  sessionKey,
1695
+ localAgentId,
1597
1696
  remoteIdentity: remoteAddress,
1598
1697
  routeStatus: routed?.status || null,
1599
1698
  bodyPreview: String(Body || '').slice(0, 240),
@@ -1602,26 +1701,66 @@ async function maybeBridgeRuntimeDelivery({
1602
1701
  commandAuthorized,
1603
1702
  });
1604
1703
 
1605
- const { dispatcher, replyOptions, markDispatchIdle, didReply, getRuntimeOutputSummary } = createDeliveryReplyDispatcher({
1704
+ let {
1705
+ dispatchResult,
1706
+ replied,
1707
+ runtimeOutputSummary,
1708
+ } = await runDeliveryReplyDispatch({
1606
1709
  runtime,
1607
1710
  currentCfg,
1608
1711
  relayClient,
1609
1712
  deliveryId,
1610
1713
  sessionKey,
1611
- localAgentId: runtimeConfig.relay?.agentId || relayClient?.boundAgentId || null,
1714
+ localAgentId,
1612
1715
  allowReply: metadata.allowReply !== false,
1613
1716
  logger,
1614
1717
  runtimeAccountId,
1718
+ inboundCtx,
1615
1719
  });
1616
- const dispatchResult = await runtime.channel.reply.dispatchReplyFromConfig({
1617
- ctx: inboundCtx,
1618
- cfg: currentCfg,
1619
- dispatcher,
1620
- replyOptions,
1621
- });
1622
- await markDispatchIdle();
1623
- const replied = didReply();
1624
- const runtimeOutputSummary = getRuntimeOutputSummary();
1720
+
1721
+ const shouldRetryKickoffDispatch = (
1722
+ metadata.deliveryType === 'kickoff'
1723
+ && metadata.allowReply !== false
1724
+ && replied !== true
1725
+ && runtimeOutputSummary.counts.final > 0
1726
+ && runtimeOutputSummary.counts.operationalNotice > 0
1727
+ && runtimeOutputSummary.counts.final === runtimeOutputSummary.counts.operationalNotice
1728
+ && runtimeOutputSummary.counts.block === 0
1729
+ && runtimeOutputSummary.counts.tool === 0
1730
+ && runtimeOutputSummary.counts.partial === 0
1731
+ && runtimeOutputSummary.counts.reasoning === 0
1732
+ && runtimeOutputSummary.counts.toolStart === 0
1733
+ && runtimeOutputSummary.counts.assistantMessageStart === 0
1734
+ && runtimeOutputSummary.counts.reasoningEnd === 0
1735
+ && runtimeOutputSummary.counts.compactionStart === 0
1736
+ && runtimeOutputSummary.counts.compactionEnd === 0
1737
+ );
1738
+
1739
+ if (shouldRetryKickoffDispatch) {
1740
+ logger.warn?.(`[claworld:${runtimeAccountId}] kickoff delivery produced only operational notices; retrying dispatch once`, {
1741
+ deliveryId,
1742
+ sessionKey,
1743
+ localAgentId,
1744
+ runtimeOutputSummary,
1745
+ });
1746
+
1747
+ ({
1748
+ dispatchResult,
1749
+ replied,
1750
+ runtimeOutputSummary,
1751
+ } = await runDeliveryReplyDispatch({
1752
+ runtime,
1753
+ currentCfg,
1754
+ relayClient,
1755
+ deliveryId,
1756
+ sessionKey,
1757
+ localAgentId,
1758
+ allowReply: metadata.allowReply !== false,
1759
+ logger,
1760
+ runtimeAccountId,
1761
+ inboundCtx,
1762
+ }));
1763
+ }
1625
1764
 
1626
1765
  logger.info?.(`[claworld:${runtimeAccountId}] delivery bridge completed`, {
1627
1766
  deliveryId,
@@ -202,6 +202,7 @@ function buildWorldRecord({
202
202
  status = null,
203
203
  schemaVersion = 1,
204
204
  existingMetrics = null,
205
+ existingConversationTemplate = null,
205
206
  } = {}) {
206
207
  const resolvedStatus = status || (enabled ? 'enabled' : 'draft');
207
208
  const participantContextField = buildDefaultEntryProfileField();
@@ -489,6 +490,7 @@ export function createWorldAdminService({ worldService, worldAuthorizationServic
489
490
  : (normalizeBoolean(enabled, false) ? 'enabled' : 'disabled'),
490
491
  schemaVersion: nextSchemaVersion,
491
492
  existingMetrics: existingWorld.metrics || null,
493
+ existingConversationTemplate: existingWorld.conversationTemplate || null,
492
494
  });
493
495
  } else if (enabled != null) {
494
496
  nextRecord = {