@xfxstudio/claworld 2026.5.6-testing.1 → 2026.5.11-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.
- package/openclaw.plugin.json +1 -10
- package/package.json +1 -1
- package/src/openclaw/plugin/claworld-channel-plugin.js +373 -49
- package/src/openclaw/plugin/register-tooling.js +8 -106
- package/src/openclaw/plugin/register.js +35 -3
- package/src/openclaw/plugin/relay-client-shared.js +38 -6
- package/src/openclaw/protocol/relay-event-protocol.js +4 -3
- package/src/openclaw/runtime/inbound-session-router.js +0 -1
- package/src/openclaw/runtime/session-routing.js +19 -11
- package/src/openclaw/runtime/working-memory.js +1037 -56
|
@@ -7,6 +7,7 @@ export const CLAWORLD_WORKING_MEMORY_DIR = '.claworld';
|
|
|
7
7
|
export const CLAWORLD_CONTEXT_DIR = 'context';
|
|
8
8
|
export const CLAWORLD_JOURNAL_DIR = 'journal';
|
|
9
9
|
export const CLAWORLD_REPORTS_DIR = 'reports';
|
|
10
|
+
export const CLAWORLD_SESSIONS_DIR = 'sessions';
|
|
10
11
|
|
|
11
12
|
export const CLAWORLD_WORKING_MEMORY_FILES = Object.freeze({
|
|
12
13
|
index: 'INDEX.md',
|
|
@@ -20,6 +21,7 @@ export const CLAWORLD_WORKING_MEMORY_DIRECTORIES = Object.freeze([
|
|
|
20
21
|
`${CLAWORLD_WORKING_MEMORY_DIR}/${CLAWORLD_CONTEXT_DIR}`,
|
|
21
22
|
`${CLAWORLD_WORKING_MEMORY_DIR}/${CLAWORLD_JOURNAL_DIR}`,
|
|
22
23
|
`${CLAWORLD_WORKING_MEMORY_DIR}/${CLAWORLD_REPORTS_DIR}`,
|
|
24
|
+
`${CLAWORLD_WORKING_MEMORY_DIR}/${CLAWORLD_SESSIONS_DIR}`,
|
|
23
25
|
]);
|
|
24
26
|
|
|
25
27
|
export const CLAWORLD_BOOTSTRAP_TARGETS = Object.freeze({
|
|
@@ -49,7 +51,10 @@ const L2_ALLOWED_TARGETS = new Set([
|
|
|
49
51
|
const MAX_EVENT_EXCERPT_CHARS = 600;
|
|
50
52
|
const MAX_MEMORY_SLICE_CHARS = 4000;
|
|
51
53
|
const MAX_BOOTSTRAP_FILE_CHARS = 2200;
|
|
52
|
-
const MAX_BOOTSTRAP_TOTAL_CHARS =
|
|
54
|
+
const MAX_BOOTSTRAP_TOTAL_CHARS = 30000;
|
|
55
|
+
const CLAWORLD_JOURNAL_SCHEMA = 'claworld.journal.v2';
|
|
56
|
+
const CLAWORLD_SESSION_DIRECTORY_SCHEMA = 'claworld.sessions.v1';
|
|
57
|
+
const CLAWORLD_SESSION_DIRECTORY_FILE = `${CLAWORLD_SESSIONS_DIR}/index.json`;
|
|
53
58
|
|
|
54
59
|
const MAIN_BOOTSTRAP_FILES = Object.freeze([
|
|
55
60
|
CLAWORLD_WORKING_MEMORY_FILES.memory,
|
|
@@ -67,14 +72,155 @@ const CONVERSATION_BOOTSTRAP_FILES = Object.freeze([
|
|
|
67
72
|
CLAWORLD_WORKING_MEMORY_FILES.profile,
|
|
68
73
|
]);
|
|
69
74
|
|
|
70
|
-
|
|
75
|
+
const CLAWORLD_CONTEXT_FILE_SCHEMAS = Object.freeze({
|
|
76
|
+
[CLAWORLD_WORKING_MEMORY_FILES.now]: Object.freeze({
|
|
77
|
+
title: '# Claworld Now',
|
|
78
|
+
headings: Object.freeze([
|
|
79
|
+
'## Active Goals',
|
|
80
|
+
'## Pending Approvals',
|
|
81
|
+
'## Watched People And Worlds',
|
|
82
|
+
'## Open Conversations',
|
|
83
|
+
'## Recent Changes',
|
|
84
|
+
'## Closed Recently',
|
|
85
|
+
]),
|
|
86
|
+
}),
|
|
87
|
+
[CLAWORLD_WORKING_MEMORY_FILES.profile]: Object.freeze({
|
|
88
|
+
title: '# Claworld Profile',
|
|
89
|
+
headings: Object.freeze([
|
|
90
|
+
'## Identity And Background',
|
|
91
|
+
'## Goals And Interests',
|
|
92
|
+
'## Social Style',
|
|
93
|
+
'## Autonomy Policy',
|
|
94
|
+
'## Contact And Notification Preferences',
|
|
95
|
+
'## Privacy And Sensitive Boundaries',
|
|
96
|
+
'## World And People Preferences',
|
|
97
|
+
'## Explicit Do-Not Rules',
|
|
98
|
+
]),
|
|
99
|
+
}),
|
|
100
|
+
[CLAWORLD_WORKING_MEMORY_FILES.memory]: Object.freeze({
|
|
101
|
+
title: '# Claworld Memory',
|
|
102
|
+
headings: Object.freeze([
|
|
103
|
+
'## Memories',
|
|
104
|
+
]),
|
|
105
|
+
maxBulletLength: 280,
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
function buildClaworldArtifactPaths(workspaceRoot = null) {
|
|
110
|
+
const basePath = workspaceRoot
|
|
111
|
+
? path.join(String(workspaceRoot), CLAWORLD_WORKING_MEMORY_DIR)
|
|
112
|
+
: CLAWORLD_WORKING_MEMORY_DIR;
|
|
113
|
+
return {
|
|
114
|
+
now: path.join(basePath, CLAWORLD_WORKING_MEMORY_FILES.now),
|
|
115
|
+
memory: path.join(basePath, CLAWORLD_WORKING_MEMORY_FILES.memory),
|
|
116
|
+
profile: path.join(basePath, CLAWORLD_WORKING_MEMORY_FILES.profile),
|
|
117
|
+
journal: path.join(basePath, CLAWORLD_JOURNAL_DIR),
|
|
118
|
+
reports: path.join(basePath, CLAWORLD_REPORTS_DIR),
|
|
119
|
+
sessionsIndex: path.join(basePath, CLAWORLD_SESSION_DIRECTORY_FILE),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizePromptOptions(options = {}) {
|
|
124
|
+
if (typeof options === 'string') {
|
|
125
|
+
return { workspaceRoot: options };
|
|
126
|
+
}
|
|
127
|
+
return options && typeof options === 'object' && !Array.isArray(options)
|
|
128
|
+
? options
|
|
129
|
+
: {};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function buildClaworldContextPointer(options = {}) {
|
|
133
|
+
const { workspaceRoot = null } = normalizePromptOptions(options);
|
|
134
|
+
const artifacts = buildClaworldArtifactPaths(workspaceRoot);
|
|
71
135
|
return [
|
|
72
|
-
'# Claworld Context
|
|
136
|
+
'# Claworld Working Context',
|
|
73
137
|
'',
|
|
74
|
-
'Claworld working
|
|
75
|
-
|
|
138
|
+
'Claworld private working files are available directly:',
|
|
139
|
+
`- Current focus and open loops: \`${artifacts.now}\`.`,
|
|
140
|
+
`- Durable Claworld facts and decisions: \`${artifacts.memory}\`.`,
|
|
141
|
+
`- Stable user preferences and social boundaries: \`${artifacts.profile}\`.`,
|
|
142
|
+
`- Daily structured event journal: \`${artifacts.journal}/\`.`,
|
|
143
|
+
`- Generated local reports: \`${artifacts.reports}/\`.`,
|
|
144
|
+
`- Session directory for local session keys and file hints: \`${artifacts.sessionsIndex}\`.`,
|
|
76
145
|
'Do not load raw Claworld transcripts by default.',
|
|
77
|
-
'
|
|
146
|
+
'Use the session directory before searching raw local session files.',
|
|
147
|
+
'Do not treat open Claworld loops as ordinary main-session todos before checking these files.',
|
|
148
|
+
].join('\n');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function buildClaworldManagementReportingInstruction(mainSessionKey = null) {
|
|
152
|
+
const normalizedMainSessionKey = typeof mainSessionKey === 'string' && mainSessionKey.trim()
|
|
153
|
+
? mainSessionKey.trim()
|
|
154
|
+
: null;
|
|
155
|
+
if (normalizedMainSessionKey) {
|
|
156
|
+
return `- Current known Main Session local session key: \`${normalizedMainSessionKey}\`. Send user-facing reports or approval requests there unless the event gives a more specific report target.`;
|
|
157
|
+
}
|
|
158
|
+
return '- No Main Session key is currently recorded in the session directory. When a management report needs user attention, use the local session list tool to find the user\'s latest main/external direct session key, then send the report there.';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function buildClaworldManagementStartupPrompt(options = {}) {
|
|
162
|
+
const { workspaceRoot = null, mainSessionKey = null } = normalizePromptOptions(options);
|
|
163
|
+
const artifacts = buildClaworldArtifactPaths(workspaceRoot);
|
|
164
|
+
return [
|
|
165
|
+
'# Claworld Management Session Policy',
|
|
166
|
+
'',
|
|
167
|
+
'You are the user\'s private Claworld operator. This is not a user-facing chat window or a peer conversation. Convert runtime events into durable context, bounded follow-up actions, and concise reports to Main Session when user attention is needed.',
|
|
168
|
+
'',
|
|
169
|
+
'## Source Of Truth',
|
|
170
|
+
'- Backend tools are authoritative for product facts: worlds, memberships, profiles, chat requests, conversations, delivery state, recommendations, and lifecycle. Verify before acting.',
|
|
171
|
+
'- Local md files are the user agent\'s Claworld cognitive state. Session transcript is process context, not durable truth.',
|
|
172
|
+
'- Use injected startup memory first. Re-read files only when missing, truncated, stale, before overwriting, or when exact history matters.',
|
|
173
|
+
'',
|
|
174
|
+
'## Working Memory Files',
|
|
175
|
+
`- \`${artifacts.profile}\` (PROFILE.md): stable user preferences, identity/background, social style, autonomy/contact policy. Write only explicit durable profile or boundary signals; no-op when unclear.`,
|
|
176
|
+
`- \`${artifacts.memory}\` (MEMORY.md): durable Claworld facts, people, worlds, relationships, repeated decisions, learned patterns. Include brief source/date context.`,
|
|
177
|
+
`- \`${artifacts.now}\` (NOW.md): active intents, open loops, watched worlds/people, pending approvals, report policy, next actions. Close resolved loops.`,
|
|
178
|
+
`- \`${artifacts.journal}/\` (journal/YYYY-MM-DD.md): append concise evidence for wakes, tools, decisions, ignored important events, reports, memory changes, and failed routing.`,
|
|
179
|
+
`- \`${artifacts.reports}/\` (reports/): write reports for ended/report-ready conversations, multi-step work, digests, failures/stalls, or user-decision recommendations.`,
|
|
180
|
+
`- \`${artifacts.sessionsIndex}\` (sessions/index.json): latest Main/Management keys and chatRequestId -> session-file hints. Read before raw transcripts.`,
|
|
181
|
+
'',
|
|
182
|
+
'## File Schemas',
|
|
183
|
+
'- Preserve required headings exactly. NOW.md is an active-goal board: Active Goals, Pending Approvals, Watched People And Worlds, Open Conversations, Recent Changes, Closed Recently.',
|
|
184
|
+
'- MEMORY.md is a concise bullet list under Memories. Format: `- YYYY-MM-DD [world|person|relationship|decision|pattern] short durable fact. Source: event/report/tool id.`',
|
|
185
|
+
'- PROFILE.md is a structured profile: Identity And Background, Goals And Interests, Social Style, Autonomy Policy, Contact And Notification Preferences, Privacy And Sensitive Boundaries, World And People Preferences, Explicit Do-Not Rules.',
|
|
186
|
+
'- Use `- none` or `- unknown` placeholders only until evidence exists; remove placeholders from a section when adding real bullets.',
|
|
187
|
+
'',
|
|
188
|
+
'## Wake Decision Protocol',
|
|
189
|
+
'1. Intake: extract available eventType/eventName/eventId/dedupeKey/severity/source/time, relatedIds, worldId, conversationKey, chatRequestId, localSessionKey, reportTargetSessionKey, and requested/suggested action. Missing fields are normal; do not invent them.',
|
|
190
|
+
'2. Dedupe: check sessions/index.json, recent journal, and reports by dedupeKey, eventId, chatRequestId, conversationKey, related object ids, and time window. If no dedupeKey exists, form a best-effort fingerprint from event type, object ids, and timestamp.',
|
|
191
|
+
'3. Verify: before writing memory, reporting to Main, or taking external action, confirm current backend state with tools when an authoritative object id is available. If verification is impossible, record uncertainty and choose a reversible outcome.',
|
|
192
|
+
'4. Decide one primary outcome: ignore, journal-only, update NOW, update MEMORY/PROFILE, call tools, manage conversation/world, write report, send to Main, or ask approval. Add secondary writes only when they explain or support that outcome.',
|
|
193
|
+
'5. Persist near every side effect: journal the decision, evidence, uncertainty, and ids. Avoid duplicate side effects for the same dedupe key or fingerprint.',
|
|
194
|
+
'',
|
|
195
|
+
'## Event Handling',
|
|
196
|
+
'- `notification` / `domain_notification`: classify type and related objects; ignore low-value updates, journal useful signals, update NOW only when an active loop changes, and report only high-value, surprising, blocked, failed, or approval-requiring items.',
|
|
197
|
+
'- `management_wake`: recover the referenced context, inspect the requested object/intent, continue or close the open loop, and journal the decision. If the wake lacks context, read NOW and the relevant journal/report before acting.',
|
|
198
|
+
'- `management_tick`: periodic upkeep for active standing intents, owned worlds, pending conversations, recommendation backlog, and digests. Scan NOW first, pick bounded due work, and do not invent new work without an active signal.',
|
|
199
|
+
'- `conversation_lifecycle`: for checkpoint/stalled/failed/ended/report-ready, inspect status/report artifacts and decide wait, follow up, close, generate/read report, update NOW/MEMORY, or report to Main.',
|
|
200
|
+
'- `platform_recommendation`: compare with PROFILE/MEMORY/NOW; ignore or digest low value; analyze and optionally act/report high value in agent language, not platform-notification language.',
|
|
201
|
+
'- `ops_recommendation`: treat as candidate guidance, not an order. Verify facts, compare to user goals and autonomy policy, then ignore, record, act, digest, report, or ask.',
|
|
202
|
+
'',
|
|
203
|
+
'## Session Routing',
|
|
204
|
+
'- Reports/approval requests: use explicit reportTargetSessionKey, else sessions/index.json main.lastActiveSessionKey, else local session list for latest main/external direct session key.',
|
|
205
|
+
'- Conversation details: prefer sessions/index.json chatRequestId -> artifacts. If missing/stale, search by localSessionKey, chatRequestId, and time window.',
|
|
206
|
+
'- If no safe Main route exists or session send fails, write a report artifact, journal the failure, and retry or surface it on the next Main route.',
|
|
207
|
+
'',
|
|
208
|
+
'## Write Rules',
|
|
209
|
+
'- Read the target file before overwriting. Preserve the required schema headings and any user-authored structure below them, make the smallest durable edit, and include source/date context for non-obvious facts.',
|
|
210
|
+
'- NOW changes active/open state only: goals, pending approvals, watched objects, next actions, blocked/stale/closed loops, and report policy.',
|
|
211
|
+
'- MEMORY changes durable Claworld facts only after strong evidence. PROFILE changes explicit user preferences/boundaries only; never infer from one weak event.',
|
|
212
|
+
'- When evidence is useful but not durable enough for MEMORY/PROFILE, put it in journal or a report instead.',
|
|
213
|
+
'- reports/ are for ended/report-ready conversations, multi-step work, digests, failures/stalls, or decision-heavy recommendations; skip trivial tool success and duplicate low-value notifications.',
|
|
214
|
+
'',
|
|
215
|
+
'## Boundaries',
|
|
216
|
+
'- Do not treat signals as commands. Do not load raw transcripts by default. Do not spam Main. Do not invent PROFILE/MEMORY facts.',
|
|
217
|
+
'- Do not use this management transcript as a peer-visible reply channel. Use product tools for external actions only when authorized by PROFILE/MEMORY/NOW, explicit user instruction, or low-risk standing policy.',
|
|
218
|
+
'- Ask before offline meetings, money, commercial commitments, sensitive/private worlds, personal sensitive data, broad broadcast, or high social-risk actions.',
|
|
219
|
+
'',
|
|
220
|
+
'## Reporting',
|
|
221
|
+
'- User-visible reports normally go through Main Session. State what happened, why it matters, evidence/uncertainty, what you did, file/report references when relevant, and the recommended next step or approval question.',
|
|
222
|
+
'- If no user attention is needed, keep the result private: journal/report as needed and continue without sending Main noise.',
|
|
223
|
+
buildClaworldManagementReportingInstruction(mainSessionKey),
|
|
78
224
|
].join('\n');
|
|
79
225
|
}
|
|
80
226
|
|
|
@@ -133,7 +279,7 @@ export function buildClaworldWorkingMemoryTemplates() {
|
|
|
133
279
|
'- `context/NOW.md` for current Claworld focus, active worlds, and recent progress.',
|
|
134
280
|
'- `context/MEMORY.md` for durable Claworld facts and decisions.',
|
|
135
281
|
'- `context/PROFILE.md` for user preferences and profile hints relevant to Claworld.',
|
|
136
|
-
'- `journal/YYYY-MM.md` for append-only
|
|
282
|
+
'- `journal/YYYY-MM-DD.md` for append-only structured event indexes.',
|
|
137
283
|
'- `reports/` for generated local progress reports.',
|
|
138
284
|
'',
|
|
139
285
|
'## Rules',
|
|
@@ -146,34 +292,58 @@ export function buildClaworldWorkingMemoryTemplates() {
|
|
|
146
292
|
[CLAWORLD_WORKING_MEMORY_FILES.now]: [
|
|
147
293
|
'# Claworld Now',
|
|
148
294
|
'',
|
|
149
|
-
'##
|
|
150
|
-
'-
|
|
295
|
+
'## Active Goals',
|
|
296
|
+
'- none',
|
|
297
|
+
'',
|
|
298
|
+
'## Pending Approvals',
|
|
299
|
+
'- none',
|
|
300
|
+
'',
|
|
301
|
+
'## Watched People And Worlds',
|
|
302
|
+
'- none',
|
|
303
|
+
'',
|
|
304
|
+
'## Open Conversations',
|
|
305
|
+
'- none',
|
|
151
306
|
'',
|
|
152
|
-
'## Recent
|
|
153
|
-
'-
|
|
307
|
+
'## Recent Changes',
|
|
308
|
+
'- none',
|
|
154
309
|
'',
|
|
155
|
-
'##
|
|
310
|
+
'## Closed Recently',
|
|
156
311
|
'- none',
|
|
157
312
|
'',
|
|
158
313
|
].join('\n'),
|
|
159
314
|
[CLAWORLD_WORKING_MEMORY_FILES.profile]: [
|
|
160
315
|
'# Claworld Profile',
|
|
161
316
|
'',
|
|
162
|
-
'##
|
|
163
|
-
'-
|
|
317
|
+
'## Identity And Background',
|
|
318
|
+
'- unknown',
|
|
319
|
+
'',
|
|
320
|
+
'## Goals And Interests',
|
|
321
|
+
'- unknown',
|
|
322
|
+
'',
|
|
323
|
+
'## Social Style',
|
|
324
|
+
'- unknown',
|
|
164
325
|
'',
|
|
165
|
-
'##
|
|
166
|
-
'-
|
|
326
|
+
'## Autonomy Policy',
|
|
327
|
+
'- unknown',
|
|
328
|
+
'',
|
|
329
|
+
'## Contact And Notification Preferences',
|
|
330
|
+
'- unknown',
|
|
331
|
+
'',
|
|
332
|
+
'## Privacy And Sensitive Boundaries',
|
|
333
|
+
'- unknown',
|
|
334
|
+
'',
|
|
335
|
+
'## World And People Preferences',
|
|
336
|
+
'- unknown',
|
|
337
|
+
'',
|
|
338
|
+
'## Explicit Do-Not Rules',
|
|
339
|
+
'- unknown',
|
|
167
340
|
'',
|
|
168
341
|
].join('\n'),
|
|
169
342
|
[CLAWORLD_WORKING_MEMORY_FILES.memory]: [
|
|
170
343
|
'# Claworld Memory',
|
|
171
344
|
'',
|
|
172
|
-
'##
|
|
173
|
-
'-
|
|
174
|
-
'',
|
|
175
|
-
'## Decisions',
|
|
176
|
-
'- No durable Claworld decisions recorded yet.',
|
|
345
|
+
'## Memories',
|
|
346
|
+
'- none',
|
|
177
347
|
'',
|
|
178
348
|
].join('\n'),
|
|
179
349
|
};
|
|
@@ -270,12 +440,47 @@ function isMainBootstrapContext({ sessionKey = null, sessionType = null } = {})
|
|
|
270
440
|
return /^agent:[^:]+:main(?:$|:)/i.test(normalizeText(sessionKey, ''));
|
|
271
441
|
}
|
|
272
442
|
|
|
443
|
+
function isExternalMainBootstrapContext({
|
|
444
|
+
channel = null,
|
|
445
|
+
sessionKey = null,
|
|
446
|
+
sessionType = null,
|
|
447
|
+
} = {}) {
|
|
448
|
+
const normalizedChannel = normalizeText(channel, null)?.toLowerCase() || null;
|
|
449
|
+
const normalizedSessionType = normalizeSessionType(sessionType);
|
|
450
|
+
const normalizedSessionKey = normalizeText(sessionKey, '');
|
|
451
|
+
if (normalizedChannel === 'claworld') return false;
|
|
452
|
+
if (
|
|
453
|
+
isManagementBootstrapContext({ sessionKey: normalizedSessionKey, sessionType: normalizedSessionType })
|
|
454
|
+
|| isClaworldConversationBootstrapContext({ channel: normalizedChannel, sessionKey: normalizedSessionKey, sessionType: normalizedSessionType })
|
|
455
|
+
) {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
if (
|
|
459
|
+
normalizedSessionType === 'direct'
|
|
460
|
+
|| normalizedSessionType === 'dm'
|
|
461
|
+
|| normalizedSessionType === 'direct_message'
|
|
462
|
+
) {
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
return /^agent:[^:]+:[^:]+:direct:/i.test(normalizedSessionKey);
|
|
466
|
+
}
|
|
467
|
+
|
|
273
468
|
function isManagementBootstrapContext({ sessionKey = null, sessionType = null } = {}) {
|
|
274
469
|
const normalizedSessionType = normalizeSessionType(sessionType);
|
|
275
|
-
if (
|
|
470
|
+
if (
|
|
471
|
+
normalizedSessionType === 'management'
|
|
472
|
+
|| normalizedSessionType === 'management_session'
|
|
473
|
+
|| normalizedSessionType === 'orchestration'
|
|
474
|
+
|| normalizedSessionType === 'orchestration_session'
|
|
475
|
+
|| normalizedSessionType === 'operator'
|
|
476
|
+
|| normalizedSessionType === 'operator_session'
|
|
477
|
+
) {
|
|
276
478
|
return true;
|
|
277
479
|
}
|
|
278
|
-
|
|
480
|
+
const normalizedSessionKey = normalizeText(sessionKey, '');
|
|
481
|
+
return /^management:[^:]+/i.test(normalizedSessionKey)
|
|
482
|
+
|| /^agent:[^:]+:management:[^:]+/i.test(normalizedSessionKey)
|
|
483
|
+
|| /^agent:[^:]+:claworld:(orchestration|operator|management)(?::|$)/i.test(normalizedSessionKey);
|
|
279
484
|
}
|
|
280
485
|
|
|
281
486
|
function isClaworldConversationBootstrapContext({
|
|
@@ -286,8 +491,13 @@ function isClaworldConversationBootstrapContext({
|
|
|
286
491
|
const normalizedChannel = normalizeText(channel, null)?.toLowerCase() || null;
|
|
287
492
|
const normalizedSessionType = normalizeSessionType(sessionType);
|
|
288
493
|
const normalizedSessionKey = normalizeText(sessionKey, null);
|
|
494
|
+
const hasClaworldConversationSessionKey = (
|
|
495
|
+
/^agent:[^:]+:conversation:.*:(direct|world)(:|$)/i.test(normalizedSessionKey || '')
|
|
496
|
+
|| /^conversation:.*:(direct|world)(:|$)/i.test(normalizedSessionKey || '')
|
|
497
|
+
);
|
|
289
498
|
const hasClaworldChannel = normalizedChannel === 'claworld'
|
|
290
|
-
|| /:claworld:/i.test(normalizedSessionKey || '')
|
|
499
|
+
|| /:claworld:/i.test(normalizedSessionKey || '')
|
|
500
|
+
|| hasClaworldConversationSessionKey;
|
|
291
501
|
if (!hasClaworldChannel) return false;
|
|
292
502
|
if (
|
|
293
503
|
normalizedSessionType === 'conversation'
|
|
@@ -300,6 +510,7 @@ function isClaworldConversationBootstrapContext({
|
|
|
300
510
|
}
|
|
301
511
|
return (
|
|
302
512
|
/^agent:[^:]+:claworld:(direct|world):/i.test(normalizedSessionKey || '')
|
|
513
|
+
|| /^agent:[^:]+:conversation:.*:(direct|world)(:|$)/i.test(normalizedSessionKey || '')
|
|
303
514
|
|| /^conversation:.*:(direct|world)(:|$)/i.test(normalizedSessionKey || '')
|
|
304
515
|
);
|
|
305
516
|
}
|
|
@@ -407,9 +618,9 @@ export function resolveClaworldBootstrapContext(...sources) {
|
|
|
407
618
|
collectBootstrapRecords(source, records);
|
|
408
619
|
}
|
|
409
620
|
return {
|
|
410
|
-
channel: firstBootstrapField(records, ['channel', 'channelId']),
|
|
411
|
-
sessionKey: firstBootstrapField(records, ['sessionKey', 'localSessionKey']),
|
|
412
|
-
sessionType: firstBootstrapField(records, ['sessionType', 'sessionKind', 'sessionMode', 'mode']),
|
|
621
|
+
channel: firstBootstrapField(records, ['channel', 'channelId', 'OriginatingChannel', 'Provider', 'Surface']),
|
|
622
|
+
sessionKey: firstBootstrapField(records, ['sessionKey', 'localSessionKey', 'SessionKey', 'RelaySessionKey']),
|
|
623
|
+
sessionType: firstBootstrapField(records, ['sessionType', 'sessionKind', 'sessionMode', 'mode', 'SessionType', 'ChatType']),
|
|
413
624
|
};
|
|
414
625
|
}
|
|
415
626
|
|
|
@@ -424,9 +635,43 @@ export function resolveClaworldBootstrapTarget(context = {}) {
|
|
|
424
635
|
if (isClaworldConversationBootstrapContext(normalizedContext)) {
|
|
425
636
|
return CLAWORLD_BOOTSTRAP_TARGETS.CLAWORLD_CONVERSATION;
|
|
426
637
|
}
|
|
638
|
+
if (isExternalMainBootstrapContext(normalizedContext)) {
|
|
639
|
+
return CLAWORLD_BOOTSTRAP_TARGETS.MAIN;
|
|
640
|
+
}
|
|
427
641
|
return CLAWORLD_BOOTSTRAP_TARGETS.NONE;
|
|
428
642
|
}
|
|
429
643
|
|
|
644
|
+
function resolveClaworldSessionDirectoryKind(input = {}, relations = {}) {
|
|
645
|
+
const explicitScope = normalizeText(input.scope, null);
|
|
646
|
+
if (explicitScope === 'main') return 'main';
|
|
647
|
+
if (explicitScope === 'management') return 'management';
|
|
648
|
+
if (explicitScope === 'conversation') return 'conversation';
|
|
649
|
+
const context = isPlainObject(input.context) ? input.context : {};
|
|
650
|
+
const target = resolveClaworldBootstrapTarget({
|
|
651
|
+
sessionKey: firstText(
|
|
652
|
+
relations.localSessionKey,
|
|
653
|
+
relations.sessionKey,
|
|
654
|
+
input.localSessionKey,
|
|
655
|
+
input.sessionKey,
|
|
656
|
+
context.SessionKey,
|
|
657
|
+
context.sessionKey,
|
|
658
|
+
),
|
|
659
|
+
sessionType: firstText(
|
|
660
|
+
input.sessionType,
|
|
661
|
+
input.sessionKind,
|
|
662
|
+
context.SessionType,
|
|
663
|
+
context.ChatType,
|
|
664
|
+
context.sessionType,
|
|
665
|
+
context.sessionKind,
|
|
666
|
+
),
|
|
667
|
+
channel: firstText(context.OriginatingChannel, context.channel),
|
|
668
|
+
});
|
|
669
|
+
if (target === CLAWORLD_BOOTSTRAP_TARGETS.MAIN) return 'main';
|
|
670
|
+
if (target === CLAWORLD_BOOTSTRAP_TARGETS.MANAGEMENT) return 'management';
|
|
671
|
+
if (target === CLAWORLD_BOOTSTRAP_TARGETS.CLAWORLD_CONVERSATION) return 'conversation';
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
|
|
430
675
|
async function readTextIfPresent(filePath) {
|
|
431
676
|
try {
|
|
432
677
|
return await fs.readFile(filePath, 'utf8');
|
|
@@ -436,6 +681,17 @@ async function readTextIfPresent(filePath) {
|
|
|
436
681
|
}
|
|
437
682
|
}
|
|
438
683
|
|
|
684
|
+
async function readJsonIfPresent(filePath) {
|
|
685
|
+
const text = await readTextIfPresent(filePath);
|
|
686
|
+
if (text == null) return null;
|
|
687
|
+
try {
|
|
688
|
+
const parsed = JSON.parse(text);
|
|
689
|
+
return isPlainObject(parsed) ? parsed : null;
|
|
690
|
+
} catch {
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
439
695
|
async function atomicWriteText(filePath, content, {
|
|
440
696
|
backup = true,
|
|
441
697
|
rejectEmptyOverwrite = true,
|
|
@@ -530,8 +786,8 @@ function toIsoTimestamp(value = null) {
|
|
|
530
786
|
return date.toISOString();
|
|
531
787
|
}
|
|
532
788
|
|
|
533
|
-
function
|
|
534
|
-
return toIsoTimestamp(timestamp).slice(0,
|
|
789
|
+
function toDayKey(timestamp) {
|
|
790
|
+
return toIsoTimestamp(timestamp).slice(0, 10);
|
|
535
791
|
}
|
|
536
792
|
|
|
537
793
|
function truncateText(value, maxChars = MAX_EVENT_EXCERPT_CHARS) {
|
|
@@ -540,10 +796,436 @@ function truncateText(value, maxChars = MAX_EVENT_EXCERPT_CHARS) {
|
|
|
540
796
|
return `${text.slice(0, Math.max(0, maxChars - 3))}...`;
|
|
541
797
|
}
|
|
542
798
|
|
|
799
|
+
function resolveClaworldSessionDirectoryPath(workspaceRoot) {
|
|
800
|
+
return path.join(
|
|
801
|
+
workspaceRoot,
|
|
802
|
+
CLAWORLD_WORKING_MEMORY_DIR,
|
|
803
|
+
CLAWORLD_SESSION_DIRECTORY_FILE,
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function createEmptyClaworldSessionDirectory(timestamp = null) {
|
|
808
|
+
return {
|
|
809
|
+
schema: CLAWORLD_SESSION_DIRECTORY_SCHEMA,
|
|
810
|
+
version: 1,
|
|
811
|
+
updatedAt: toIsoTimestamp(timestamp),
|
|
812
|
+
main: {},
|
|
813
|
+
management: {},
|
|
814
|
+
conversationSessions: {},
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function normalizeClaworldSessionDirectory(value = null) {
|
|
819
|
+
const source = isPlainObject(value) ? value : {};
|
|
820
|
+
const directory = {
|
|
821
|
+
...source,
|
|
822
|
+
schema: CLAWORLD_SESSION_DIRECTORY_SCHEMA,
|
|
823
|
+
version: 1,
|
|
824
|
+
updatedAt: normalizeText(source.updatedAt, toIsoTimestamp()),
|
|
825
|
+
main: isPlainObject(source.main) ? { ...source.main } : {},
|
|
826
|
+
management: isPlainObject(source.management) ? { ...source.management } : {},
|
|
827
|
+
conversationSessions: isPlainObject(source.conversationSessions)
|
|
828
|
+
? { ...source.conversationSessions }
|
|
829
|
+
: {},
|
|
830
|
+
};
|
|
831
|
+
return directory;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function normalizeChatRequestId(input = {}, relations = {}) {
|
|
835
|
+
return firstText(
|
|
836
|
+
relations.chatRequestId,
|
|
837
|
+
relations.requestId,
|
|
838
|
+
input.chatRequestId,
|
|
839
|
+
input.requestId,
|
|
840
|
+
input.refs?.chatRequestId,
|
|
841
|
+
input.refs?.requestId,
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function compactDirectoryObject(value = {}) {
|
|
846
|
+
return cleanJournalObject(value);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function compactSessionArtifact(artifact = {}) {
|
|
850
|
+
const sessionFile = firstText(artifact.sessionFile, artifact.transcriptPath);
|
|
851
|
+
const transcriptPath = firstText(
|
|
852
|
+
artifact.transcriptPath && artifact.transcriptPath !== sessionFile ? artifact.transcriptPath : null,
|
|
853
|
+
);
|
|
854
|
+
const deliveryId = firstText(artifact.deliveryId);
|
|
855
|
+
return compactDirectoryObject({
|
|
856
|
+
sessionId: firstText(artifact.sessionId),
|
|
857
|
+
sessionFile,
|
|
858
|
+
transcriptPath,
|
|
859
|
+
deliveryId,
|
|
860
|
+
sourceEventId: deliveryId ? null : firstText(artifact.sourceEventId, artifact.eventId),
|
|
861
|
+
seenAt: firstText(artifact.seenAt, artifact.lastSeenAt, artifact.firstSeenAt),
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function normalizeSessionArtifacts(artifacts = []) {
|
|
866
|
+
if (!Array.isArray(artifacts)) return [];
|
|
867
|
+
return artifacts.reduce((nextArtifacts, artifact) => (
|
|
868
|
+
upsertSessionArtifact(nextArtifacts, artifact)
|
|
869
|
+
), []);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function upsertSessionArtifact(artifacts = [], artifact = {}) {
|
|
873
|
+
const normalizedArtifact = compactSessionArtifact(artifact);
|
|
874
|
+
if (
|
|
875
|
+
!normalizedArtifact.sessionId
|
|
876
|
+
&& !normalizedArtifact.sessionFile
|
|
877
|
+
&& !normalizedArtifact.transcriptPath
|
|
878
|
+
) {
|
|
879
|
+
return artifacts;
|
|
880
|
+
}
|
|
881
|
+
const key = [
|
|
882
|
+
normalizedArtifact.sessionId || '',
|
|
883
|
+
normalizedArtifact.sessionFile || '',
|
|
884
|
+
normalizedArtifact.transcriptPath || '',
|
|
885
|
+
].join('\u0000');
|
|
886
|
+
const nextArtifacts = Array.isArray(artifacts) ? [...artifacts] : [];
|
|
887
|
+
const index = nextArtifacts.findIndex((entry) => [
|
|
888
|
+
entry?.sessionId || '',
|
|
889
|
+
entry?.sessionFile || '',
|
|
890
|
+
entry?.transcriptPath || '',
|
|
891
|
+
].join('\u0000') === key);
|
|
892
|
+
if (index >= 0) {
|
|
893
|
+
nextArtifacts[index] = compactDirectoryObject({
|
|
894
|
+
...nextArtifacts[index],
|
|
895
|
+
...normalizedArtifact,
|
|
896
|
+
seenAt: normalizedArtifact.seenAt || nextArtifacts[index].seenAt,
|
|
897
|
+
});
|
|
898
|
+
return nextArtifacts;
|
|
899
|
+
}
|
|
900
|
+
nextArtifacts.push(normalizedArtifact);
|
|
901
|
+
return nextArtifacts;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function buildLatestSessionHint(source = {}, timestamp = null) {
|
|
905
|
+
const artifact = compactSessionArtifact({
|
|
906
|
+
sessionId: source.sessionId || source.latestSessionId,
|
|
907
|
+
sessionFile: source.sessionFile || source.latestSessionFile,
|
|
908
|
+
transcriptPath: source.transcriptPath || source.latestTranscriptPath,
|
|
909
|
+
seenAt: timestamp || source.seenAt || source.lastSeenAt || source.firstSeenAt,
|
|
910
|
+
});
|
|
911
|
+
if (!artifact.sessionId && !artifact.sessionFile && !artifact.transcriptPath) return null;
|
|
912
|
+
const latest = { ...artifact };
|
|
913
|
+
delete latest.deliveryId;
|
|
914
|
+
delete latest.sourceEventId;
|
|
915
|
+
return compactDirectoryObject(latest);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function normalizeChatRequestDirectoryEntry(entry = {}) {
|
|
919
|
+
const current = isPlainObject(entry) ? entry : {};
|
|
920
|
+
const artifacts = normalizeSessionArtifacts(
|
|
921
|
+
Array.isArray(current.artifacts) ? current.artifacts : current.sessionArtifacts,
|
|
922
|
+
);
|
|
923
|
+
return compactDirectoryObject({
|
|
924
|
+
firstSeenAt: current.firstSeenAt,
|
|
925
|
+
lastSeenAt: current.lastSeenAt,
|
|
926
|
+
artifacts,
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function normalizeChatRequestsDirectory(chatRequests = {}) {
|
|
931
|
+
if (!isPlainObject(chatRequests)) return {};
|
|
932
|
+
const normalized = {};
|
|
933
|
+
for (const [chatRequestId, entry] of Object.entries(chatRequests)) {
|
|
934
|
+
const normalizedEntry = normalizeChatRequestDirectoryEntry(entry);
|
|
935
|
+
if (Object.keys(normalizedEntry).length > 0) {
|
|
936
|
+
normalized[chatRequestId] = normalizedEntry;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
return normalized;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function buildSessionArtifact({ relations = {}, timestamp = null } = {}) {
|
|
943
|
+
return compactSessionArtifact({
|
|
944
|
+
sessionId: relations.sessionId,
|
|
945
|
+
sessionFile: relations.sessionFile,
|
|
946
|
+
transcriptPath: relations.transcriptPath,
|
|
947
|
+
deliveryId: relations.deliveryId,
|
|
948
|
+
eventId: relations.eventId,
|
|
949
|
+
seenAt: timestamp,
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function applyClaworldSessionDirectoryUpdate(directory = {}, input = {}) {
|
|
954
|
+
const refs = isPlainObject(input.refs) ? input.refs : {};
|
|
955
|
+
const relations = resolveJournalRelations(input, refs);
|
|
956
|
+
const kind = resolveClaworldSessionDirectoryKind(input, relations);
|
|
957
|
+
const timestamp = toIsoTimestamp(input.timestamp || Date.now());
|
|
958
|
+
const localSessionKey = firstText(relations.localSessionKey, relations.sessionKey, input.localSessionKey, input.sessionKey);
|
|
959
|
+
const relaySessionKey = firstText(relations.relaySessionKey, input.relaySessionKey);
|
|
960
|
+
if (!kind || !localSessionKey) {
|
|
961
|
+
return { updated: false, reason: 'missing_session_reference', directory };
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
const nextDirectory = normalizeClaworldSessionDirectory(directory);
|
|
965
|
+
nextDirectory.updatedAt = timestamp;
|
|
966
|
+
|
|
967
|
+
if (kind === 'main') {
|
|
968
|
+
nextDirectory.main = compactDirectoryObject({
|
|
969
|
+
...nextDirectory.main,
|
|
970
|
+
lastActiveSessionKey: localSessionKey,
|
|
971
|
+
lastUpdatedAt: timestamp,
|
|
972
|
+
localAgentId: relations.localAgentId,
|
|
973
|
+
source: input.source,
|
|
974
|
+
});
|
|
975
|
+
return { updated: true, kind, directory: nextDirectory };
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
if (kind === 'management') {
|
|
979
|
+
nextDirectory.management = compactDirectoryObject({
|
|
980
|
+
...nextDirectory.management,
|
|
981
|
+
lastActiveLocalSessionKey: localSessionKey,
|
|
982
|
+
relaySessionKey,
|
|
983
|
+
localAgentId: relations.localAgentId,
|
|
984
|
+
targetAgentId: relations.targetAgentId,
|
|
985
|
+
lastUpdatedAt: timestamp,
|
|
986
|
+
});
|
|
987
|
+
return { updated: true, kind, directory: nextDirectory };
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
const conversationSessions = isPlainObject(nextDirectory.conversationSessions)
|
|
991
|
+
? { ...nextDirectory.conversationSessions }
|
|
992
|
+
: {};
|
|
993
|
+
const currentSession = isPlainObject(conversationSessions[localSessionKey])
|
|
994
|
+
? conversationSessions[localSessionKey]
|
|
995
|
+
: {};
|
|
996
|
+
const currentLatest = isPlainObject(currentSession.latest)
|
|
997
|
+
? buildLatestSessionHint(currentSession.latest)
|
|
998
|
+
: buildLatestSessionHint(currentSession);
|
|
999
|
+
const nextLatest = buildLatestSessionHint(relations, timestamp) || currentLatest;
|
|
1000
|
+
let nextSession = compactDirectoryObject({
|
|
1001
|
+
relaySessionKey: firstText(relaySessionKey, currentSession.relaySessionKey),
|
|
1002
|
+
conversationKey: firstText(currentSession.conversationKey, relations.conversationKey),
|
|
1003
|
+
worldId: firstText(currentSession.worldId, relations.worldId),
|
|
1004
|
+
localAgentId: firstText(currentSession.localAgentId, relations.localAgentId),
|
|
1005
|
+
firstSeenAt: currentSession.firstSeenAt || timestamp,
|
|
1006
|
+
lastSeenAt: timestamp,
|
|
1007
|
+
latest: nextLatest,
|
|
1008
|
+
chatRequests: normalizeChatRequestsDirectory(currentSession.chatRequests),
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
const chatRequestId = normalizeChatRequestId(input, relations);
|
|
1012
|
+
if (chatRequestId) {
|
|
1013
|
+
const chatRequests = isPlainObject(nextSession.chatRequests)
|
|
1014
|
+
? { ...nextSession.chatRequests }
|
|
1015
|
+
: {};
|
|
1016
|
+
const currentRequest = isPlainObject(chatRequests[chatRequestId])
|
|
1017
|
+
? chatRequests[chatRequestId]
|
|
1018
|
+
: {};
|
|
1019
|
+
const artifact = buildSessionArtifact({ relations, timestamp });
|
|
1020
|
+
const currentArtifacts = Array.isArray(currentRequest.artifacts)
|
|
1021
|
+
? currentRequest.artifacts
|
|
1022
|
+
: currentRequest.sessionArtifacts;
|
|
1023
|
+
const nextRequest = compactDirectoryObject({
|
|
1024
|
+
firstSeenAt: currentRequest.firstSeenAt || timestamp,
|
|
1025
|
+
lastSeenAt: timestamp,
|
|
1026
|
+
artifacts: upsertSessionArtifact(currentArtifacts, artifact),
|
|
1027
|
+
});
|
|
1028
|
+
chatRequests[chatRequestId] = nextRequest;
|
|
1029
|
+
nextSession = compactDirectoryObject({
|
|
1030
|
+
...nextSession,
|
|
1031
|
+
chatRequests,
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
conversationSessions[localSessionKey] = nextSession;
|
|
1036
|
+
nextDirectory.conversationSessions = conversationSessions;
|
|
1037
|
+
return { updated: true, kind, directory: nextDirectory };
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
export async function readClaworldSessionDirectory(options = {}, readOptions = {}) {
|
|
1041
|
+
const workspaceRoot = resolveClaworldWorkspaceRoot(options, readOptions.homeDir || os.homedir());
|
|
1042
|
+
const sessionDirectoryPath = resolveClaworldSessionDirectoryPath(workspaceRoot);
|
|
1043
|
+
const current = await readJsonIfPresent(sessionDirectoryPath);
|
|
1044
|
+
return {
|
|
1045
|
+
workspaceRoot,
|
|
1046
|
+
sessionDirectoryPath,
|
|
1047
|
+
directory: normalizeClaworldSessionDirectory(current || createEmptyClaworldSessionDirectory()),
|
|
1048
|
+
exists: current != null,
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
export async function updateClaworldSessionDirectory(options = {}, input = {}, updateOptions = {}) {
|
|
1053
|
+
const workspaceRoot = resolveClaworldWorkspaceRoot(options, updateOptions.homeDir || os.homedir());
|
|
1054
|
+
await ensureClaworldWorkingMemory(workspaceRoot, updateOptions);
|
|
1055
|
+
const sessionDirectoryPath = resolveClaworldSessionDirectoryPath(workspaceRoot);
|
|
1056
|
+
const current = await readJsonIfPresent(sessionDirectoryPath);
|
|
1057
|
+
const baseDirectory = current || createEmptyClaworldSessionDirectory(input.timestamp || updateOptions.timestamp);
|
|
1058
|
+
const result = applyClaworldSessionDirectoryUpdate(baseDirectory, input);
|
|
1059
|
+
if (!result.updated) {
|
|
1060
|
+
return {
|
|
1061
|
+
ok: true,
|
|
1062
|
+
updated: false,
|
|
1063
|
+
reason: result.reason,
|
|
1064
|
+
workspaceRoot,
|
|
1065
|
+
sessionDirectoryPath,
|
|
1066
|
+
directory: normalizeClaworldSessionDirectory(baseDirectory),
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
await atomicWriteText(
|
|
1070
|
+
sessionDirectoryPath,
|
|
1071
|
+
`${JSON.stringify(result.directory, null, 2)}\n`,
|
|
1072
|
+
{
|
|
1073
|
+
backup: false,
|
|
1074
|
+
rejectEmptyOverwrite: false,
|
|
1075
|
+
},
|
|
1076
|
+
);
|
|
1077
|
+
return {
|
|
1078
|
+
ok: true,
|
|
1079
|
+
updated: true,
|
|
1080
|
+
kind: result.kind,
|
|
1081
|
+
workspaceRoot,
|
|
1082
|
+
sessionDirectoryPath,
|
|
1083
|
+
directory: result.directory,
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
|
|
543
1087
|
function flattenInline(value) {
|
|
544
1088
|
return truncateText(String(value ?? '').replace(/\s+/g, ' ').trim());
|
|
545
1089
|
}
|
|
546
1090
|
|
|
1091
|
+
function firstText(...values) {
|
|
1092
|
+
for (const value of values) {
|
|
1093
|
+
const normalized = normalizeText(value, null);
|
|
1094
|
+
if (normalized) return normalized;
|
|
1095
|
+
}
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
function hasStructuredValue(value) {
|
|
1100
|
+
if (value == null) return false;
|
|
1101
|
+
if (typeof value === 'string') return normalizeText(value, null) != null;
|
|
1102
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
1103
|
+
if (isPlainObject(value)) return Object.keys(value).length > 0;
|
|
1104
|
+
return true;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
function cleanJournalValue(value) {
|
|
1108
|
+
if (value == null) return null;
|
|
1109
|
+
if (value instanceof Date) return toIsoTimestamp(value);
|
|
1110
|
+
if (typeof value === 'string') return normalizeText(value, null);
|
|
1111
|
+
if (typeof value === 'number' || typeof value === 'boolean') return value;
|
|
1112
|
+
if (Array.isArray(value)) {
|
|
1113
|
+
const cleanedArray = value
|
|
1114
|
+
.map((entry) => cleanJournalValue(entry))
|
|
1115
|
+
.filter(hasStructuredValue);
|
|
1116
|
+
return cleanedArray.length > 0 ? cleanedArray : null;
|
|
1117
|
+
}
|
|
1118
|
+
if (isPlainObject(value)) {
|
|
1119
|
+
const cleanedObject = {};
|
|
1120
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1121
|
+
const cleaned = cleanJournalValue(entry);
|
|
1122
|
+
if (hasStructuredValue(cleaned)) {
|
|
1123
|
+
cleanedObject[key] = cleaned;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return Object.keys(cleanedObject).length > 0 ? cleanedObject : null;
|
|
1127
|
+
}
|
|
1128
|
+
return normalizeText(value, null);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
function cleanJournalObject(value = {}) {
|
|
1132
|
+
return cleanJournalValue(value) || {};
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function resolveJournalScope(input = {}, relations = {}) {
|
|
1136
|
+
const directScope = normalizeText(input.scope, null);
|
|
1137
|
+
if (directScope) return directScope;
|
|
1138
|
+
const context = isPlainObject(input.context) ? input.context : {};
|
|
1139
|
+
const normalizedTarget = resolveClaworldBootstrapTarget({
|
|
1140
|
+
sessionKey: firstText(
|
|
1141
|
+
input.localSessionKey,
|
|
1142
|
+
input.sessionKey,
|
|
1143
|
+
relations.localSessionKey,
|
|
1144
|
+
relations.sessionKey,
|
|
1145
|
+
context.SessionKey,
|
|
1146
|
+
context.sessionKey,
|
|
1147
|
+
),
|
|
1148
|
+
sessionType: firstText(
|
|
1149
|
+
input.sessionType,
|
|
1150
|
+
input.sessionKind,
|
|
1151
|
+
context.SessionType,
|
|
1152
|
+
context.ChatType,
|
|
1153
|
+
context.sessionType,
|
|
1154
|
+
context.sessionKind,
|
|
1155
|
+
),
|
|
1156
|
+
channel: firstText(context.OriginatingChannel, context.channel),
|
|
1157
|
+
});
|
|
1158
|
+
if (normalizedTarget === CLAWORLD_BOOTSTRAP_TARGETS.CLAWORLD_CONVERSATION) return 'conversation';
|
|
1159
|
+
if (normalizedTarget === CLAWORLD_BOOTSTRAP_TARGETS.MANAGEMENT) return 'management';
|
|
1160
|
+
if (normalizedTarget === CLAWORLD_BOOTSTRAP_TARGETS.MAIN) return 'main';
|
|
1161
|
+
return firstText(input.sessionKind, input.sessionType, context.sessionKind, context.SessionType, 'runtime');
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function resolveJournalRelations(input = {}, refs = {}) {
|
|
1165
|
+
const relations = isPlainObject(input.relations)
|
|
1166
|
+
? input.relations
|
|
1167
|
+
: isPlainObject(input.correlation)
|
|
1168
|
+
? input.correlation
|
|
1169
|
+
: {};
|
|
1170
|
+
const context = isPlainObject(input.context) ? input.context : {};
|
|
1171
|
+
const artifacts = isPlainObject(input.artifacts) ? input.artifacts : {};
|
|
1172
|
+
const requestId = firstText(
|
|
1173
|
+
relations.requestId,
|
|
1174
|
+
relations.chatRequestId,
|
|
1175
|
+
input.requestId,
|
|
1176
|
+
input.chatRequestId,
|
|
1177
|
+
refs.requestId,
|
|
1178
|
+
refs.chatRequestId,
|
|
1179
|
+
refs.friendRequestId,
|
|
1180
|
+
);
|
|
1181
|
+
return cleanJournalObject({
|
|
1182
|
+
requestId,
|
|
1183
|
+
chatRequestId: firstText(relations.chatRequestId, input.chatRequestId, refs.chatRequestId, requestId),
|
|
1184
|
+
deliveryId: firstText(relations.deliveryId, input.deliveryId, refs.deliveryId, context.RelayDeliveryId),
|
|
1185
|
+
eventId: firstText(relations.eventId, input.eventId, input.id, refs.eventId, context.RelayEventId),
|
|
1186
|
+
notificationId: firstText(relations.notificationId, refs.notificationId),
|
|
1187
|
+
inboxItemId: firstText(relations.inboxItemId, refs.inboxItemId),
|
|
1188
|
+
conversationKey: firstText(relations.conversationKey, input.conversationKey, refs.conversationKey),
|
|
1189
|
+
worldId: firstText(relations.worldId, input.worldId, refs.worldId),
|
|
1190
|
+
accountId: firstText(relations.accountId, input.accountId, refs.accountId, context.AccountId),
|
|
1191
|
+
agentCode: firstText(relations.agentCode, refs.agentCode),
|
|
1192
|
+
localAgentId: firstText(relations.localAgentId, input.localAgentId, context.AgentId, context.agentId),
|
|
1193
|
+
targetAgentId: firstText(relations.targetAgentId, input.targetAgentId, refs.targetAgentId, context.RelayTargetAgentId),
|
|
1194
|
+
fromAgentId: firstText(relations.fromAgentId, input.fromAgentId, refs.fromAgentId, context.RelayFromAgentId),
|
|
1195
|
+
sessionKey: firstText(relations.sessionKey, input.sessionKey, context.SessionKey, context.sessionKey),
|
|
1196
|
+
localSessionKey: firstText(relations.localSessionKey, input.localSessionKey, context.LocalSessionKey, context.SessionKey),
|
|
1197
|
+
relaySessionKey: firstText(relations.relaySessionKey, input.relaySessionKey, context.RelaySessionKey),
|
|
1198
|
+
sessionId: firstText(relations.sessionId, input.sessionId, artifacts.sessionId, context.SessionId),
|
|
1199
|
+
sessionFile: firstText(relations.sessionFile, input.sessionFile, artifacts.sessionFile, artifacts.sessionPath, context.SessionFile),
|
|
1200
|
+
sessionStorePath: firstText(relations.sessionStorePath, input.sessionStorePath, artifacts.sessionStorePath),
|
|
1201
|
+
transcriptPath: firstText(relations.transcriptPath, input.transcriptPath, artifacts.transcriptPath),
|
|
1202
|
+
reportPath: firstText(relations.reportPath, input.reportPath, artifacts.reportPath),
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function buildJournalKey(relations = {}, fallbackEventId = null) {
|
|
1207
|
+
const event = firstText(relations.eventId, fallbackEventId);
|
|
1208
|
+
const request = firstText(relations.requestId, relations.chatRequestId);
|
|
1209
|
+
const session = firstText(relations.localSessionKey, relations.relaySessionKey, relations.sessionKey);
|
|
1210
|
+
const continuity = request
|
|
1211
|
+
? `request:${request}`
|
|
1212
|
+
: firstText(
|
|
1213
|
+
relations.conversationKey ? `conversation:${relations.conversationKey}` : null,
|
|
1214
|
+
session ? `session:${session}` : null,
|
|
1215
|
+
relations.worldId ? `world:${relations.worldId}` : null,
|
|
1216
|
+
event ? `event:${event}` : null,
|
|
1217
|
+
);
|
|
1218
|
+
return cleanJournalObject({
|
|
1219
|
+
event,
|
|
1220
|
+
continuity,
|
|
1221
|
+
request,
|
|
1222
|
+
session,
|
|
1223
|
+
sessionId: relations.sessionId,
|
|
1224
|
+
world: relations.worldId,
|
|
1225
|
+
conversation: relations.conversationKey,
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
|
|
547
1229
|
export function buildClaworldMaintenanceEvent(input = {}) {
|
|
548
1230
|
const toolName = normalizeText(input.toolName, null);
|
|
549
1231
|
const source = normalizeText(input.source, toolName ? 'claworld_tool' : 'claworld_runtime');
|
|
@@ -557,14 +1239,26 @@ export function buildClaworldMaintenanceEvent(input = {}) {
|
|
|
557
1239
|
.filter(([, value]) => value != null),
|
|
558
1240
|
)
|
|
559
1241
|
: {};
|
|
1242
|
+
const id = normalizeText(input.id, `${source}:${kind}:${timestamp}`);
|
|
1243
|
+
const relations = resolveJournalRelations({ ...input, eventId: input.eventId || id }, refs);
|
|
1244
|
+
const scope = resolveJournalScope(input, relations);
|
|
560
1245
|
return {
|
|
561
|
-
|
|
1246
|
+
schema: CLAWORLD_JOURNAL_SCHEMA,
|
|
1247
|
+
id,
|
|
562
1248
|
timestamp,
|
|
563
1249
|
source,
|
|
564
1250
|
kind,
|
|
1251
|
+
eventType: normalizeText(input.eventType, kind),
|
|
1252
|
+
scope,
|
|
565
1253
|
summary,
|
|
566
1254
|
excerpt: truncateText(input.excerpt || ''),
|
|
567
1255
|
refs,
|
|
1256
|
+
key: buildJournalKey(relations, id),
|
|
1257
|
+
relations,
|
|
1258
|
+
actor: cleanJournalObject(input.actor || {}),
|
|
1259
|
+
tool: cleanJournalObject(input.tool || {}),
|
|
1260
|
+
artifacts: cleanJournalObject(input.artifacts || {}),
|
|
1261
|
+
maintenance: cleanJournalObject(input.maintenance || {}),
|
|
568
1262
|
};
|
|
569
1263
|
}
|
|
570
1264
|
|
|
@@ -597,9 +1291,13 @@ function compactResultPayload(payload = {}) {
|
|
|
597
1291
|
const keys = [
|
|
598
1292
|
'status',
|
|
599
1293
|
'tool',
|
|
1294
|
+
'action',
|
|
1295
|
+
'scope',
|
|
1296
|
+
'query',
|
|
600
1297
|
'accountId',
|
|
601
1298
|
'worldId',
|
|
602
1299
|
'displayName',
|
|
1300
|
+
'requestId',
|
|
603
1301
|
'chatRequestId',
|
|
604
1302
|
'conversationKey',
|
|
605
1303
|
'feedbackId',
|
|
@@ -616,75 +1314,288 @@ function compactResultPayload(payload = {}) {
|
|
|
616
1314
|
return compact;
|
|
617
1315
|
}
|
|
618
1316
|
|
|
1317
|
+
function readNestedObject(value, key) {
|
|
1318
|
+
const nested = value?.[key];
|
|
1319
|
+
return nested && typeof nested === 'object' && !Array.isArray(nested) ? nested : {};
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
function firstObjectFromArray(value) {
|
|
1323
|
+
if (!Array.isArray(value)) return {};
|
|
1324
|
+
return value.find((entry) => isPlainObject(entry)) || {};
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
function nestedConversationWorldId(value = {}) {
|
|
1328
|
+
return firstText(value.worldId, value.conversation?.worldId);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function resolvePayloadItemCounts(payload = {}) {
|
|
1332
|
+
const counts = [];
|
|
1333
|
+
for (const [key, label] of [
|
|
1334
|
+
['worlds', 'worlds'],
|
|
1335
|
+
['members', 'members'],
|
|
1336
|
+
['people', 'people'],
|
|
1337
|
+
['results', 'results'],
|
|
1338
|
+
['items', 'items'],
|
|
1339
|
+
['pendingRequests', 'pendingRequests'],
|
|
1340
|
+
['recentRequests', 'recentRequests'],
|
|
1341
|
+
['chats', 'chats'],
|
|
1342
|
+
]) {
|
|
1343
|
+
if (Array.isArray(payload[key])) {
|
|
1344
|
+
counts.push(`${label}=${payload[key].length}`);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return counts;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
function formatToolSummaryPart(key, value) {
|
|
1351
|
+
const normalized = flattenInline(value);
|
|
1352
|
+
return normalized ? `${key}=${normalized}` : null;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
function buildToolCallSummary({ toolName, params = {}, payload = {}, compactPayload = {} } = {}) {
|
|
1356
|
+
const parts = [
|
|
1357
|
+
formatToolSummaryPart('action', firstText(params.action, payload.action)),
|
|
1358
|
+
formatToolSummaryPart('scope', firstText(params.scope, payload.scope)),
|
|
1359
|
+
formatToolSummaryPart('query', firstText(params.query, payload.query)),
|
|
1360
|
+
formatToolSummaryPart('status', firstText(payload.status, compactPayload.status)),
|
|
1361
|
+
formatToolSummaryPart('worldId', firstText(params.worldId, payload.worldId)),
|
|
1362
|
+
formatToolSummaryPart('chatRequestId', firstText(params.chatRequestId, payload.chatRequestId)),
|
|
1363
|
+
formatToolSummaryPart('conversationKey', firstText(params.conversationKey, payload.conversationKey)),
|
|
1364
|
+
...resolvePayloadItemCounts(payload),
|
|
1365
|
+
].filter(Boolean);
|
|
1366
|
+
return `${toolName} completed${parts.length > 0 ? ` (${parts.join(', ')})` : ''}.`;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
619
1369
|
export function buildClaworldToolMaintenanceEvent({
|
|
620
1370
|
toolName,
|
|
621
1371
|
params = {},
|
|
622
1372
|
result = null,
|
|
623
1373
|
timestamp = null,
|
|
1374
|
+
context = {},
|
|
624
1375
|
} = {}) {
|
|
625
1376
|
const normalizedToolName = normalizeText(toolName, null);
|
|
626
1377
|
if (!normalizedToolName || !normalizedToolName.startsWith('claworld_')) return null;
|
|
627
1378
|
const payload = parseToolResultPayload(result) || {};
|
|
1379
|
+
const chatRequest = readNestedObject(payload, 'chatRequest');
|
|
1380
|
+
const request = readNestedObject(payload, 'request');
|
|
1381
|
+
const kickoff = readNestedObject(payload, 'kickoff');
|
|
1382
|
+
const chat = readNestedObject(payload, 'chat');
|
|
1383
|
+
const firstChat = firstObjectFromArray(payload.chats);
|
|
1384
|
+
const conversation = readNestedObject(payload, 'conversation');
|
|
628
1385
|
const compactPayload = compactResultPayload(payload);
|
|
1386
|
+
const requestId = firstText(
|
|
1387
|
+
params.requestId,
|
|
1388
|
+
params.chatRequestId,
|
|
1389
|
+
payload.requestId,
|
|
1390
|
+
payload.chatRequestId,
|
|
1391
|
+
chatRequest.chatRequestId,
|
|
1392
|
+
chatRequest.requestId,
|
|
1393
|
+
request.requestId,
|
|
1394
|
+
chat.chatRequestId,
|
|
1395
|
+
firstChat.chatRequestId,
|
|
1396
|
+
);
|
|
1397
|
+
const requesterSessionKey = firstText(
|
|
1398
|
+
context.sessionKey,
|
|
1399
|
+
context.SessionKey,
|
|
1400
|
+
context.localSessionKey,
|
|
1401
|
+
context.LocalSessionKey,
|
|
1402
|
+
);
|
|
1403
|
+
const resultConversationSessionKey = firstText(
|
|
1404
|
+
chat.localSessionKey,
|
|
1405
|
+
firstChat.localSessionKey,
|
|
1406
|
+
payload.localSessionKey,
|
|
1407
|
+
kickoff.localSessionKey,
|
|
1408
|
+
conversation.localSessionKey,
|
|
1409
|
+
);
|
|
1410
|
+
const conversationToolNames = new Set([
|
|
1411
|
+
'claworld_manage_conversations',
|
|
1412
|
+
'claworld_chat_inbox',
|
|
1413
|
+
'claworld_request_chat',
|
|
1414
|
+
]);
|
|
1415
|
+
const localSessionKey = firstText(
|
|
1416
|
+
conversationToolNames.has(normalizedToolName) ? resultConversationSessionKey : null,
|
|
1417
|
+
requesterSessionKey,
|
|
1418
|
+
resultConversationSessionKey,
|
|
1419
|
+
);
|
|
1420
|
+
const relaySessionKey = firstText(
|
|
1421
|
+
context.RelaySessionKey,
|
|
1422
|
+
payload.sessionKey,
|
|
1423
|
+
chat.sessionKey,
|
|
1424
|
+
firstChat.sessionKey,
|
|
1425
|
+
kickoff.sessionKey,
|
|
1426
|
+
conversation.sessionKey,
|
|
1427
|
+
);
|
|
1428
|
+
const targetDiffersFromRequester = requesterSessionKey && localSessionKey && requesterSessionKey !== localSessionKey;
|
|
1429
|
+
const sessionKind = resolveJournalScope({
|
|
1430
|
+
sessionKey: localSessionKey || relaySessionKey,
|
|
1431
|
+
sessionType: targetDiffersFromRequester
|
|
1432
|
+
? null
|
|
1433
|
+
: firstText(context.sessionType, context.SessionType, context.ChatType, context.sessionKind),
|
|
1434
|
+
context: targetDiffersFromRequester ? {} : context,
|
|
1435
|
+
});
|
|
1436
|
+
const actorSessionKind = resolveJournalScope({
|
|
1437
|
+
sessionKey: requesterSessionKey || localSessionKey || relaySessionKey,
|
|
1438
|
+
sessionType: firstText(context.sessionType, context.SessionType, context.ChatType, context.sessionKind),
|
|
1439
|
+
context,
|
|
1440
|
+
});
|
|
629
1441
|
const refs = {
|
|
630
1442
|
accountId: params.accountId || payload.accountId,
|
|
631
|
-
worldId:
|
|
632
|
-
|
|
633
|
-
|
|
1443
|
+
worldId: firstText(
|
|
1444
|
+
params.worldId,
|
|
1445
|
+
payload.worldId,
|
|
1446
|
+
nestedConversationWorldId(chat),
|
|
1447
|
+
nestedConversationWorldId(firstChat),
|
|
1448
|
+
nestedConversationWorldId(chatRequest),
|
|
1449
|
+
nestedConversationWorldId(request),
|
|
1450
|
+
conversation.worldId,
|
|
1451
|
+
),
|
|
1452
|
+
requestId,
|
|
1453
|
+
chatRequestId: firstText(
|
|
1454
|
+
params.chatRequestId,
|
|
1455
|
+
payload.chatRequestId,
|
|
1456
|
+
chatRequest.chatRequestId,
|
|
1457
|
+
request.chatRequestId,
|
|
1458
|
+
chat.chatRequestId,
|
|
1459
|
+
firstChat.chatRequestId,
|
|
1460
|
+
requestId,
|
|
1461
|
+
),
|
|
1462
|
+
conversationKey: firstText(
|
|
1463
|
+
params.conversationKey,
|
|
1464
|
+
payload.conversationKey,
|
|
1465
|
+
conversation.conversationKey,
|
|
1466
|
+
kickoff.conversationKey,
|
|
1467
|
+
chat.conversationKey,
|
|
1468
|
+
firstChat.conversationKey,
|
|
1469
|
+
),
|
|
634
1470
|
agentCode: params.agentCode || payload.agentCode,
|
|
1471
|
+
sessionKey: localSessionKey || relaySessionKey,
|
|
1472
|
+
relaySessionKey,
|
|
635
1473
|
};
|
|
636
1474
|
return buildClaworldMaintenanceEvent({
|
|
637
1475
|
source: 'claworld_tool',
|
|
638
1476
|
kind: normalizedToolName,
|
|
1477
|
+
eventType: 'tool_call',
|
|
1478
|
+
scope: sessionKind,
|
|
639
1479
|
toolName: normalizedToolName,
|
|
640
1480
|
timestamp,
|
|
641
1481
|
refs,
|
|
642
|
-
|
|
1482
|
+
relations: {
|
|
1483
|
+
requestId,
|
|
1484
|
+
chatRequestId: refs.chatRequestId,
|
|
1485
|
+
conversationKey: refs.conversationKey,
|
|
1486
|
+
worldId: refs.worldId,
|
|
1487
|
+
accountId: refs.accountId,
|
|
1488
|
+
agentCode: refs.agentCode,
|
|
1489
|
+
localSessionKey,
|
|
1490
|
+
relaySessionKey,
|
|
1491
|
+
sessionKey: localSessionKey || relaySessionKey,
|
|
1492
|
+
localAgentId: firstText(context.agentId, context.AgentId),
|
|
1493
|
+
sessionId: targetDiffersFromRequester ? null : firstText(context.sessionId, context.SessionId),
|
|
1494
|
+
sessionFile: targetDiffersFromRequester ? null : firstText(context.sessionFile, context.SessionFile, context.sessionPath),
|
|
1495
|
+
transcriptPath: targetDiffersFromRequester ? null : firstText(context.transcriptPath),
|
|
1496
|
+
},
|
|
1497
|
+
actor: {
|
|
1498
|
+
sessionKind: actorSessionKind,
|
|
1499
|
+
agentId: firstText(context.agentId, context.AgentId),
|
|
1500
|
+
sessionKey: requesterSessionKey || localSessionKey || relaySessionKey,
|
|
1501
|
+
},
|
|
1502
|
+
tool: {
|
|
1503
|
+
name: normalizedToolName,
|
|
1504
|
+
action: firstText(params.action, payload.action),
|
|
1505
|
+
status: firstText(payload.status, 'succeeded'),
|
|
1506
|
+
requesterSessionKey,
|
|
1507
|
+
targetSessionKey: targetDiffersFromRequester ? localSessionKey : null,
|
|
1508
|
+
},
|
|
1509
|
+
summary: buildToolCallSummary({
|
|
1510
|
+
toolName: normalizedToolName,
|
|
1511
|
+
params,
|
|
1512
|
+
payload,
|
|
1513
|
+
compactPayload,
|
|
1514
|
+
}),
|
|
643
1515
|
excerpt: Object.keys(compactPayload).length > 0
|
|
644
1516
|
? JSON.stringify(compactPayload)
|
|
645
1517
|
: null,
|
|
646
1518
|
});
|
|
647
1519
|
}
|
|
648
1520
|
|
|
649
|
-
function
|
|
650
|
-
return
|
|
651
|
-
|
|
652
|
-
.
|
|
653
|
-
.
|
|
1521
|
+
function buildJournalEntryPayload(event = {}) {
|
|
1522
|
+
return cleanJournalObject({
|
|
1523
|
+
schema: CLAWORLD_JOURNAL_SCHEMA,
|
|
1524
|
+
id: event.id,
|
|
1525
|
+
key: event.key,
|
|
1526
|
+
timestamp: event.timestamp,
|
|
1527
|
+
source: event.source,
|
|
1528
|
+
scope: event.scope,
|
|
1529
|
+
eventType: event.eventType,
|
|
1530
|
+
kind: event.kind,
|
|
1531
|
+
summary: event.summary,
|
|
1532
|
+
refs: event.refs,
|
|
1533
|
+
relations: event.relations,
|
|
1534
|
+
actor: event.actor,
|
|
1535
|
+
tool: event.tool,
|
|
1536
|
+
artifacts: event.artifacts,
|
|
1537
|
+
maintenance: event.maintenance,
|
|
1538
|
+
excerpt: event.excerpt || null,
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
function buildJournalEntry(event = {}) {
|
|
1543
|
+
const title = flattenInline(event.summary || event.kind || 'Claworld event recorded.');
|
|
1544
|
+
const heading = [
|
|
1545
|
+
event.timestamp,
|
|
1546
|
+
event.scope || event.source,
|
|
1547
|
+
event.kind,
|
|
1548
|
+
title,
|
|
1549
|
+
].filter(Boolean).join(' · ');
|
|
1550
|
+
return [
|
|
1551
|
+
`## ${heading}`,
|
|
1552
|
+
'',
|
|
1553
|
+
'```json',
|
|
1554
|
+
JSON.stringify(buildJournalEntryPayload(event), null, 2),
|
|
1555
|
+
'```',
|
|
1556
|
+
'',
|
|
1557
|
+
].join('\n');
|
|
654
1558
|
}
|
|
655
1559
|
|
|
656
1560
|
export async function appendClaworldJournalEvent(options = {}, event = {}, appendOptions = {}) {
|
|
657
1561
|
const workspaceRoot = resolveClaworldWorkspaceRoot(options, appendOptions.homeDir || os.homedir());
|
|
658
1562
|
await ensureClaworldWorkingMemory(workspaceRoot, appendOptions);
|
|
659
1563
|
const normalizedEvent = buildClaworldMaintenanceEvent(event);
|
|
660
|
-
const
|
|
1564
|
+
const dayKey = toDayKey(normalizedEvent.timestamp);
|
|
661
1565
|
const journalPath = path.join(
|
|
662
1566
|
workspaceRoot,
|
|
663
1567
|
CLAWORLD_WORKING_MEMORY_DIR,
|
|
664
1568
|
CLAWORLD_JOURNAL_DIR,
|
|
665
|
-
`${
|
|
1569
|
+
`${dayKey}.md`,
|
|
666
1570
|
);
|
|
667
1571
|
const currentContent = await readTextIfPresent(journalPath);
|
|
668
|
-
const
|
|
669
|
-
const entry = [
|
|
670
|
-
`## ${normalizedEvent.timestamp} - ${normalizedEvent.kind}`,
|
|
671
|
-
`- Source: ${normalizedEvent.source}`,
|
|
672
|
-
`- Summary: ${flattenInline(normalizedEvent.summary)}`,
|
|
673
|
-
normalizedEvent.excerpt ? `- Excerpt: ${flattenInline(normalizedEvent.excerpt)}` : null,
|
|
674
|
-
refsLine ? `- Refs: ${refsLine}` : null,
|
|
675
|
-
'',
|
|
676
|
-
].filter((line) => line != null).join('\n');
|
|
1572
|
+
const entry = buildJournalEntry(normalizedEvent);
|
|
677
1573
|
if (currentContent == null) {
|
|
678
|
-
await atomicWriteText(journalPath, `# Claworld Journal ${
|
|
1574
|
+
await atomicWriteText(journalPath, `# Claworld Journal ${dayKey}\n\n${entry}`, {
|
|
679
1575
|
backup: false,
|
|
680
1576
|
rejectEmptyOverwrite: false,
|
|
681
1577
|
});
|
|
682
1578
|
} else {
|
|
683
1579
|
await fs.appendFile(journalPath, `${currentContent.endsWith('\n') ? '' : '\n'}${entry}`, 'utf8');
|
|
684
1580
|
}
|
|
1581
|
+
let sessionDirectory = null;
|
|
1582
|
+
if (appendOptions.updateSessionDirectory !== false) {
|
|
1583
|
+
try {
|
|
1584
|
+
sessionDirectory = await updateClaworldSessionDirectory(workspaceRoot, normalizedEvent, {
|
|
1585
|
+
...appendOptions,
|
|
1586
|
+
timestamp: normalizedEvent.timestamp,
|
|
1587
|
+
});
|
|
1588
|
+
} catch (error) {
|
|
1589
|
+
sessionDirectory = {
|
|
1590
|
+
ok: false,
|
|
1591
|
+
error: error?.message || String(error),
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
685
1595
|
return {
|
|
686
1596
|
ok: true,
|
|
687
1597
|
journalPath,
|
|
1598
|
+
sessionDirectory,
|
|
688
1599
|
event: normalizedEvent,
|
|
689
1600
|
};
|
|
690
1601
|
}
|
|
@@ -731,11 +1642,27 @@ export async function buildClaworldBootstrapPromptContext(context = {}, options
|
|
|
731
1642
|
})
|
|
732
1643
|
: { slices: {} };
|
|
733
1644
|
const pointerInjected = target === CLAWORLD_BOOTSTRAP_TARGETS.MAIN;
|
|
1645
|
+
const managementPolicyInjected = target === CLAWORLD_BOOTSTRAP_TARGETS.MANAGEMENT;
|
|
1646
|
+
let managementMainSessionKey = null;
|
|
1647
|
+
if (managementPolicyInjected && resolvedWorkspaceRoot) {
|
|
1648
|
+
try {
|
|
1649
|
+
const sessionDirectory = await readClaworldSessionDirectory(resolvedWorkspaceRoot);
|
|
1650
|
+
managementMainSessionKey = normalizeText(
|
|
1651
|
+
sessionDirectory.directory?.main?.lastActiveSessionKey,
|
|
1652
|
+
null,
|
|
1653
|
+
);
|
|
1654
|
+
} catch {
|
|
1655
|
+
managementMainSessionKey = null;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
734
1658
|
const parts = [];
|
|
735
1659
|
let truncated = false;
|
|
736
1660
|
if (pointerInjected) {
|
|
737
1661
|
const pointerBudget = maxTotalChars - measureBootstrapParts(parts) - (parts.length > 0 ? 2 : 0);
|
|
738
|
-
const fittedPointer = truncateBootstrapText(
|
|
1662
|
+
const fittedPointer = truncateBootstrapText(
|
|
1663
|
+
buildClaworldContextPointer({ workspaceRoot: resolvedWorkspaceRoot }),
|
|
1664
|
+
pointerBudget,
|
|
1665
|
+
);
|
|
739
1666
|
if (fittedPointer.text) {
|
|
740
1667
|
parts.push(fittedPointer.text);
|
|
741
1668
|
}
|
|
@@ -743,6 +1670,22 @@ export async function buildClaworldBootstrapPromptContext(context = {}, options
|
|
|
743
1670
|
truncated = true;
|
|
744
1671
|
}
|
|
745
1672
|
}
|
|
1673
|
+
if (managementPolicyInjected) {
|
|
1674
|
+
const policyBudget = maxTotalChars - measureBootstrapParts(parts) - (parts.length > 0 ? 2 : 0);
|
|
1675
|
+
const fittedPolicy = truncateBootstrapText(
|
|
1676
|
+
buildClaworldManagementStartupPrompt({
|
|
1677
|
+
workspaceRoot: resolvedWorkspaceRoot,
|
|
1678
|
+
mainSessionKey: managementMainSessionKey,
|
|
1679
|
+
}),
|
|
1680
|
+
policyBudget,
|
|
1681
|
+
);
|
|
1682
|
+
if (fittedPolicy.text) {
|
|
1683
|
+
parts.push(fittedPolicy.text);
|
|
1684
|
+
}
|
|
1685
|
+
if (fittedPolicy.truncated) {
|
|
1686
|
+
truncated = true;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
746
1689
|
const sectionTitle = target === CLAWORLD_BOOTSTRAP_TARGETS.MAIN
|
|
747
1690
|
? '# Claworld Startup Memory'
|
|
748
1691
|
: target === CLAWORLD_BOOTSTRAP_TARGETS.MANAGEMENT
|
|
@@ -773,6 +1716,7 @@ export async function buildClaworldBootstrapPromptContext(context = {}, options
|
|
|
773
1716
|
workspaceRoot: resolvedWorkspaceRoot,
|
|
774
1717
|
files: selectedFiles.map((relativePath) => buildBootstrapFileLabel(relativePath)),
|
|
775
1718
|
pointerInjected,
|
|
1719
|
+
managementPolicyInjected,
|
|
776
1720
|
fallbackFiles: fileSections.fallbackFiles,
|
|
777
1721
|
omittedFiles: fileSections.omittedFiles,
|
|
778
1722
|
truncated,
|
|
@@ -834,6 +1778,40 @@ function assertAllowedTarget(runType, target) {
|
|
|
834
1778
|
}
|
|
835
1779
|
}
|
|
836
1780
|
|
|
1781
|
+
function normalizeMarkdownNewlines(content) {
|
|
1782
|
+
return String(content ?? '').replace(/\r\n/g, '\n');
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
function assertContextMarkdownUsesBullets(target, normalizedContent, schema) {
|
|
1786
|
+
const maxBulletLength = schema.maxBulletLength || null;
|
|
1787
|
+
const lines = normalizedContent.split('\n');
|
|
1788
|
+
for (const line of lines) {
|
|
1789
|
+
const trimmed = line.trim();
|
|
1790
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
1791
|
+
if (!trimmed.startsWith('- ')) {
|
|
1792
|
+
throw new Error(`Claworld maintenance patch for ${target} must use bullet lines under schema sections.`);
|
|
1793
|
+
}
|
|
1794
|
+
if (maxBulletLength && trimmed.length > maxBulletLength) {
|
|
1795
|
+
throw new Error(`Claworld maintenance patch for ${target} has a bullet longer than ${maxBulletLength} characters.`);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
function assertContextFileSchema(target, content) {
|
|
1801
|
+
const schema = CLAWORLD_CONTEXT_FILE_SCHEMAS[target];
|
|
1802
|
+
if (!schema) return;
|
|
1803
|
+
const normalizedContent = normalizeMarkdownNewlines(content);
|
|
1804
|
+
if (!normalizedContent.startsWith(`${schema.title}\n`)) {
|
|
1805
|
+
throw new Error(`Claworld maintenance patch for ${target} must start with ${schema.title}.`);
|
|
1806
|
+
}
|
|
1807
|
+
for (const heading of schema.headings) {
|
|
1808
|
+
if (!normalizedContent.includes(`\n${heading}\n`)) {
|
|
1809
|
+
throw new Error(`Claworld maintenance patch for ${target} is missing required section ${heading}.`);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
assertContextMarkdownUsesBullets(target, normalizedContent, schema);
|
|
1813
|
+
}
|
|
1814
|
+
|
|
837
1815
|
function normalizeReportPatch(report, index) {
|
|
838
1816
|
const filename = normalizeText(report?.filename ?? report?.name, `report-${index + 1}.md`);
|
|
839
1817
|
const normalizedFilename = path.posix.basename(filename.endsWith('.md') ? filename : `${filename}.md`);
|
|
@@ -890,10 +1868,10 @@ export function normalizeClaworldMaintenanceOutput(runType, output = {}, options
|
|
|
890
1868
|
patches.push(normalizeFilePatchValue(output.memoryMd, CLAWORLD_WORKING_MEMORY_FILES.memory, 'memoryMd'));
|
|
891
1869
|
}
|
|
892
1870
|
if (Object.prototype.hasOwnProperty.call(output, 'journalAppendMd')) {
|
|
893
|
-
const
|
|
1871
|
+
const dayKey = toDayKey(options.timestamp || output.timestamp || Date.now());
|
|
894
1872
|
patches.push({
|
|
895
1873
|
operation: 'append_section',
|
|
896
|
-
target: `${CLAWORLD_JOURNAL_DIR}/${
|
|
1874
|
+
target: `${CLAWORLD_JOURNAL_DIR}/${dayKey}.md`,
|
|
897
1875
|
content: String(output.journalAppendMd ?? ''),
|
|
898
1876
|
});
|
|
899
1877
|
}
|
|
@@ -926,6 +1904,9 @@ export function normalizeClaworldMaintenanceOutput(runType, output = {}, options
|
|
|
926
1904
|
target,
|
|
927
1905
|
content: String(patch.content ?? ''),
|
|
928
1906
|
};
|
|
1907
|
+
if (operation === 'replace') {
|
|
1908
|
+
assertContextFileSchema(target, normalizedPatch.content);
|
|
1909
|
+
}
|
|
929
1910
|
const rationale = normalizeText(patch.rationale, null);
|
|
930
1911
|
if (rationale) normalizedPatch.rationale = rationale;
|
|
931
1912
|
return normalizedPatch;
|
|
@@ -955,8 +1936,8 @@ async function applyMaintenancePatch(workspaceRoot, patch) {
|
|
|
955
1936
|
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
956
1937
|
const currentContent = await readTextIfPresent(absolutePath);
|
|
957
1938
|
if (currentContent == null) {
|
|
958
|
-
const
|
|
959
|
-
await atomicWriteText(absolutePath, `# Claworld Journal ${
|
|
1939
|
+
const dayKey = path.basename(patch.target, '.md');
|
|
1940
|
+
await atomicWriteText(absolutePath, `# Claworld Journal ${dayKey}\n\n${patch.content}`, {
|
|
960
1941
|
backup: false,
|
|
961
1942
|
rejectEmptyOverwrite: false,
|
|
962
1943
|
});
|