@xfxstudio/claworld 2026.4.30-testing.3 → 2026.5.3-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,11 @@ 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
+ appendClaworldJournalEvent,
38
+ buildClaworldRuntimeMaintenanceEvent,
39
+ } from '../runtime/working-memory.js';
40
+ import { resolveOpenClawWorkspaceRoot } from '../runtime/workspace-resolver.js';
35
41
  import {
36
42
  broadcastModeratedWorld,
37
43
  createModeratedWorld,
@@ -1841,6 +1847,385 @@ function buildDeliveryInboundEnvelope({
1841
1847
  };
1842
1848
  }
1843
1849
 
1850
+ function resolveRuntimeManagementEnvelope(event = {}) {
1851
+ const envelope = event?.delivery && typeof event.delivery === 'object' && !Array.isArray(event.delivery)
1852
+ ? event.delivery
1853
+ : {};
1854
+ const payload = envelope.payload && typeof envelope.payload === 'object' && !Array.isArray(envelope.payload)
1855
+ ? envelope.payload
1856
+ : {};
1857
+ const notification = payload.notification && typeof payload.notification === 'object' && !Array.isArray(payload.notification)
1858
+ ? payload.notification
1859
+ : {};
1860
+ return {
1861
+ envelope,
1862
+ payload,
1863
+ notification,
1864
+ eventType: resolveNormalizedText(envelope.eventType || payload.eventType || event.eventType, null),
1865
+ eventName: resolveNormalizedText(envelope.eventName || payload.eventName || notification.notificationType, null),
1866
+ eventId: resolveNormalizedText(
1867
+ envelope.eventId,
1868
+ resolveNormalizedText(payload.inboxItemId, resolveNormalizedText(notification.notificationId, null)),
1869
+ ),
1870
+ sessionKey: resolveNormalizedText(envelope.sessionKey, resolveNormalizedText(payload.sessionKey, null)),
1871
+ targetAgentId: resolveNormalizedText(
1872
+ envelope.targetAgentId,
1873
+ resolveNormalizedText(payload.targetAgentId, resolveNormalizedText(notification.targetAgentId, null)),
1874
+ ),
1875
+ worldId: resolveNormalizedText(envelope.worldId, resolveNormalizedText(payload.worldId, resolveNormalizedText(notification.relatedObjects?.worldId, null))),
1876
+ conversationKey: resolveNormalizedText(
1877
+ envelope.conversationKey,
1878
+ resolveNormalizedText(payload.conversationKey, resolveNormalizedText(notification.relatedObjects?.conversationKey, null)),
1879
+ ),
1880
+ createdAt: envelope.createdAt || payload.createdAt || notification.createdAt || null,
1881
+ };
1882
+ }
1883
+
1884
+ function stableJsonPreview(value = null, maxChars = 1200) {
1885
+ if (!value || typeof value !== 'object') return null;
1886
+ let json;
1887
+ try {
1888
+ json = JSON.stringify(value, null, 2);
1889
+ } catch {
1890
+ return null;
1891
+ }
1892
+ if (json.length <= maxChars) return json;
1893
+ return `${json.slice(0, Math.max(0, maxChars - 3))}...`;
1894
+ }
1895
+
1896
+ function buildManagementRuntimeEventBody(details = {}, { localSessionKey = null } = {}) {
1897
+ const notification = details.notification || {};
1898
+ const relatedObjects = notification.relatedObjects || details.payload?.relatedObjects || null;
1899
+ const nextActions = notification.nextActions || details.payload?.nextActions || null;
1900
+ const lines = [
1901
+ 'Claworld management event received.',
1902
+ '',
1903
+ `Event type: ${details.eventType || 'unknown'}`,
1904
+ `Event name: ${details.eventName || details.eventType || 'unknown'}`,
1905
+ details.eventId ? `Event id: ${details.eventId}` : null,
1906
+ `Management session: ${localSessionKey || details.sessionKey || 'unknown'}`,
1907
+ details.targetAgentId ? `Target agent: ${details.targetAgentId}` : null,
1908
+ details.worldId ? `World: ${details.worldId}` : null,
1909
+ details.conversationKey ? `Conversation: ${details.conversationKey}` : null,
1910
+ '',
1911
+ notification.notificationType ? `Notification type: ${notification.notificationType}` : null,
1912
+ notification.title ? `Title: ${notification.title}` : null,
1913
+ notification.body ? `Body: ${notification.body}` : null,
1914
+ notification.whyReceived ? `Why received: ${notification.whyReceived}` : null,
1915
+ relatedObjects ? 'Related objects:' : null,
1916
+ relatedObjects ? stableJsonPreview(relatedObjects, 900) : null,
1917
+ Array.isArray(nextActions) && nextActions.length > 0 ? 'Next actions:' : null,
1918
+ Array.isArray(nextActions) && nextActions.length > 0 ? stableJsonPreview(nextActions, 900) : null,
1919
+ '',
1920
+ 'Handle this as a Claworld Management Session input: decide whether to record, digest, act, report, or ask the user according to the current Claworld working memory and proactivity settings.',
1921
+ ].filter((line) => line != null && line !== '');
1922
+ return lines.join('\n');
1923
+ }
1924
+
1925
+ function normalizeSessionStoreKey(value) {
1926
+ return resolveNormalizedText(value, '').toLowerCase();
1927
+ }
1928
+
1929
+ function resolveSessionStoreEntry(store = null, sessionKey = null) {
1930
+ if (!store || typeof store !== 'object' || Array.isArray(store)) return null;
1931
+ const normalizedSessionKey = resolveNormalizedText(sessionKey, null);
1932
+ if (!normalizedSessionKey) return null;
1933
+ if (store[normalizedSessionKey] && typeof store[normalizedSessionKey] === 'object') {
1934
+ return store[normalizedSessionKey];
1935
+ }
1936
+ const lowerSessionKey = normalizeSessionStoreKey(normalizedSessionKey);
1937
+ if (store[lowerSessionKey] && typeof store[lowerSessionKey] === 'object') {
1938
+ return store[lowerSessionKey];
1939
+ }
1940
+ const match = Object.entries(store).find(([key, value]) => (
1941
+ normalizeSessionStoreKey(key) === lowerSessionKey
1942
+ && value
1943
+ && typeof value === 'object'
1944
+ && !Array.isArray(value)
1945
+ ));
1946
+ return match ? match[1] : null;
1947
+ }
1948
+
1949
+ function readRuntimeSessionStoreEntry({ runtime = null, sessionStorePath = null, sessionKey = null } = {}) {
1950
+ if (!runtime?.agent?.session?.loadSessionStore || !sessionStorePath || !sessionKey) return null;
1951
+ try {
1952
+ return resolveSessionStoreEntry(
1953
+ runtime.agent.session.loadSessionStore(sessionStorePath),
1954
+ sessionKey,
1955
+ );
1956
+ } catch {
1957
+ return null;
1958
+ }
1959
+ }
1960
+
1961
+ function resolveSessionFilePathFromRuntime({
1962
+ runtime = null,
1963
+ sessionId = null,
1964
+ record = {},
1965
+ sessionStorePath = null,
1966
+ localAgentId = null,
1967
+ } = {}) {
1968
+ const normalizedSessionId = resolveNormalizedText(sessionId, null);
1969
+ if (!normalizedSessionId) return null;
1970
+
1971
+ const sessionsDir = sessionStorePath
1972
+ ? path.dirname(path.resolve(sessionStorePath))
1973
+ : null;
1974
+ if (typeof runtime?.agent?.session?.resolveSessionFilePath === 'function') {
1975
+ try {
1976
+ const resolved = runtime.agent.session.resolveSessionFilePath(
1977
+ normalizedSessionId,
1978
+ record,
1979
+ {
1980
+ ...(sessionsDir ? { sessionsDir } : {}),
1981
+ ...(localAgentId ? { agentId: localAgentId } : {}),
1982
+ },
1983
+ );
1984
+ const normalized = resolveNormalizedText(resolved, null);
1985
+ if (normalized) return normalized;
1986
+ } catch {
1987
+ // Fall through to the local derivation below.
1988
+ }
1989
+ }
1990
+
1991
+ const candidate = resolveNormalizedText(record?.sessionFile, null);
1992
+ if (candidate) {
1993
+ if (path.isAbsolute(candidate) || !sessionsDir) return candidate;
1994
+ return path.resolve(sessionsDir, candidate);
1995
+ }
1996
+
1997
+ if (sessionsDir) {
1998
+ return path.join(sessionsDir, `${normalizedSessionId}.jsonl`);
1999
+ }
2000
+ return null;
2001
+ }
2002
+
2003
+ function resolveSessionRecordArtifacts(record = null, fallbackStorePath = null, options = {}) {
2004
+ const normalizedRecord = record && typeof record === 'object' && !Array.isArray(record) ? record : {};
2005
+ const nestedSession = normalizedRecord.session && typeof normalizedRecord.session === 'object' && !Array.isArray(normalizedRecord.session)
2006
+ ? normalizedRecord.session
2007
+ : {};
2008
+ const sessionId = resolveNormalizedText(
2009
+ normalizedRecord.sessionId,
2010
+ resolveNormalizedText(nestedSession.sessionId, resolveNormalizedText(normalizedRecord.id, null)),
2011
+ );
2012
+ const sessionStorePath = resolveNormalizedText(
2013
+ normalizedRecord.storePath,
2014
+ resolveNormalizedText(normalizedRecord.sessionStorePath, fallbackStorePath),
2015
+ );
2016
+ const directSessionFile = resolveNormalizedText(
2017
+ normalizedRecord.sessionFile,
2018
+ resolveNormalizedText(
2019
+ normalizedRecord.sessionPath,
2020
+ resolveNormalizedText(
2021
+ normalizedRecord.filePath,
2022
+ resolveNormalizedText(normalizedRecord.path, resolveNormalizedText(nestedSession.filePath, null)),
2023
+ ),
2024
+ ),
2025
+ );
2026
+ const sessionFile = directSessionFile || resolveSessionFilePathFromRuntime({
2027
+ runtime: options.runtime,
2028
+ sessionId,
2029
+ record: normalizedRecord,
2030
+ sessionStorePath,
2031
+ localAgentId: options.localAgentId,
2032
+ });
2033
+ const transcriptPath = resolveNormalizedText(
2034
+ normalizedRecord.transcriptPath,
2035
+ resolveNormalizedText(nestedSession.transcriptPath, sessionFile),
2036
+ );
2037
+ return {
2038
+ sessionId,
2039
+ sessionFile,
2040
+ sessionStorePath,
2041
+ transcriptPath,
2042
+ };
2043
+ }
2044
+
2045
+ async function recordRuntimeInboundSessionArtifacts({
2046
+ runtime = null,
2047
+ currentCfg = {},
2048
+ localAgentId = null,
2049
+ sessionKey = null,
2050
+ ctx = {},
2051
+ logger = console,
2052
+ runtimeAccountId = null,
2053
+ logLabel = 'inbound',
2054
+ logContext = {},
2055
+ } = {}) {
2056
+ let sessionStorePath = null;
2057
+ let sessionRecord = null;
2058
+ const sessionApi = runtime?.channel?.session || {};
2059
+ const localSessionKey = resolveNormalizedText(ctx?.SessionKey, sessionKey);
2060
+
2061
+ if (sessionApi.resolveStorePath && localAgentId) {
2062
+ sessionStorePath = sessionApi.resolveStorePath(currentCfg.session?.store, {
2063
+ agentId: localAgentId,
2064
+ });
2065
+ const onRecordError = (error) => {
2066
+ logger.error?.(`[claworld:${runtimeAccountId}] failed to record ${logLabel} inbound session`, {
2067
+ ...logContext,
2068
+ sessionKey,
2069
+ localSessionKey,
2070
+ localAgentId,
2071
+ error: error?.message || String(error),
2072
+ });
2073
+ };
2074
+ try {
2075
+ if (typeof sessionApi.recordSessionMetaFromInbound === 'function') {
2076
+ sessionRecord = await sessionApi.recordSessionMetaFromInbound({
2077
+ storePath: sessionStorePath,
2078
+ sessionKey: localSessionKey,
2079
+ ctx,
2080
+ });
2081
+ } else if (typeof sessionApi.recordInboundSession === 'function') {
2082
+ sessionRecord = await sessionApi.recordInboundSession({
2083
+ storePath: sessionStorePath,
2084
+ sessionKey: localSessionKey,
2085
+ ctx,
2086
+ onRecordError,
2087
+ });
2088
+ }
2089
+ } catch (error) {
2090
+ onRecordError(error);
2091
+ }
2092
+ if (!sessionRecord) {
2093
+ sessionRecord = readRuntimeSessionStoreEntry({
2094
+ runtime,
2095
+ sessionStorePath,
2096
+ sessionKey: localSessionKey,
2097
+ });
2098
+ }
2099
+ }
2100
+
2101
+ return {
2102
+ sessionStorePath,
2103
+ sessionRecord,
2104
+ sessionArtifacts: resolveSessionRecordArtifacts(sessionRecord, sessionStorePath, {
2105
+ runtime,
2106
+ localAgentId,
2107
+ }),
2108
+ };
2109
+ }
2110
+
2111
+ function buildDeliveryRuntimeMaintenanceEvent({
2112
+ delivery = {},
2113
+ metadata = {},
2114
+ payload = {},
2115
+ localSessionKey = null,
2116
+ localAgentId = null,
2117
+ sessionArtifacts = {},
2118
+ workspaceRoot = null,
2119
+ } = {}) {
2120
+ const deliveryId = resolveNormalizedText(delivery.deliveryId, null);
2121
+ const sessionKey = resolveNormalizedText(delivery.sessionKey, null);
2122
+ const requestId = resolveNormalizedText(
2123
+ metadata.kickoffRequestId,
2124
+ resolveNormalizedText(metadata.requestId, resolveNormalizedText(metadata.chatRequestId, null)),
2125
+ );
2126
+ const worldId = resolveNormalizedText(
2127
+ metadata.worldId,
2128
+ resolveNormalizedText(delivery.worldId, null),
2129
+ );
2130
+ const conversationKey = resolveNormalizedText(metadata.conversationKey, resolveNormalizedText(delivery.conversationKey, null));
2131
+ const fromAgentId = resolveNormalizedText(metadata.fromAgentId, null);
2132
+ const summary = [
2133
+ 'Inbound Claworld delivery joined local session',
2134
+ requestId ? `for request ${requestId}` : null,
2135
+ fromAgentId ? `from ${fromAgentId}` : null,
2136
+ ].filter(Boolean).join(' ');
2137
+ return buildClaworldRuntimeMaintenanceEvent({
2138
+ id: deliveryId ? `runtime:delivery:${deliveryId}` : null,
2139
+ timestamp: delivery.createdAt || metadata.createdAt || payload.createdAt || null,
2140
+ kind: metadata.deliveryType ? `delivery.${metadata.deliveryType}` : 'delivery',
2141
+ eventType: 'delivery',
2142
+ scope: 'conversation',
2143
+ summary,
2144
+ excerpt: payload.contextText
2145
+ ? 'Inbound delivery included contextText; raw dialogue is kept in the OpenClaw session transcript.'
2146
+ : 'Inbound delivery routed into an OpenClaw conversation session; raw dialogue is kept in the session transcript.',
2147
+ refs: {
2148
+ deliveryId,
2149
+ requestId,
2150
+ chatRequestId: requestId,
2151
+ worldId,
2152
+ conversationKey,
2153
+ fromAgentId,
2154
+ sessionKey: localSessionKey || sessionKey,
2155
+ relaySessionKey: sessionKey,
2156
+ },
2157
+ relations: {
2158
+ deliveryId,
2159
+ requestId,
2160
+ chatRequestId: requestId,
2161
+ worldId,
2162
+ conversationKey,
2163
+ fromAgentId,
2164
+ localAgentId,
2165
+ localSessionKey,
2166
+ relaySessionKey: sessionKey,
2167
+ sessionKey: localSessionKey || sessionKey,
2168
+ sessionId: sessionArtifacts.sessionId,
2169
+ sessionFile: sessionArtifacts.sessionFile,
2170
+ sessionStorePath: sessionArtifacts.sessionStorePath,
2171
+ transcriptPath: sessionArtifacts.transcriptPath,
2172
+ },
2173
+ artifacts: {
2174
+ workspaceRoot,
2175
+ ...sessionArtifacts,
2176
+ },
2177
+ });
2178
+ }
2179
+
2180
+ function buildManagementRuntimeMaintenanceEvent(details = {}, {
2181
+ localSessionKey = null,
2182
+ localAgentId = null,
2183
+ sessionArtifacts = {},
2184
+ workspaceRoot = null,
2185
+ } = {}) {
2186
+ const notification = details.notification || {};
2187
+ const refs = {
2188
+ inboxItemId: details.payload?.inboxItemId || details.eventId,
2189
+ notificationId: notification.notificationId,
2190
+ notificationType: notification.notificationType || details.payload?.notificationType,
2191
+ worldId: details.worldId,
2192
+ conversationKey: details.conversationKey,
2193
+ targetAgentId: details.targetAgentId,
2194
+ sessionKey: localSessionKey || details.sessionKey,
2195
+ relaySessionKey: details.sessionKey,
2196
+ };
2197
+ return buildClaworldRuntimeMaintenanceEvent({
2198
+ id: details.eventId ? `runtime:${details.eventType}:${details.eventId}` : null,
2199
+ timestamp: details.createdAt || notification.createdAt || null,
2200
+ kind: details.eventName || details.eventType || 'runtime_event',
2201
+ eventType: details.eventType || 'runtime_event',
2202
+ scope: 'management',
2203
+ summary: notification.title || notification.body || `${details.eventType || 'runtime'} event received.`,
2204
+ excerpt: buildManagementRuntimeEventBody(details),
2205
+ refs,
2206
+ relations: {
2207
+ inboxItemId: refs.inboxItemId,
2208
+ notificationId: refs.notificationId,
2209
+ notificationType: refs.notificationType,
2210
+ worldId: details.worldId,
2211
+ conversationKey: details.conversationKey,
2212
+ targetAgentId: details.targetAgentId,
2213
+ localAgentId,
2214
+ localSessionKey,
2215
+ relaySessionKey: details.sessionKey,
2216
+ sessionKey: localSessionKey || details.sessionKey,
2217
+ sessionId: sessionArtifacts.sessionId,
2218
+ sessionFile: sessionArtifacts.sessionFile,
2219
+ sessionStorePath: sessionArtifacts.sessionStorePath,
2220
+ transcriptPath: sessionArtifacts.transcriptPath,
2221
+ },
2222
+ artifacts: {
2223
+ workspaceRoot,
2224
+ ...sessionArtifacts,
2225
+ },
2226
+ });
2227
+ }
2228
+
1844
2229
  function createDeliveryReplyDispatcher({
1845
2230
  runtime,
1846
2231
  currentCfg,
@@ -2229,10 +2614,13 @@ async function maybeBridgeRuntimeInboundEvent({
2229
2614
  const eventType = resolveNormalizedText(delivery.eventType, resolveNormalizedText(event?.eventType, 'delivery'));
2230
2615
  const deliveryId = resolveInboundMessageId({ delivery, payload, metadata });
2231
2616
  const sessionKey = resolveNormalizedText(delivery.sessionKey, null);
2617
+ const fallbackRuntimeEventText = eventType !== 'delivery'
2618
+ ? buildManagementRuntimeEventBody(resolveRuntimeManagementEnvelope(event))
2619
+ : null;
2232
2620
  const contextText = resolveNormalizedText(payload.contextText, null);
2233
2621
  const incomingText = resolveNormalizedText(
2234
2622
  payload.commandText,
2235
- contextText ? null : resolveNormalizedText(payload.text, resolveNormalizedText(payload.body, null)),
2623
+ contextText ? null : resolveNormalizedText(payload.text, resolveNormalizedText(payload.body, fallbackRuntimeEventText)),
2236
2624
  );
2237
2625
  const commandText = resolveNormalizedText(payload.commandText, incomingText);
2238
2626
  const fromAgentId = resolveNormalizedText(metadata.fromAgentId, null);
@@ -2331,6 +2719,9 @@ async function maybeBridgeRuntimeInboundEvent({
2331
2719
  OriginatingFrom: remoteIdentity,
2332
2720
  OriginatingTo: remoteIdentity,
2333
2721
  ChatType: isManagementSession ? 'management' : 'direct',
2722
+ SessionType: isManagementSession ? 'management' : 'direct',
2723
+ sessionType: isManagementSession ? 'management' : 'direct',
2724
+ sessionKind: isManagementSession ? 'management' : 'conversation',
2334
2725
  SenderName: senderName,
2335
2726
  SenderId: remoteIdentity,
2336
2727
  MessageId: deliveryId,
@@ -2346,25 +2737,66 @@ async function maybeBridgeRuntimeInboundEvent({
2346
2737
  UntrustedContext,
2347
2738
  });
2348
2739
 
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,
2740
+ const {
2741
+ sessionArtifacts,
2742
+ } = await recordRuntimeInboundSessionArtifacts({
2743
+ runtime,
2744
+ currentCfg,
2745
+ localAgentId,
2746
+ sessionKey,
2747
+ ctx: inboundCtx,
2748
+ logger,
2749
+ runtimeAccountId,
2750
+ logLabel: isRelayDelivery ? 'delivery' : 'inbound',
2751
+ logContext: { eventType, deliveryId },
2752
+ });
2753
+
2754
+ const workspaceRoot = resolveOpenClawWorkspaceRoot({
2755
+ sources: [
2756
+ { agentId: localAgentId, localAgentId },
2757
+ currentCfg,
2758
+ runtimeConfig,
2759
+ ],
2760
+ config: currentCfg,
2761
+ agentId: localAgentId,
2762
+ });
2763
+ let journalResult = null;
2764
+ if (workspaceRoot) {
2765
+ try {
2766
+ const maintenanceEvent = isRelayDelivery
2767
+ ? buildDeliveryRuntimeMaintenanceEvent({
2768
+ delivery,
2769
+ metadata,
2770
+ payload,
2362
2771
  localSessionKey,
2363
2772
  localAgentId,
2364
- error: error?.message || String(error),
2773
+ sessionArtifacts,
2774
+ workspaceRoot,
2775
+ })
2776
+ : buildManagementRuntimeMaintenanceEvent(resolveRuntimeManagementEnvelope(event), {
2777
+ localSessionKey,
2778
+ localAgentId,
2779
+ sessionArtifacts,
2780
+ workspaceRoot,
2365
2781
  });
2366
- },
2367
- });
2782
+ journalResult = await appendClaworldJournalEvent(
2783
+ workspaceRoot,
2784
+ maintenanceEvent,
2785
+ {
2786
+ trigger: isRelayDelivery ? 'delivery' : 'runtime_event',
2787
+ rule: isRelayDelivery ? 'runtime_delivery_joined_session' : 'runtime_event_joined_session',
2788
+ },
2789
+ );
2790
+ } catch (error) {
2791
+ logger.warn?.(`[claworld:${runtimeAccountId}] inbound journal append failed`, {
2792
+ eventType,
2793
+ deliveryId,
2794
+ sessionKey,
2795
+ localSessionKey,
2796
+ localAgentId,
2797
+ error: error?.message || String(error),
2798
+ });
2799
+ }
2368
2800
  }
2369
2801
 
2370
2802
  logger.info?.(`[claworld:${runtimeAccountId}] ${isRelayDelivery ? 'routing delivery into runtime session' : 'routing inbound event into runtime session'}`, {
@@ -2373,12 +2805,15 @@ async function maybeBridgeRuntimeInboundEvent({
2373
2805
  sessionKey,
2374
2806
  localSessionKey,
2375
2807
  localAgentId,
2808
+ sessionId: sessionArtifacts.sessionId || null,
2809
+ sessionFile: sessionArtifacts.sessionFile || null,
2376
2810
  remoteIdentity,
2377
2811
  routeStatus: routed?.status || null,
2378
2812
  bodyPreview: String(Body || '').slice(0, 240),
2379
2813
  rawBodyPreview: String(RawBody || '').slice(0, 240),
2380
2814
  allowReply,
2381
2815
  commandAuthorized,
2816
+ journal: journalResult?.ok === true,
2382
2817
  });
2383
2818
 
2384
2819
  if (isRelayDelivery && metadata.acceptanceRequired !== false) {
@@ -2491,6 +2926,199 @@ async function maybeBridgeRuntimeInboundEvent({
2491
2926
  };
2492
2927
  }
2493
2928
 
2929
+ async function maybeBridgeRuntimeManagementEvent({
2930
+ runtimeConfig,
2931
+ runtimeAccountId,
2932
+ event,
2933
+ logger,
2934
+ runtime,
2935
+ cfg,
2936
+ inbound,
2937
+ }) {
2938
+ const details = resolveRuntimeManagementEnvelope(event);
2939
+ const route = event?.route || inbound?.routeInboundEvent?.(details.envelope, {
2940
+ sessionTarget: runtimeConfig.routing?.sessionTarget,
2941
+ fallbackTarget: runtimeConfig.routing?.fallbackTarget,
2942
+ }) || null;
2943
+ const sessionKey = resolveNormalizedText(details.sessionKey, resolveNormalizedText(route?.sessionKey, null));
2944
+ const eventId = resolveNormalizedText(details.eventId, `${details.eventType || 'runtime_event'}:${Date.now()}`);
2945
+
2946
+ if (
2947
+ !runtime?.channel?.reply?.finalizeInboundContext
2948
+ || !runtime?.channel?.reply?.dispatchReplyFromConfig
2949
+ || !runtime?.channel?.reply?.createReplyDispatcherWithTyping
2950
+ ) {
2951
+ logger.warn?.(`[claworld:${runtimeAccountId}] skipping management event bridge: missing runtime bridge hooks`, {
2952
+ eventType: details.eventType,
2953
+ eventName: details.eventName,
2954
+ sessionKey,
2955
+ });
2956
+ return { skipped: true, reason: 'missing_runtime_bridge_hooks' };
2957
+ }
2958
+ if (!details.eventType || !sessionKey || route?.status === 'invalid') {
2959
+ logger.warn?.(`[claworld:${runtimeAccountId}] skipping management event bridge: missing runtime event payload`, {
2960
+ eventType: details.eventType,
2961
+ eventName: details.eventName,
2962
+ sessionKey,
2963
+ routeStatus: route?.status || null,
2964
+ });
2965
+ return { skipped: true, reason: 'missing_management_event_payload' };
2966
+ }
2967
+
2968
+ const loadedCfg = await runtime.config?.loadConfig?.() || {};
2969
+ const currentCfg = {
2970
+ ...(loadedCfg && typeof loadedCfg === 'object' && !Array.isArray(loadedCfg) ? loadedCfg : {}),
2971
+ ...(cfg && typeof cfg === 'object' && !Array.isArray(cfg) ? cfg : {}),
2972
+ agents: cfg?.agents || loadedCfg?.agents,
2973
+ bindings: cfg?.bindings || loadedCfg?.bindings,
2974
+ channels: cfg?.channels || loadedCfg?.channels,
2975
+ session: cfg?.session || loadedCfg?.session,
2976
+ };
2977
+ const localAgentId = resolveBoundLocalAgentId({
2978
+ cfg: currentCfg,
2979
+ runtimeConfig,
2980
+ });
2981
+ const localSessionKey = buildAgentScopedLocalSessionKey({
2982
+ sessionKey,
2983
+ localAgentId,
2984
+ });
2985
+ const body = buildManagementRuntimeEventBody(details, { localSessionKey });
2986
+ const timestamp = Date.parse(details.createdAt || '');
2987
+ const inboundTimestamp = Number.isFinite(timestamp) ? timestamp : Date.now();
2988
+ const localIdentity = normalizeClaworldText(runtimeConfig.relay?.agentId, runtimeConfig.accountId);
2989
+ const inboundCtx = runtime.channel.reply.finalizeInboundContext({
2990
+ Body: body,
2991
+ RawBody: body,
2992
+ CommandBody: body,
2993
+ BodyForAgent: body,
2994
+ BodyForCommands: body,
2995
+ From: 'claworld:management',
2996
+ To: `claworld:${localIdentity}`,
2997
+ SessionKey: localSessionKey || sessionKey,
2998
+ RelaySessionKey: sessionKey,
2999
+ AccountId: runtimeConfig.accountId,
3000
+ OriginatingChannel: 'claworld',
3001
+ OriginatingFrom: 'management',
3002
+ OriginatingTo: localIdentity,
3003
+ ChatType: 'management',
3004
+ SessionType: 'management',
3005
+ sessionType: 'management',
3006
+ sessionKind: 'management',
3007
+ SenderName: 'Claworld Management',
3008
+ SenderId: 'claworld-management',
3009
+ MessageId: eventId,
3010
+ Provider: 'claworld',
3011
+ Surface: 'claworld',
3012
+ ConversationLabel: 'Claworld Management',
3013
+ Timestamp: inboundTimestamp,
3014
+ MessageSid: eventId,
3015
+ WasMentioned: false,
3016
+ CommandAuthorized: false,
3017
+ RelayEventId: eventId,
3018
+ RelayEventType: details.eventType,
3019
+ RelayEventName: details.eventName,
3020
+ RelayTargetAgentId: details.targetAgentId,
3021
+ });
3022
+
3023
+ const {
3024
+ sessionStorePath,
3025
+ sessionArtifacts,
3026
+ } = await recordRuntimeInboundSessionArtifacts({
3027
+ runtime,
3028
+ currentCfg,
3029
+ localAgentId,
3030
+ sessionKey,
3031
+ ctx: inboundCtx,
3032
+ logger,
3033
+ runtimeAccountId,
3034
+ logLabel: 'management',
3035
+ logContext: {
3036
+ eventType: details.eventType,
3037
+ eventName: details.eventName,
3038
+ },
3039
+ });
3040
+
3041
+ const workspaceRoot = resolveOpenClawWorkspaceRoot({
3042
+ sources: [
3043
+ { agentId: localAgentId, localAgentId },
3044
+ currentCfg,
3045
+ runtimeConfig,
3046
+ ],
3047
+ config: currentCfg,
3048
+ agentId: localAgentId,
3049
+ });
3050
+ let journalResult = null;
3051
+ if (workspaceRoot) {
3052
+ try {
3053
+ journalResult = await appendClaworldJournalEvent(
3054
+ workspaceRoot,
3055
+ buildManagementRuntimeMaintenanceEvent(details, {
3056
+ localSessionKey,
3057
+ localAgentId,
3058
+ sessionArtifacts,
3059
+ workspaceRoot,
3060
+ }),
3061
+ );
3062
+ } catch (error) {
3063
+ logger.warn?.(`[claworld:${runtimeAccountId}] management event journal append failed`, {
3064
+ eventType: details.eventType,
3065
+ eventName: details.eventName,
3066
+ sessionKey,
3067
+ error: error?.message || String(error),
3068
+ });
3069
+ }
3070
+ }
3071
+
3072
+ logger.info?.(`[claworld:${runtimeAccountId}] routing management event into runtime session`, {
3073
+ eventType: details.eventType,
3074
+ eventName: details.eventName,
3075
+ eventId,
3076
+ sessionKey,
3077
+ localSessionKey,
3078
+ localAgentId,
3079
+ sessionId: sessionArtifacts.sessionId || null,
3080
+ sessionFile: sessionArtifacts.sessionFile || null,
3081
+ routeStatus: route?.status || null,
3082
+ journal: journalResult?.ok === true,
3083
+ });
3084
+
3085
+ const {
3086
+ dispatchResult,
3087
+ runtimeOutputSummary,
3088
+ } = await runDeliveryReplyDispatch({
3089
+ runtime,
3090
+ currentCfg,
3091
+ relayClient: null,
3092
+ deliveryId: eventId,
3093
+ sessionKey,
3094
+ localAgentId,
3095
+ allowReply: false,
3096
+ logger,
3097
+ runtimeAccountId,
3098
+ inboundCtx,
3099
+ });
3100
+
3101
+ logger.info?.(`[claworld:${runtimeAccountId}] management event bridge completed`, {
3102
+ eventType: details.eventType,
3103
+ eventName: details.eventName,
3104
+ eventId,
3105
+ sessionKey,
3106
+ localSessionKey,
3107
+ queuedFinal: Boolean(dispatchResult?.queuedFinal),
3108
+ routeStatus: route?.status || null,
3109
+ runtimeOutputSummary,
3110
+ });
3111
+
3112
+ return {
3113
+ skipped: false,
3114
+ ok: true,
3115
+ queuedFinal: Boolean(dispatchResult?.queuedFinal),
3116
+ sessionKey,
3117
+ localSessionKey,
3118
+ routeStatus: route?.status || null,
3119
+ };
3120
+ }
3121
+
2494
3122
  export function createClaworldChannelPlugin({
2495
3123
  logger = console,
2496
3124
  relayClientFactory = createClaworldRelayClient,
@@ -2925,6 +3553,7 @@ export function createClaworldChannelPlugin({
2925
3553
  eventType: event?.eventType || null,
2926
3554
  target: event?.route?.target || null,
2927
3555
  deliveryId: event?.delivery?.deliveryId || null,
3556
+ eventId: event?.delivery?.eventId || null,
2928
3557
  sessionKey: event?.delivery?.sessionKey || null,
2929
3558
  });
2930
3559
 
@@ -2944,6 +3573,21 @@ export function createClaworldChannelPlugin({
2944
3573
  error: error?.message || String(error),
2945
3574
  });
2946
3575
  });
3576
+ } else if (event?.route?.sessionKind === 'management' || event?.delivery?.sessionKey?.startsWith?.('management:')) {
3577
+ const runtimeContext = accountRuntimeContexts.get(accountKey) || {};
3578
+ maybeBridgeRuntimeManagementEvent({
3579
+ runtimeConfig,
3580
+ runtimeAccountId,
3581
+ event,
3582
+ logger,
3583
+ runtime: runtimeContext.runtime,
3584
+ cfg: runtimeContext.cfg,
3585
+ inbound,
3586
+ }).catch((error) => {
3587
+ logger.error?.(`[claworld:${runtimeAccountId}] management event bridge exception`, {
3588
+ error: error?.message || String(error),
3589
+ });
3590
+ });
2947
3591
  }
2948
3592
  });
2949
3593
 
@@ -3086,7 +3730,8 @@ export function createClaworldChannelPlugin({
3086
3730
  }
3087
3731
 
3088
3732
  async function updateRuntimePublicIdentity(context = {}) {
3089
- const resolvedContext = resolveConfiguredRuntimeContext(context);
3733
+ const configuredContext = resolveConfiguredRuntimeContext(context);
3734
+ const resolvedContext = await resolveBoundRuntimeContext(context);
3090
3735
  const updateResult = await updatePublicIdentity({
3091
3736
  runtimeConfig: resolvedContext.runtimeConfig,
3092
3737
  agentId: resolvedContext.agentId || null,
@@ -3116,14 +3761,20 @@ async function updateRuntimePublicIdentity(context = {}) {
3116
3761
  ? applyRuntimeIdentity(nextRuntimeConfig, { agentId: nextAgentId })
3117
3762
  : nextRuntimeConfig;
3118
3763
 
3764
+ const configuredAppToken = resolveRuntimeAppToken(configuredContext.runtimeConfig);
3119
3765
  const previousAgentId = normalizeClaworldText(
3120
- resolvedContext.runtimeConfig?.relay?.agentId,
3121
- normalizeClaworldText(resolvedContext.agentId, null),
3766
+ configuredContext.runtimeConfig?.relay?.agentId,
3767
+ normalizeClaworldText(configuredContext.agentId, null),
3122
3768
  );
3769
+ const nextAppToken = resolveRuntimeAppToken(boundRuntimeConfig);
3123
3770
  const shouldPersistRuntimeBinding = Boolean(
3124
- resolveRuntimeAppToken(nextRuntimeConfig)
3771
+ nextAppToken
3125
3772
  && nextAgentId
3126
- && (runtimeActivation || previousAgentId !== nextAgentId),
3773
+ && (
3774
+ runtimeActivation
3775
+ || configuredAppToken !== nextAppToken
3776
+ || previousAgentId !== nextAgentId
3777
+ ),
3127
3778
  );
3128
3779
 
3129
3780
  if (shouldPersistRuntimeBinding) {
@@ -3132,7 +3783,7 @@ async function updateRuntimePublicIdentity(context = {}) {
3132
3783
  await persistRuntimeAppToken({
3133
3784
  runtime: runtimeResolution.runtime,
3134
3785
  accountId: resolvedContext.accountId || boundRuntimeConfig.accountId || null,
3135
- appToken: resolveRuntimeAppToken(boundRuntimeConfig),
3786
+ appToken: nextAppToken,
3136
3787
  relayAgentId: nextAgentId,
3137
3788
  });
3138
3789
  } catch (error) {
@@ -3145,7 +3796,9 @@ async function updateRuntimePublicIdentity(context = {}) {
3145
3796
  rememberAccountBinding({
3146
3797
  runtimeConfig: boundRuntimeConfig,
3147
3798
  accountId: resolvedContext.accountId || boundRuntimeConfig.accountId || null,
3148
- bindingSource: runtimeActivation ? 'activated_app_token' : 'configured_app_token',
3799
+ bindingSource: runtimeActivation
3800
+ ? 'activated_app_token'
3801
+ : (resolvedContext.bindingSource || 'configured_app_token'),
3149
3802
  });
3150
3803
 
3151
3804
  const accountKey = resolveAccountBindingKey(boundRuntimeConfig, resolvedContext.accountId || null);