@xfxstudio/claworld 2026.5.10-testing.1 → 2026.5.14-testing.1

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.
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
+ import path from 'node:path';
2
3
 
3
4
  import {
4
5
  applyRuntimeIdentity,
@@ -32,6 +33,15 @@ import { createInboundSessionRouter } from '../runtime/inbound-session-router.js
32
33
  import { createOutboundSessionBridge } from '../runtime/outbound-session-bridge.js';
33
34
  import { createCanonicalResultBuilder } from '../runtime/canonical-result-builder.js';
34
35
  import { createDemoSessionBootstrap } from '../runtime/demo-session-bootstrap.js';
36
+ import {
37
+ CLAWORLD_PUBLIC_TOOL_NAMES,
38
+ CLAWORLD_RETIRED_PUBLIC_TOOL_NAMES,
39
+ CLAWORLD_TOOL_CONTRACT_VERSION,
40
+ } from '../runtime/tool-inventory.js';
41
+ import {
42
+ appendClaworldJournalEvent,
43
+ buildClaworldRuntimeMaintenanceEvent,
44
+ } from '../runtime/working-memory.js';
35
45
  import {
36
46
  broadcastModeratedWorld,
37
47
  createModeratedWorld,
@@ -62,6 +72,7 @@ import {
62
72
  resolveWorldSelectionFlow,
63
73
  } from '../runtime/product-shell-helper.js';
64
74
  import { extractBackendErrorContext } from '../runtime/backend-error-context.js';
75
+ import { resolveOpenClawWorkspaceRoot } from '../runtime/workspace-resolver.js';
65
76
  import { getClaworldRuntime } from './runtime.js';
66
77
  import {
67
78
  CLAWORLD_PLUGIN_CURRENT_VERSION,
@@ -582,19 +593,17 @@ async function deliverRelayMessage({ runtimeConfig, to, text, fetchImpl, logger,
582
593
 
583
594
  if (!result.ok) {
584
595
  logger.error?.('[claworld:outbound] message delivery failed', { status: result.status, body: result.body });
585
- throw createRuntimeBoundaryError({
596
+ createRelayRouteError({
597
+ result,
598
+ runtimeConfig,
586
599
  code: 'relay_message_delivery_failed',
587
- category: 'transport',
588
- status: result.status >= 500 ? 502 : result.status,
589
- message: `claworld outbound failed: ${result.status}`,
590
600
  publicMessage: 'claworld outbound message delivery failed',
591
- recoverable: true,
601
+ message: `claworld outbound failed: ${result.status}`,
592
602
  context: {
593
- accountId: runtimeConfig.accountId || null,
594
603
  fromAgentId,
595
604
  targetAgentId,
596
- status: result.status,
597
605
  },
606
+ passThroughBackendConflict: true,
598
607
  });
599
608
  }
600
609
 
@@ -630,13 +639,23 @@ function createRelayRouteError({
630
639
  publicMessage,
631
640
  message,
632
641
  context = {},
642
+ passThroughBackendConflict = false,
633
643
  }) {
644
+ const backendCode = resolveNormalizedText(result?.body?.error, null);
645
+ const backendMessage = resolveNormalizedText(result?.body?.message, null);
646
+ const shouldPassThroughConflict = passThroughBackendConflict === true
647
+ && Number(result?.status) === 409
648
+ && backendCode;
634
649
  throw createRuntimeBoundaryError({
635
- code,
636
- category: 'transport',
650
+ code: shouldPassThroughConflict ? backendCode : code,
651
+ category: shouldPassThroughConflict ? 'conflict' : 'transport',
637
652
  status: result?.status >= 500 ? 502 : result?.status || 502,
638
- message: message || publicMessage,
639
- publicMessage,
653
+ message: shouldPassThroughConflict
654
+ ? (backendMessage || message || publicMessage)
655
+ : (message || publicMessage),
656
+ publicMessage: shouldPassThroughConflict
657
+ ? (backendMessage || publicMessage)
658
+ : publicMessage,
640
659
  recoverable: true,
641
660
  context: {
642
661
  accountId: runtimeConfig.accountId || null,
@@ -727,6 +746,7 @@ async function createChatRequest({
727
746
  displayName: normalizedDisplayName,
728
747
  agentCode: normalizedAgentCode,
729
748
  },
749
+ passThroughBackendConflict: true,
730
750
  });
731
751
  }
732
752
  return result.body || {};
@@ -809,6 +829,7 @@ async function acceptChatRequest({
809
829
  code: 'chat_request_accept_failed',
810
830
  publicMessage: 'failed to accept chat request',
811
831
  context: { actorAgentId, chatRequestId },
832
+ passThroughBackendConflict: true,
812
833
  });
813
834
  }
814
835
  return normalizeChatInboxPayloadSessionKeys(result.body || {}, { localAgentId });
@@ -837,6 +858,7 @@ async function rejectChatRequest({
837
858
  code: 'chat_request_reject_failed',
838
859
  publicMessage: 'failed to reject chat request',
839
860
  context: { actorAgentId, chatRequestId },
861
+ passThroughBackendConflict: true,
840
862
  });
841
863
  }
842
864
  return result.body || {};
@@ -1841,6 +1863,297 @@ function buildDeliveryInboundEnvelope({
1841
1863
  };
1842
1864
  }
1843
1865
 
1866
+ function normalizeSessionStoreKey(value) {
1867
+ return resolveNormalizedText(value, '').toLowerCase();
1868
+ }
1869
+
1870
+ function resolveSessionStoreEntry(store = null, sessionKey = null) {
1871
+ if (!store || typeof store !== 'object' || Array.isArray(store)) return null;
1872
+ const normalizedSessionKey = resolveNormalizedText(sessionKey, null);
1873
+ if (!normalizedSessionKey) return null;
1874
+ if (store[normalizedSessionKey] && typeof store[normalizedSessionKey] === 'object') {
1875
+ return store[normalizedSessionKey];
1876
+ }
1877
+ const lowerSessionKey = normalizeSessionStoreKey(normalizedSessionKey);
1878
+ if (store[lowerSessionKey] && typeof store[lowerSessionKey] === 'object') {
1879
+ return store[lowerSessionKey];
1880
+ }
1881
+ const match = Object.entries(store).find(([key, value]) => (
1882
+ normalizeSessionStoreKey(key) === lowerSessionKey
1883
+ && value
1884
+ && typeof value === 'object'
1885
+ && !Array.isArray(value)
1886
+ ));
1887
+ return match ? match[1] : null;
1888
+ }
1889
+
1890
+ function readRuntimeSessionStoreEntry({ runtime = null, sessionStorePath = null, sessionKey = null } = {}) {
1891
+ if (!runtime?.agent?.session?.loadSessionStore || !sessionStorePath || !sessionKey) return null;
1892
+ try {
1893
+ return resolveSessionStoreEntry(
1894
+ runtime.agent.session.loadSessionStore(sessionStorePath),
1895
+ sessionKey,
1896
+ );
1897
+ } catch {
1898
+ return null;
1899
+ }
1900
+ }
1901
+
1902
+ function resolveSessionFilePathFromRuntime({
1903
+ runtime = null,
1904
+ sessionId = null,
1905
+ record = {},
1906
+ sessionStorePath = null,
1907
+ localAgentId = null,
1908
+ } = {}) {
1909
+ const normalizedSessionId = resolveNormalizedText(sessionId, null);
1910
+ if (!normalizedSessionId) return null;
1911
+
1912
+ const sessionsDir = sessionStorePath
1913
+ ? path.dirname(path.resolve(sessionStorePath))
1914
+ : null;
1915
+ if (typeof runtime?.agent?.session?.resolveSessionFilePath === 'function') {
1916
+ try {
1917
+ const resolved = runtime.agent.session.resolveSessionFilePath(
1918
+ normalizedSessionId,
1919
+ record,
1920
+ {
1921
+ ...(sessionsDir ? { sessionsDir } : {}),
1922
+ ...(localAgentId ? { agentId: localAgentId } : {}),
1923
+ },
1924
+ );
1925
+ const normalized = resolveNormalizedText(resolved, null);
1926
+ if (normalized) return normalized;
1927
+ } catch {
1928
+ // Fall through to the local derivation below.
1929
+ }
1930
+ }
1931
+
1932
+ const candidate = resolveNormalizedText(record?.sessionFile, null);
1933
+ if (candidate) {
1934
+ if (path.isAbsolute(candidate) || !sessionsDir) return candidate;
1935
+ return path.resolve(sessionsDir, candidate);
1936
+ }
1937
+
1938
+ if (sessionsDir) {
1939
+ return path.join(sessionsDir, `${normalizedSessionId}.jsonl`);
1940
+ }
1941
+ return null;
1942
+ }
1943
+
1944
+ function resolveSessionRecordArtifacts(record = null, fallbackStorePath = null, options = {}) {
1945
+ const normalizedRecord = record && typeof record === 'object' && !Array.isArray(record) ? record : {};
1946
+ const nestedSession = normalizedRecord.session && typeof normalizedRecord.session === 'object' && !Array.isArray(normalizedRecord.session)
1947
+ ? normalizedRecord.session
1948
+ : {};
1949
+ const sessionId = resolveNormalizedText(
1950
+ normalizedRecord.sessionId,
1951
+ resolveNormalizedText(nestedSession.sessionId, resolveNormalizedText(normalizedRecord.id, null)),
1952
+ );
1953
+ const sessionStorePath = resolveNormalizedText(
1954
+ normalizedRecord.storePath,
1955
+ resolveNormalizedText(normalizedRecord.sessionStorePath, fallbackStorePath),
1956
+ );
1957
+ const directSessionFile = resolveNormalizedText(
1958
+ normalizedRecord.sessionFile,
1959
+ resolveNormalizedText(
1960
+ normalizedRecord.sessionPath,
1961
+ resolveNormalizedText(
1962
+ normalizedRecord.filePath,
1963
+ resolveNormalizedText(normalizedRecord.path, resolveNormalizedText(nestedSession.filePath, null)),
1964
+ ),
1965
+ ),
1966
+ );
1967
+ const sessionFile = directSessionFile || resolveSessionFilePathFromRuntime({
1968
+ runtime: options.runtime,
1969
+ sessionId,
1970
+ record: normalizedRecord,
1971
+ sessionStorePath,
1972
+ localAgentId: options.localAgentId,
1973
+ });
1974
+ const transcriptPath = resolveNormalizedText(
1975
+ normalizedRecord.transcriptPath,
1976
+ resolveNormalizedText(nestedSession.transcriptPath, sessionFile),
1977
+ );
1978
+ return {
1979
+ sessionId,
1980
+ sessionFile,
1981
+ sessionStorePath,
1982
+ transcriptPath,
1983
+ };
1984
+ }
1985
+
1986
+ async function recordRuntimeInboundSessionArtifacts({
1987
+ runtime = null,
1988
+ currentCfg = {},
1989
+ localAgentId = null,
1990
+ sessionKey = null,
1991
+ ctx = {},
1992
+ logger = console,
1993
+ runtimeAccountId = null,
1994
+ logLabel = 'inbound',
1995
+ logContext = {},
1996
+ } = {}) {
1997
+ let sessionStorePath = null;
1998
+ let sessionRecord = null;
1999
+ const sessionApi = runtime?.channel?.session || {};
2000
+ const localSessionKey = resolveNormalizedText(ctx?.SessionKey, sessionKey);
2001
+
2002
+ if (sessionApi.resolveStorePath && localAgentId) {
2003
+ sessionStorePath = sessionApi.resolveStorePath(currentCfg.session?.store, {
2004
+ agentId: localAgentId,
2005
+ });
2006
+ const onRecordError = (error) => {
2007
+ logger.error?.(`[claworld:${runtimeAccountId}] failed to record ${logLabel} inbound session`, {
2008
+ ...logContext,
2009
+ sessionKey,
2010
+ localSessionKey,
2011
+ localAgentId,
2012
+ error: error?.message || String(error),
2013
+ });
2014
+ };
2015
+ try {
2016
+ if (typeof sessionApi.recordSessionMetaFromInbound === 'function') {
2017
+ sessionRecord = await sessionApi.recordSessionMetaFromInbound({
2018
+ storePath: sessionStorePath,
2019
+ sessionKey: localSessionKey,
2020
+ ctx,
2021
+ });
2022
+ } else if (typeof sessionApi.recordInboundSession === 'function') {
2023
+ sessionRecord = await sessionApi.recordInboundSession({
2024
+ storePath: sessionStorePath,
2025
+ sessionKey: localSessionKey,
2026
+ ctx,
2027
+ onRecordError,
2028
+ });
2029
+ }
2030
+ } catch (error) {
2031
+ onRecordError(error);
2032
+ }
2033
+ if (!sessionRecord) {
2034
+ sessionRecord = readRuntimeSessionStoreEntry({
2035
+ runtime,
2036
+ sessionStorePath,
2037
+ sessionKey: localSessionKey,
2038
+ });
2039
+ }
2040
+ }
2041
+
2042
+ return {
2043
+ sessionStorePath,
2044
+ sessionRecord,
2045
+ sessionArtifacts: resolveSessionRecordArtifacts(sessionRecord, sessionStorePath, {
2046
+ runtime,
2047
+ localAgentId,
2048
+ }),
2049
+ };
2050
+ }
2051
+
2052
+ function buildInboundRuntimeMaintenanceEvent({
2053
+ delivery = {},
2054
+ metadata = {},
2055
+ payload = {},
2056
+ messageId = null,
2057
+ eventType = 'delivery',
2058
+ sessionKind = null,
2059
+ localSessionKey = null,
2060
+ localAgentId = null,
2061
+ sessionArtifacts = {},
2062
+ workspaceRoot = null,
2063
+ } = {}) {
2064
+ const normalizedEventType = resolveNormalizedText(eventType, 'delivery');
2065
+ const isRelayDelivery = normalizedEventType === 'delivery';
2066
+ const sessionKey = resolveNormalizedText(delivery.sessionKey, null);
2067
+ const requestId = resolveNormalizedText(
2068
+ metadata.kickoffRequestId,
2069
+ resolveNormalizedText(metadata.requestId, resolveNormalizedText(metadata.chatRequestId, null)),
2070
+ );
2071
+ const worldId = resolveNormalizedText(
2072
+ metadata.worldId,
2073
+ resolveNormalizedText(delivery.worldId, resolveNormalizedText(payload.worldId, null)),
2074
+ );
2075
+ const conversationKey = resolveNormalizedText(
2076
+ metadata.conversationKey,
2077
+ resolveNormalizedText(delivery.conversationKey, resolveNormalizedText(payload.conversationKey, null)),
2078
+ );
2079
+ const fromAgentId = resolveNormalizedText(metadata.fromAgentId, null);
2080
+ const targetAgentId = resolveNormalizedText(
2081
+ delivery.targetAgentId,
2082
+ resolveNormalizedText(payload.targetAgentId, resolveNormalizedText(metadata.targetAgentId, null)),
2083
+ );
2084
+ const notificationId = resolveNormalizedText(
2085
+ metadata.notificationId,
2086
+ resolveNormalizedText(payload.notificationId, null),
2087
+ );
2088
+ const inboxItemId = resolveNormalizedText(
2089
+ metadata.inboxItemId,
2090
+ resolveNormalizedText(payload.inboxItemId, null),
2091
+ );
2092
+ const scope = sessionKind === 'management' ? 'management' : 'conversation';
2093
+ const summary = [
2094
+ isRelayDelivery
2095
+ ? 'Inbound Claworld delivery joined local session'
2096
+ : 'Inbound Claworld runtime input joined local session',
2097
+ requestId ? `for request ${requestId}` : null,
2098
+ fromAgentId ? `from ${fromAgentId}` : null,
2099
+ ].filter(Boolean).join(' ');
2100
+ return buildClaworldRuntimeMaintenanceEvent({
2101
+ id: messageId ? `runtime:${normalizedEventType}:${messageId}` : null,
2102
+ timestamp: delivery.createdAt || metadata.createdAt || payload.createdAt || null,
2103
+ kind: isRelayDelivery
2104
+ ? (metadata.deliveryType ? `delivery.${metadata.deliveryType}` : 'delivery')
2105
+ : 'runtime_event',
2106
+ eventType: normalizedEventType,
2107
+ scope,
2108
+ summary,
2109
+ excerpt: isRelayDelivery
2110
+ ? (
2111
+ payload.contextText
2112
+ ? 'Inbound delivery included contextText; raw dialogue is kept in the OpenClaw session transcript.'
2113
+ : 'Inbound delivery routed into an OpenClaw session after backend session resolution.'
2114
+ )
2115
+ : 'Inbound runtime input routed into an OpenClaw session after backend session resolution.',
2116
+ refs: {
2117
+ deliveryId: isRelayDelivery ? messageId : null,
2118
+ eventId: messageId,
2119
+ requestId,
2120
+ chatRequestId: requestId,
2121
+ worldId,
2122
+ conversationKey,
2123
+ fromAgentId,
2124
+ targetAgentId,
2125
+ notificationId,
2126
+ inboxItemId,
2127
+ sessionKey: localSessionKey || sessionKey,
2128
+ relaySessionKey: sessionKey,
2129
+ },
2130
+ relations: {
2131
+ deliveryId: isRelayDelivery ? messageId : null,
2132
+ eventId: messageId,
2133
+ requestId,
2134
+ chatRequestId: requestId,
2135
+ worldId,
2136
+ conversationKey,
2137
+ fromAgentId,
2138
+ targetAgentId,
2139
+ notificationId,
2140
+ inboxItemId,
2141
+ localAgentId,
2142
+ localSessionKey,
2143
+ relaySessionKey: sessionKey,
2144
+ sessionKey: localSessionKey || sessionKey,
2145
+ sessionId: sessionArtifacts.sessionId,
2146
+ sessionFile: sessionArtifacts.sessionFile,
2147
+ sessionStorePath: sessionArtifacts.sessionStorePath,
2148
+ transcriptPath: sessionArtifacts.transcriptPath,
2149
+ },
2150
+ artifacts: {
2151
+ workspaceRoot,
2152
+ ...sessionArtifacts,
2153
+ },
2154
+ });
2155
+ }
2156
+
1844
2157
  function createDeliveryReplyDispatcher({
1845
2158
  runtime,
1846
2159
  currentCfg,
@@ -2232,18 +2545,14 @@ async function maybeBridgeRuntimeInboundEvent({
2232
2545
  const contextText = resolveNormalizedText(payload.contextText, null);
2233
2546
  const incomingText = resolveNormalizedText(
2234
2547
  payload.commandText,
2235
- contextText ? null : resolveNormalizedText(payload.text, resolveNormalizedText(payload.body, null)),
2548
+ contextText
2549
+ ? null
2550
+ : resolveNormalizedText(payload.text, resolveNormalizedText(payload.body, null)),
2236
2551
  );
2237
2552
  const commandText = resolveNormalizedText(payload.commandText, incomingText);
2238
2553
  const fromAgentId = resolveNormalizedText(metadata.fromAgentId, null);
2239
- const routeSessionKind = resolveNormalizedText(
2240
- event?.route?.sessionKind,
2241
- resolveNormalizedText(delivery.sessionKind, resolveNormalizedText(payload.sessionKind, null)),
2242
- );
2243
2554
  const isRelayDelivery = eventType === 'delivery';
2244
2555
  const allowReply = metadata.allowReply === true || (isRelayDelivery && metadata.allowReply !== false);
2245
- const remoteIdentity = fromAgentId
2246
- || resolveNormalizedText(metadata.source, routeSessionKind === 'management' ? 'claworld-management' : 'unknown-peer');
2247
2556
 
2248
2557
  if (
2249
2558
  !runtime?.channel?.reply?.finalizeInboundContext
@@ -2290,6 +2599,12 @@ async function maybeBridgeRuntimeInboundEvent({
2290
2599
  sessionTarget: runtimeConfig.routing?.sessionTarget,
2291
2600
  fallbackTarget: runtimeConfig.routing?.fallbackTarget,
2292
2601
  }) || null;
2602
+ const routeSessionKind = resolveNormalizedText(
2603
+ event?.route?.sessionKind,
2604
+ resolveNormalizedText(routed?.sessionKind, null),
2605
+ );
2606
+ const remoteIdentity = fromAgentId
2607
+ || resolveNormalizedText(metadata.source, routeSessionKind === 'management' ? 'claworld-management' : 'unknown-peer');
2293
2608
  const worldId = resolveDeliveryWorldId(delivery);
2294
2609
  const commandAuthorized = isRelayDelivery && shouldAuthorizeBridgedCommand({
2295
2610
  runtimeConfig,
@@ -2331,6 +2646,9 @@ async function maybeBridgeRuntimeInboundEvent({
2331
2646
  OriginatingFrom: remoteIdentity,
2332
2647
  OriginatingTo: remoteIdentity,
2333
2648
  ChatType: isManagementSession ? 'management' : 'direct',
2649
+ SessionType: isManagementSession ? 'management' : 'direct',
2650
+ sessionType: isManagementSession ? 'management' : 'direct',
2651
+ sessionKind: isManagementSession ? 'management' : 'conversation',
2334
2652
  SenderName: senderName,
2335
2653
  SenderId: remoteIdentity,
2336
2654
  MessageId: deliveryId,
@@ -2346,26 +2664,21 @@ async function maybeBridgeRuntimeInboundEvent({
2346
2664
  UntrustedContext,
2347
2665
  });
2348
2666
 
2349
- if (runtime?.channel?.session?.recordInboundSession && runtime?.channel?.session?.resolveStorePath && localAgentId) {
2350
- const storePath = runtime.channel.session.resolveStorePath(currentCfg.session?.store, {
2351
- agentId: localAgentId,
2352
- });
2353
- await runtime.channel.session.recordInboundSession({
2354
- storePath,
2355
- sessionKey: inboundCtx.SessionKey || sessionKey,
2356
- ctx: inboundCtx,
2357
- onRecordError: (error) => {
2358
- logger.error?.(`[claworld:${runtimeAccountId}] failed to record inbound session`, {
2359
- eventType,
2360
- deliveryId,
2361
- sessionKey,
2362
- localSessionKey,
2363
- localAgentId,
2364
- error: error?.message || String(error),
2365
- });
2366
- },
2367
- });
2368
- }
2667
+ const {
2668
+ sessionArtifacts,
2669
+ } = await recordRuntimeInboundSessionArtifacts({
2670
+ runtime,
2671
+ currentCfg,
2672
+ localAgentId,
2673
+ sessionKey,
2674
+ ctx: inboundCtx,
2675
+ logger,
2676
+ runtimeAccountId,
2677
+ logContext: {
2678
+ eventType,
2679
+ deliveryId,
2680
+ },
2681
+ });
2369
2682
 
2370
2683
  logger.info?.(`[claworld:${runtimeAccountId}] ${isRelayDelivery ? 'routing delivery into runtime session' : 'routing inbound event into runtime session'}`, {
2371
2684
  eventType,
@@ -2467,16 +2780,54 @@ async function maybeBridgeRuntimeInboundEvent({
2467
2780
  }));
2468
2781
  }
2469
2782
 
2783
+ let journalResult = null;
2784
+ const workspaceRoot = resolveOpenClawWorkspaceRoot({
2785
+ sources: [
2786
+ { agentId: localAgentId, localAgentId },
2787
+ currentCfg,
2788
+ runtimeConfig,
2789
+ ],
2790
+ config: currentCfg,
2791
+ agentId: localAgentId,
2792
+ });
2793
+ if (workspaceRoot) {
2794
+ try {
2795
+ const maintenanceEvent = buildInboundRuntimeMaintenanceEvent({
2796
+ delivery,
2797
+ metadata,
2798
+ payload,
2799
+ messageId: deliveryId,
2800
+ eventType,
2801
+ sessionKind: routeSessionKind,
2802
+ localSessionKey,
2803
+ localAgentId,
2804
+ sessionArtifacts,
2805
+ workspaceRoot,
2806
+ });
2807
+ journalResult = await appendClaworldJournalEvent(workspaceRoot, maintenanceEvent);
2808
+ } catch (error) {
2809
+ logger.warn?.(`[claworld:${runtimeAccountId}] inbound journal append failed`, {
2810
+ eventType,
2811
+ deliveryId,
2812
+ sessionKey,
2813
+ error: error?.message || String(error),
2814
+ });
2815
+ }
2816
+ }
2817
+
2470
2818
  logger.info?.(`[claworld:${runtimeAccountId}] ${isRelayDelivery ? 'delivery bridge completed' : 'inbound bridge completed'}`, {
2471
2819
  eventType,
2472
2820
  deliveryId,
2473
2821
  sessionKey,
2474
2822
  localSessionKey,
2823
+ sessionId: sessionArtifacts.sessionId || null,
2824
+ sessionFile: sessionArtifacts.sessionFile || null,
2475
2825
  queuedFinal: Boolean(dispatchResult?.queuedFinal),
2476
2826
  replied,
2477
2827
  keptSilent,
2478
2828
  routeStatus: routed?.status || null,
2479
2829
  runtimeOutputSummary,
2830
+ journal: journalResult?.ok === true,
2480
2831
  });
2481
2832
 
2482
2833
  return {
@@ -3063,6 +3414,9 @@ export function createClaworldChannelPlugin({
3063
3414
  ok: true,
3064
3415
  pluginId: 'claworld',
3065
3416
  version: CLAWORLD_PLUGIN_CURRENT_VERSION,
3417
+ toolContractVersion: CLAWORLD_TOOL_CONTRACT_VERSION,
3418
+ publicToolNames: [...CLAWORLD_PUBLIC_TOOL_NAMES],
3419
+ retiredPublicToolNames: [...CLAWORLD_RETIRED_PUBLIC_TOOL_NAMES],
3066
3420
  defaultAccountId: null,
3067
3421
  accounts: accountSnapshots,
3068
3422
  relayClients: Object.fromEntries(