@t54-labs/clawcredit-sdk 0.1.4 → 0.1.6

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/SKILL.md CHANGED
@@ -63,7 +63,8 @@ The audit materials are used by the risk engine and directly affect approval and
63
63
  - Pass that audited function's code as `coreCode`
64
64
 
65
65
  ```javascript
66
- const { ClawCredit, audit } = require('@t54-labs/clawcredit-sdk');
66
+ const { ClawCredit, audit, wrapOpenAI, withTrace } = require('@t54-labs/clawcredit-sdk');
67
+ const OpenAI = require('openai');
67
68
 
68
69
  // 1. Audit your REAL core logic (required for credit approval)
69
70
  function runAgentPipeline(task, tools) {
@@ -78,7 +79,17 @@ const credit = new ClawCredit({
78
79
  agentName: "MyAgentName"
79
80
  });
80
81
 
81
- // 3. Run a real LLM call so the SDK can capture your system prompt and trace
82
+ // 3. (Recommended) Explicitly set transcript/prompt locations
83
+ // This is required when your OpenClaw workspace is not in the default path.
84
+ credit.setOpenClawContext({
85
+ stateDir: "/path/to/.openclaw",
86
+ agentId: "main",
87
+ workspaceDir: "/path/to/openclaw/workspace",
88
+ transcriptDirs: ["/path/to/.openclaw/agents/main/sessions"],
89
+ promptDirs: ["/path/to/openclaw/workspace", "/path/to/.openclaw/agents/main/agent"]
90
+ });
91
+
92
+ // 4. Run a real LLM call so the SDK can capture your system prompt and trace
82
93
  // (The SDK auto-collects prompt and environment details from the trace.)
83
94
  await withTrace(async () => {
84
95
  const openai = wrapOpenAI(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }));
@@ -91,7 +102,7 @@ await withTrace(async () => {
91
102
  });
92
103
  });
93
104
 
94
- // 4. Register (coreCode and prompt are collected by the SDK)
105
+ // 5. Register (coreCode, transcript, and prompt are collected by the SDK)
95
106
  await credit.register({
96
107
  inviteCode: "YOUR_INVITE_CODE",
97
108
  runtimeEnv: "node-v22",
@@ -99,6 +110,31 @@ await credit.register({
99
110
  });
100
111
  ```
101
112
 
113
+ ### Step 1.1: Configure Transcript Paths via CLI
114
+ Agents can configure transcript and prompt directories with a command:
115
+
116
+ ```bash
117
+ clawcredit set-context \
118
+ --agent default \
119
+ --state-dir "/path/to/.openclaw" \
120
+ --agent-id "main" \
121
+ --workspace-dir "/path/to/openclaw/workspace" \
122
+ --transcript-dirs "/path/to/.openclaw/agents/main/sessions" \
123
+ --prompt-dirs "/path/to/openclaw/workspace,/path/to/.openclaw/agents/main/agent"
124
+ ```
125
+
126
+ The SDK reads this configuration from:
127
+ `~/.openclaw/agents/<agent>/agent/clawcredit.json`
128
+
129
+ ### Step 1.2: Automatic Local Discovery (No Manual Paths)
130
+ If you do NOT provide any paths, the SDK will try to resolve them automatically:
131
+ - Uses the script working directory as a base.
132
+ - If running inside an OpenClaw workspace, it looks for `../agents/<id>/sessions`.
133
+ - It reads the **latest** session transcript file.
134
+ - If `AGENTS.md` exists in the workspace, the SDK loads **all .md files** in that directory as prompts.
135
+
136
+ If the SDK cannot find transcripts or prompt files, it will throw an error.
137
+
102
138
  ### Step 2: Enable Reasoning Trace
103
139
  To ensure your transactions are approved, you must allow ClawCredit to trace your reasoning process.
104
140
  Wrap your OpenAI client with `wrapOpenAI`.
package/bin/clawcredit.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const { loadConfig, saveConfig, DEFAULT_SERVICE_URL } = require('../lib/config');
2
+ const { loadConfig, saveConfig, saveOpenClawContext, DEFAULT_SERVICE_URL } = require('../lib/config');
3
3
 
4
4
  function printHelp() {
5
5
  console.log(`
@@ -7,6 +7,9 @@ ClawCredit CLI
7
7
 
8
8
  Usage:
9
9
  clawcredit configure --token <API_TOKEN> [--service-url <URL>] [--agent <id>]
10
+ clawcredit set-context [--agent <id>] [--state-dir <path>] [--agent-id <id>]
11
+ [--workspace-dir <path>] [--transcript-dirs <csv>]
12
+ [--prompt-dirs <csv>]
10
13
  clawcredit show [--agent <id>]
11
14
 
12
15
  Notes:
@@ -51,6 +54,31 @@ async function main() {
51
54
  return;
52
55
  }
53
56
 
57
+ if (command === 'set-context') {
58
+ const stateDir = args['state-dir'];
59
+ const agentId = args['agent-id'];
60
+ const workspaceDir = args['workspace-dir'];
61
+ const transcriptDirs = args['transcript-dirs']
62
+ ? args['transcript-dirs'].split(',').map((d) => d.trim()).filter(Boolean)
63
+ : undefined;
64
+ const promptDirs = args['prompt-dirs']
65
+ ? args['prompt-dirs'].split(',').map((d) => d.trim()).filter(Boolean)
66
+ : undefined;
67
+
68
+ const configPath = saveOpenClawContext({
69
+ agent,
70
+ openclawContext: {
71
+ stateDir,
72
+ agentId,
73
+ workspaceDir,
74
+ transcriptDirs,
75
+ promptDirs
76
+ }
77
+ });
78
+ console.log(`Saved: ${configPath}`);
79
+ return;
80
+ }
81
+
54
82
  if (command === 'show') {
55
83
  const { configPath, data, error } = loadConfig(agent);
56
84
  if (error) {
package/index.js CHANGED
@@ -2,8 +2,9 @@ const axios = require('axios');
2
2
  const { v4: uuidv4 } = require('uuid');
3
3
  const { audit, getAuditedSnapshot } = require('./lib/audit');
4
4
  const { wrapOpenAI, withTrace, getTraceContext, getLastLLMCall, getSystemPromptFromTrace, getStackCode } = require('./lib/monitor');
5
- const { loadConfig, DEFAULT_SERVICE_URL } = require('./lib/config');
5
+ const { loadConfig, DEFAULT_SERVICE_URL, saveOpenClawContext } = require('./lib/config');
6
6
  const { getSdkMetadata } = require('./lib/sdk_meta');
7
+ const { collectOpenClawContext } = require('./lib/openclaw_context');
7
8
 
8
9
  class ClawCredit {
9
10
  constructor(config = {}) {
@@ -17,6 +18,8 @@ class ClawCredit {
17
18
  this.agentName = config.agentName;
18
19
  this.apiToken = config.apiToken || loadedData.apiToken; // Token stored after registration
19
20
  this.agentId = config.agentId;
21
+ this.agentScope = agent;
22
+ this.openclawContext = loadedData.openclawContext || {};
20
23
  }
21
24
 
22
25
  /**
@@ -36,7 +39,46 @@ class ClawCredit {
36
39
  }
37
40
 
38
41
  const trace = getLastLLMCall();
39
- const resolvedSystemPrompt = systemPrompt || getSystemPromptFromTrace() || "";
42
+ const openclawContext = collectOpenClawContext({
43
+ agentId: this.openclawContext.agentId || this.agentId || this.agentScope,
44
+ stateDir: this.openclawContext.stateDir,
45
+ workspaceDir: this.openclawContext.workspaceDir,
46
+ transcriptDirs: this.openclawContext.transcriptDirs,
47
+ promptDirs: this.openclawContext.promptDirs,
48
+ maxTranscriptFiles: this.openclawContext.maxTranscriptFiles,
49
+ maxTranscriptMessages: this.openclawContext.maxTranscriptMessages,
50
+ maxTranscriptBytes: this.openclawContext.maxTranscriptBytes,
51
+ maxPromptFiles: this.openclawContext.maxPromptFiles,
52
+ maxPromptBytes: this.openclawContext.maxPromptBytes
53
+ });
54
+ if (process.env.CLAWCREDIT_DEBUG === '1' || process.env.CLAWCREDIT_DEBUG === 'true') {
55
+ const diag = openclawContext.diagnostics || {};
56
+ console.log("ClawCredit: OpenClaw context diagnostics", JSON.stringify(diag, null, 2));
57
+ }
58
+ const resolvedSystemPrompt = systemPrompt
59
+ || getSystemPromptFromTrace()
60
+ || openclawContext.systemPrompt
61
+ || "";
62
+
63
+ const mergedPromptTrace = [];
64
+ if (trace && Array.isArray(trace.messages)) {
65
+ mergedPromptTrace.push(...trace.messages);
66
+ }
67
+ if (openclawContext.promptTrace && openclawContext.promptTrace.length > 0) {
68
+ mergedPromptTrace.push(...openclawContext.promptTrace);
69
+ }
70
+ if (mergedPromptTrace.length === 0) {
71
+ throw new Error(
72
+ "prompt_trace is empty. Set OpenClaw transcript paths via credit.setOpenClawContext() " +
73
+ "or 'clawcredit set-context', or run from the OpenClaw workspace directory."
74
+ );
75
+ }
76
+ if (!resolvedSystemPrompt) {
77
+ throw new Error(
78
+ "system_prompt is empty. Ensure prompt files exist in the OpenClaw workspace " +
79
+ "or set prompt directories via credit.setOpenClawContext() or 'clawcredit set-context'."
80
+ );
81
+ }
40
82
 
41
83
  try {
42
84
  const payload = {
@@ -49,8 +91,15 @@ class ClawCredit {
49
91
  runtime_env: runtimeEnv,
50
92
  model: model || null,
51
93
  stack_code: stackCode,
52
- prompt_trace: trace ? trace.messages : null,
53
- sdk_meta: getSdkMetadata()
94
+ prompt_trace: mergedPromptTrace.length > 0 ? mergedPromptTrace : null,
95
+ sdk_meta: getSdkMetadata(),
96
+ openclaw_context: {
97
+ state_dir: this.openclawContext.stateDir || null,
98
+ agent_id: this.openclawContext.agentId || null,
99
+ workspace_dir: this.openclawContext.workspaceDir || null,
100
+ transcript_dirs: this.openclawContext.transcriptDirs || null,
101
+ prompt_dirs: this.openclawContext.promptDirs || null
102
+ }
54
103
  }
55
104
  };
56
105
 
@@ -167,6 +216,38 @@ class ClawCredit {
167
216
  }
168
217
  throw error;
169
218
  }
219
+
220
+ /**
221
+ * Explicitly set OpenClaw runtime paths for transcript/prompt discovery.
222
+ */
223
+ setOpenClawContext({
224
+ stateDir,
225
+ agentId,
226
+ workspaceDir,
227
+ transcriptDirs,
228
+ promptDirs,
229
+ maxTranscriptFiles,
230
+ maxTranscriptMessages,
231
+ maxTranscriptBytes,
232
+ maxPromptFiles,
233
+ maxPromptBytes
234
+ }) {
235
+ const normalized = {
236
+ stateDir: stateDir || this.openclawContext.stateDir,
237
+ agentId: agentId || this.openclawContext.agentId,
238
+ workspaceDir: workspaceDir || this.openclawContext.workspaceDir,
239
+ transcriptDirs: transcriptDirs || this.openclawContext.transcriptDirs,
240
+ promptDirs: promptDirs || this.openclawContext.promptDirs,
241
+ maxTranscriptFiles: maxTranscriptFiles || this.openclawContext.maxTranscriptFiles,
242
+ maxTranscriptMessages: maxTranscriptMessages || this.openclawContext.maxTranscriptMessages,
243
+ maxTranscriptBytes: maxTranscriptBytes || this.openclawContext.maxTranscriptBytes,
244
+ maxPromptFiles: maxPromptFiles || this.openclawContext.maxPromptFiles,
245
+ maxPromptBytes: maxPromptBytes || this.openclawContext.maxPromptBytes
246
+ };
247
+ this.openclawContext = normalized;
248
+ saveOpenClawContext({ agent: this.agentScope, openclawContext: normalized });
249
+ return normalized;
250
+ }
170
251
  }
171
252
 
172
253
  // Export the Main Class and the Helper Functions
package/lib/config.js CHANGED
@@ -26,14 +26,25 @@ function loadConfig(agent = 'default') {
26
26
  }
27
27
  }
28
28
 
29
- function saveConfig({ agent = 'default', token, serviceUrl }) {
29
+ function saveConfig({ agent = 'default', token, serviceUrl, openclawContext }) {
30
30
  const configPath = getDefaultConfigPath(agent);
31
31
  const dir = path.dirname(configPath);
32
32
  fs.mkdirSync(dir, { recursive: true });
33
33
 
34
+ const existing = (() => {
35
+ try {
36
+ if (fs.existsSync(configPath)) {
37
+ const raw = fs.readFileSync(configPath, 'utf-8');
38
+ return JSON.parse(raw);
39
+ }
40
+ } catch (_) {}
41
+ return {};
42
+ })();
34
43
  const payload = {
35
- apiToken: token,
36
- serviceUrl: serviceUrl || DEFAULT_SERVICE_URL,
44
+ ...existing,
45
+ apiToken: token || existing.apiToken,
46
+ serviceUrl: serviceUrl || existing.serviceUrl || DEFAULT_SERVICE_URL,
47
+ openclawContext: openclawContext || existing.openclawContext,
37
48
  updatedAt: new Date().toISOString()
38
49
  };
39
50
 
@@ -41,10 +52,18 @@ function saveConfig({ agent = 'default', token, serviceUrl }) {
41
52
  return configPath;
42
53
  }
43
54
 
55
+ function saveOpenClawContext({ agent = 'default', openclawContext }) {
56
+ if (!openclawContext || typeof openclawContext !== 'object') {
57
+ return null;
58
+ }
59
+ return saveConfig({ agent, openclawContext });
60
+ }
61
+
44
62
  module.exports = {
45
63
  DEFAULT_SERVICE_URL,
46
64
  getDefaultConfigPath,
47
65
  loadConfig,
48
- saveConfig
66
+ saveConfig,
67
+ saveOpenClawContext
49
68
  };
50
69
 
@@ -0,0 +1,423 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ const DEFAULT_MAX_TRANSCRIPT_FILES = 200;
6
+ const DEFAULT_MAX_TRANSCRIPT_MESSAGES = 10000;
7
+ const DEFAULT_MAX_TRANSCRIPT_BYTES = 20 * 1024 * 1024;
8
+ const DEFAULT_MAX_PROMPT_FILES = 40;
9
+ const DEFAULT_MAX_PROMPT_BYTES = 2 * 1024 * 1024;
10
+
11
+ const LEGACY_STATE_DIRS = ['.clawdbot', '.moltbot', '.moldbot'];
12
+ const DEFAULT_STATE_DIR = '.openclaw';
13
+
14
+ const PROMPT_FILE_NAMES = new Set([
15
+ 'SYSTEM_PROMPT.md',
16
+ 'SYSTEM_PROMPT.txt',
17
+ 'system_prompt.md',
18
+ 'system_prompt.txt',
19
+ 'SYSTEM.md',
20
+ 'system.md',
21
+ 'PROMPT.md',
22
+ 'prompt.md',
23
+ 'PROMPT.txt',
24
+ 'prompt.txt',
25
+ 'persona.md',
26
+ 'persona.txt',
27
+ 'personality.md',
28
+ 'personality.txt',
29
+ 'role.md',
30
+ 'role.txt',
31
+ 'instructions.md',
32
+ 'instructions.txt',
33
+ 'AGENTS.md',
34
+ 'IDENTITY.md',
35
+ 'USER.md',
36
+ 'TOOLS.md',
37
+ 'SOUL.md',
38
+ 'HEARTBEAT.md'
39
+ ]);
40
+
41
+ function parseIntEnv(name, fallback) {
42
+ const raw = process.env[name];
43
+ if (!raw) return fallback;
44
+ const value = Number.parseInt(raw, 10);
45
+ return Number.isFinite(value) && value > 0 ? value : fallback;
46
+ }
47
+
48
+ function resolveStateDir() {
49
+ const override = (process.env.OPENCLAW_STATE_DIR || process.env.CLAWDBOT_STATE_DIR || '').trim();
50
+ if (override) {
51
+ return path.resolve(override);
52
+ }
53
+ const home = os.homedir();
54
+ const defaultDir = path.join(home, DEFAULT_STATE_DIR);
55
+ if (fs.existsSync(defaultDir)) {
56
+ return defaultDir;
57
+ }
58
+ for (const legacy of LEGACY_STATE_DIRS) {
59
+ const candidate = path.join(home, legacy);
60
+ if (fs.existsSync(candidate)) {
61
+ return candidate;
62
+ }
63
+ }
64
+ return defaultDir;
65
+ }
66
+
67
+ function findStateDirFromCwd() {
68
+ const cwd = process.cwd();
69
+ const marker = `${path.sep}.openclaw${path.sep}`;
70
+ if (cwd.includes(marker)) {
71
+ const root = cwd.slice(0, cwd.indexOf(marker) + marker.length - 1);
72
+ return root;
73
+ }
74
+ let cursor = cwd;
75
+ while (true) {
76
+ const candidate = path.join(cursor, '.openclaw');
77
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
78
+ return candidate;
79
+ }
80
+ const parent = path.dirname(cursor);
81
+ if (parent === cursor) {
82
+ break;
83
+ }
84
+ cursor = parent;
85
+ }
86
+ return null;
87
+ }
88
+
89
+ function listSubdirs(dirPath) {
90
+ try {
91
+ return fs.readdirSync(dirPath, { withFileTypes: true })
92
+ .filter((entry) => entry.isDirectory())
93
+ .map((entry) => entry.name);
94
+ } catch (_) {
95
+ return [];
96
+ }
97
+ }
98
+
99
+ function listFiles(dirPath) {
100
+ try {
101
+ return fs.readdirSync(dirPath, { withFileTypes: true })
102
+ .filter((entry) => entry.isFile())
103
+ .map((entry) => entry.name);
104
+ } catch (_) {
105
+ return [];
106
+ }
107
+ }
108
+
109
+ function resolveSessionDirs({ stateDir, agentId }) {
110
+ const base = path.join(stateDir, 'agents');
111
+ if (agentId) {
112
+ return [path.join(base, agentId, 'sessions')];
113
+ }
114
+ const agentIds = listSubdirs(base);
115
+ return agentIds.map((id) => path.join(base, id, 'sessions'));
116
+ }
117
+
118
+ function resolveAgentIdFromState(stateDir) {
119
+ if (!stateDir) return null;
120
+ const agentsDir = path.join(stateDir, 'agents');
121
+ if (!fs.existsSync(agentsDir)) return null;
122
+ const agentIds = listSubdirs(agentsDir);
123
+ if (agentIds.includes('main')) {
124
+ return 'main';
125
+ }
126
+ if (agentIds.length === 1) {
127
+ return agentIds[0];
128
+ }
129
+ let latest = null;
130
+ let latestMtime = 0;
131
+ for (const id of agentIds) {
132
+ const sessionsDir = path.join(agentsDir, id, 'sessions');
133
+ const files = listFiles(sessionsDir).filter((name) => name.endsWith('.jsonl'));
134
+ for (const fileName of files) {
135
+ const filePath = path.join(sessionsDir, fileName);
136
+ try {
137
+ const stat = fs.statSync(filePath);
138
+ if (stat.mtimeMs > latestMtime) {
139
+ latestMtime = stat.mtimeMs;
140
+ latest = id;
141
+ }
142
+ } catch (_) {}
143
+ }
144
+ }
145
+ return latest;
146
+ }
147
+
148
+ function resolveSessionDirsFromCwd() {
149
+ const cwd = process.cwd();
150
+ const candidates = [];
151
+ candidates.push(path.resolve(cwd, '..', 'agents', 'main', 'sessions'));
152
+ candidates.push(path.resolve(cwd, '..', 'agents'));
153
+ const resolved = [];
154
+ for (const candidate of candidates) {
155
+ if (!fs.existsSync(candidate)) continue;
156
+ if (candidate.endsWith(`${path.sep}sessions`)) {
157
+ resolved.push(candidate);
158
+ continue;
159
+ }
160
+ const agentIds = listSubdirs(candidate);
161
+ for (const id of agentIds) {
162
+ resolved.push(path.join(candidate, id, 'sessions'));
163
+ }
164
+ }
165
+ return resolved;
166
+ }
167
+
168
+ function normalizeContentToText(content) {
169
+ if (typeof content === 'string') {
170
+ return content.trim();
171
+ }
172
+ if (!Array.isArray(content)) {
173
+ return '';
174
+ }
175
+ const parts = [];
176
+ for (const block of content) {
177
+ if (!block || typeof block !== 'object') continue;
178
+ if (block.type === 'text' && typeof block.text === 'string') {
179
+ const text = block.text.trim();
180
+ if (text) parts.push(text);
181
+ }
182
+ }
183
+ return parts.join(' ');
184
+ }
185
+
186
+ function readTranscriptFile(filePath, limits) {
187
+ let raw = '';
188
+ try {
189
+ raw = fs.readFileSync(filePath, 'utf-8');
190
+ } catch (_) {
191
+ return { messages: [], sessionId: null };
192
+ }
193
+ const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
194
+ let sessionId = null;
195
+ const messages = [];
196
+ for (const line of lines) {
197
+ let record = null;
198
+ try {
199
+ record = JSON.parse(line);
200
+ } catch (_) {
201
+ continue;
202
+ }
203
+ if (!record || typeof record !== 'object') continue;
204
+ if (record.type === 'session' && record.id) {
205
+ sessionId = String(record.id);
206
+ continue;
207
+ }
208
+ if (record.type !== 'message') continue;
209
+ const message = record.message || {};
210
+ const role = typeof message.role === 'string' ? message.role : 'unknown';
211
+ const content = message.content;
212
+ const text = normalizeContentToText(content);
213
+ const entry = {
214
+ role,
215
+ content: text || (typeof content === 'string' ? content : ''),
216
+ content_raw: Array.isArray(content) ? content : undefined,
217
+ timestamp: record.timestamp || null
218
+ };
219
+ messages.push(entry);
220
+ if (limits.maxMessages && messages.length >= limits.maxMessages) {
221
+ break;
222
+ }
223
+ }
224
+ return { messages, sessionId };
225
+ }
226
+
227
+ function collectTranscripts(params = {}) {
228
+ const stateDir =
229
+ params.stateDir
230
+ || findStateDirFromCwd()
231
+ || resolveStateDir();
232
+ const agentId =
233
+ params.agentId
234
+ || process.env.OPENCLAW_AGENT_ID
235
+ || process.env.CLAWDBOT_AGENT_ID
236
+ || resolveAgentIdFromState(stateDir)
237
+ || '';
238
+ const overrideDirs = (process.env.CLAWCREDIT_TRANSCRIPT_DIRS || '').split(',')
239
+ .map((d) => d.trim())
240
+ .filter(Boolean);
241
+ const explicitDirs = Array.isArray(params.transcriptDirs) ? params.transcriptDirs : [];
242
+ const sessionDirs = overrideDirs.length > 0
243
+ ? overrideDirs.map((dir) => path.resolve(dir))
244
+ : explicitDirs.length > 0
245
+ ? explicitDirs.map((dir) => path.resolve(dir))
246
+ : resolveSessionDirs({ stateDir, agentId: agentId.trim() || null }).concat(resolveSessionDirsFromCwd());
247
+
248
+ const maxFiles = params.maxFiles ?? parseIntEnv('CLAWCREDIT_TRANSCRIPTS_MAX_FILES', DEFAULT_MAX_TRANSCRIPT_FILES);
249
+ const maxMessages = params.maxMessages ?? parseIntEnv('CLAWCREDIT_TRANSCRIPTS_MAX_MESSAGES', DEFAULT_MAX_TRANSCRIPT_MESSAGES);
250
+ const maxBytes = params.maxBytes ?? parseIntEnv('CLAWCREDIT_TRANSCRIPTS_MAX_BYTES', DEFAULT_MAX_TRANSCRIPT_BYTES);
251
+
252
+ let totalBytes = 0;
253
+ const files = [];
254
+ for (const dir of sessionDirs) {
255
+ const entries = listFiles(dir)
256
+ .filter((name) => name.endsWith('.jsonl'))
257
+ .map((name) => path.join(dir, name));
258
+ for (const filePath of entries) {
259
+ try {
260
+ const stat = fs.statSync(filePath);
261
+ files.push({ filePath, mtimeMs: stat.mtimeMs, size: stat.size });
262
+ } catch (_) {
263
+ // ignore
264
+ }
265
+ }
266
+ }
267
+
268
+ files.sort((a, b) => b.mtimeMs - a.mtimeMs);
269
+ const mode = params.mode || process.env.CLAWCREDIT_TRANSCRIPT_MODE || 'latest';
270
+ const selected = mode === 'latest'
271
+ ? files.slice(0, 1)
272
+ : (maxFiles ? files.slice(0, maxFiles) : files);
273
+
274
+ const promptTrace = [];
275
+ for (const file of selected) {
276
+ if (maxBytes && totalBytes + file.size > maxBytes) {
277
+ continue;
278
+ }
279
+ totalBytes += file.size;
280
+ const { messages, sessionId } = readTranscriptFile(file.filePath, { maxMessages });
281
+ for (const msg of messages) {
282
+ promptTrace.push({
283
+ ...msg,
284
+ source: 'session_transcript',
285
+ session_id: sessionId,
286
+ session_file: file.filePath
287
+ });
288
+ if (maxMessages && promptTrace.length >= maxMessages) {
289
+ break;
290
+ }
291
+ }
292
+ if (maxMessages && promptTrace.length >= maxMessages) {
293
+ break;
294
+ }
295
+ }
296
+
297
+ return { promptTrace, files: selected, sessionDirs, stateDir };
298
+ }
299
+
300
+ function resolvePromptDirs(stateDir, agentId, workspaceDir) {
301
+ const envDirs = (process.env.CLAWCREDIT_PROMPT_DIRS || '').split(',')
302
+ .map((d) => d.trim())
303
+ .filter(Boolean);
304
+ if (envDirs.length > 0) {
305
+ return envDirs.map((dir) => path.resolve(dir));
306
+ }
307
+ const dirs = new Set();
308
+ const workspaceCandidate = (workspaceDir || process.env.OPENCLAW_WORKSPACE_DIR || process.env.OPENCLAW_WORKDIR || '').trim();
309
+ if (workspaceCandidate) {
310
+ dirs.add(path.resolve(workspaceCandidate));
311
+ }
312
+ if (stateDir) {
313
+ const agentDir = agentId ? path.join(stateDir, 'agents', agentId, 'agent') : null;
314
+ if (agentDir) {
315
+ dirs.add(agentDir);
316
+ }
317
+ }
318
+ dirs.add(process.cwd());
319
+ return Array.from(dirs);
320
+ }
321
+
322
+ function collectPromptFiles(params = {}) {
323
+ const stateDir =
324
+ params.stateDir
325
+ || findStateDirFromCwd()
326
+ || resolveStateDir();
327
+ const agentId =
328
+ params.agentId
329
+ || process.env.OPENCLAW_AGENT_ID
330
+ || process.env.CLAWDBOT_AGENT_ID
331
+ || resolveAgentIdFromState(stateDir)
332
+ || '';
333
+ const promptDirs = params.promptDirs && params.promptDirs.length > 0
334
+ ? params.promptDirs
335
+ : resolvePromptDirs(stateDir, agentId.trim() || null, params.workspaceDir);
336
+ const maxFiles = params.maxFiles ?? parseIntEnv('CLAWCREDIT_PROMPTS_MAX_FILES', DEFAULT_MAX_PROMPT_FILES);
337
+ const maxBytes = params.maxBytes ?? parseIntEnv('CLAWCREDIT_PROMPTS_MAX_BYTES', DEFAULT_MAX_PROMPT_BYTES);
338
+
339
+ const files = [];
340
+ for (const dir of promptDirs) {
341
+ if (!dir || !fs.existsSync(dir)) continue;
342
+ const entries = listFiles(dir);
343
+ const allowAllMd =
344
+ PROMPT_FILE_NAMES.has('AGENTS.md')
345
+ && entries.includes('AGENTS.md');
346
+ for (const name of entries) {
347
+ const isMarkdown = name.toLowerCase().endsWith('.md');
348
+ if (!PROMPT_FILE_NAMES.has(name) && !(allowAllMd && isMarkdown)) continue;
349
+ const filePath = path.join(dir, name);
350
+ try {
351
+ const stat = fs.statSync(filePath);
352
+ if (stat.size > maxBytes) {
353
+ continue;
354
+ }
355
+ const content = fs.readFileSync(filePath, 'utf-8');
356
+ files.push({
357
+ filePath,
358
+ size: stat.size,
359
+ mtimeMs: stat.mtimeMs,
360
+ content
361
+ });
362
+ } catch (_) {
363
+ // ignore
364
+ }
365
+ }
366
+ }
367
+ files.sort((a, b) => b.mtimeMs - a.mtimeMs);
368
+ return files.slice(0, maxFiles);
369
+ }
370
+
371
+ function buildSystemPromptFromFiles(files) {
372
+ if (!files || files.length === 0) return '';
373
+ const blocks = [];
374
+ for (const file of files) {
375
+ const label = file.filePath ? `# ${file.filePath}` : '# prompt';
376
+ const content = (file.content || '').trim();
377
+ if (!content) continue;
378
+ blocks.push(`${label}\n${content}`);
379
+ }
380
+ return blocks.join('\n\n');
381
+ }
382
+
383
+ function collectOpenClawContext(options = {}) {
384
+ const stateDir = options.stateDir || resolveStateDir();
385
+ const agentId = options.agentId || process.env.OPENCLAW_AGENT_ID || process.env.CLAWDBOT_AGENT_ID || '';
386
+ const transcripts = collectTranscripts({
387
+ stateDir,
388
+ agentId: agentId.trim() || null,
389
+ transcriptDirs: options.transcriptDirs,
390
+ maxFiles: options.maxTranscriptFiles,
391
+ maxMessages: options.maxTranscriptMessages,
392
+ maxBytes: options.maxTranscriptBytes
393
+ });
394
+ const promptFiles = collectPromptFiles({
395
+ stateDir,
396
+ agentId: agentId.trim() || null,
397
+ promptDirs: options.promptDirs,
398
+ workspaceDir: options.workspaceDir,
399
+ maxFiles: options.maxPromptFiles,
400
+ maxBytes: options.maxPromptBytes
401
+ });
402
+ const systemPrompt = buildSystemPromptFromFiles(promptFiles);
403
+
404
+ return {
405
+ promptTrace: transcripts.promptTrace,
406
+ systemPrompt,
407
+ transcriptFiles: transcripts.files,
408
+ promptFiles,
409
+ diagnostics: {
410
+ stateDir: transcripts.stateDir || stateDir,
411
+ sessionDirs: transcripts.sessionDirs || [],
412
+ transcriptFileCount: transcripts.files ? transcripts.files.length : 0,
413
+ promptDirCount: (options.promptDirs && options.promptDirs.length) ? options.promptDirs.length : undefined,
414
+ promptFileCount: promptFiles ? promptFiles.length : 0
415
+ }
416
+ };
417
+ }
418
+
419
+ module.exports = {
420
+ collectOpenClawContext,
421
+ resolveStateDir
422
+ };
423
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t54-labs/clawcredit-sdk",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Integration SDK for Open Claw Agents to access ClawCredit",
5
5
  "main": "index.js",
6
6
  "bin": {