@way_marks/server 4.0.2 → 4.3.0

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.
@@ -0,0 +1,257 @@
1
+ "use strict";
2
+ /**
3
+ * Process-tree and system data collector.
4
+ *
5
+ * Mirrors abtop/src/collector/process.rs:
6
+ * - getProcessInfo() → ps -eo pid,ppid,rss,%cpu,command
7
+ * - getChildrenMap() → adjacency map from ppid
8
+ * - getListeningPorts() → lsof -i -P -n -sTCP:LISTEN
9
+ * - collectGitStats() → git -C <cwd> status --porcelain
10
+ * - hasActiveDescendant() → recursive CPU check
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.getProcessInfo = getProcessInfo;
47
+ exports.getChildrenMap = getChildrenMap;
48
+ exports.hasActiveDescendant = hasActiveDescendant;
49
+ exports.cmdHasBinary = cmdHasBinary;
50
+ exports.getListeningPorts = getListeningPorts;
51
+ exports.collectGitStats = collectGitStats;
52
+ exports.mapPidsToOpenPaths = mapPidsToOpenPaths;
53
+ const child_process_1 = require("child_process");
54
+ const path = __importStar(require("path"));
55
+ // ─── Process info ────────────────────────────────────────────────────────────
56
+ /**
57
+ * Run `ps` and return a map of pid → ProcInfo.
58
+ * Works on macOS and Linux (ps -eo is POSIX).
59
+ */
60
+ function getProcessInfo() {
61
+ const map = new Map();
62
+ let stdout;
63
+ try {
64
+ // -ww: unlimited command width -eo: exact output columns
65
+ stdout = (0, child_process_1.execSync)('ps -ww -eo pid,ppid,rss,%cpu,command', {
66
+ encoding: 'utf8',
67
+ stdio: ['ignore', 'pipe', 'ignore'],
68
+ });
69
+ }
70
+ catch {
71
+ return map;
72
+ }
73
+ const lines = stdout.split('\n').slice(1); // skip header
74
+ for (const line of lines) {
75
+ const parts = line.trim().split(/\s+/);
76
+ if (parts.length < 5)
77
+ continue;
78
+ const pid = parseInt(parts[0], 10);
79
+ const ppid = parseInt(parts[1], 10);
80
+ const rssKb = parseInt(parts[2], 10);
81
+ const cpuPct = parseFloat(parts[3]);
82
+ const command = parts.slice(4).join(' ');
83
+ if (isNaN(pid) || isNaN(ppid))
84
+ continue;
85
+ map.set(pid, { pid, ppid, rssKb: rssKb || 0, cpuPct: cpuPct || 0, command });
86
+ }
87
+ return map;
88
+ }
89
+ /**
90
+ * Build a parent-PID → child-PIDs map from the full process table.
91
+ */
92
+ function getChildrenMap(procs) {
93
+ const children = new Map();
94
+ for (const proc of procs.values()) {
95
+ const list = children.get(proc.ppid) ?? [];
96
+ list.push(proc.pid);
97
+ children.set(proc.ppid, list);
98
+ }
99
+ return children;
100
+ }
101
+ /**
102
+ * Return true if any descendant of `pid` has CPU% above `cpuThreshold`.
103
+ * Mirrors abtop's `has_active_descendant`.
104
+ */
105
+ function hasActiveDescendant(pid, childrenMap, processInfo, cpuThreshold = 5.0) {
106
+ const stack = [pid];
107
+ const visited = new Set();
108
+ while (stack.length > 0) {
109
+ const p = stack.pop();
110
+ if (visited.has(p))
111
+ continue;
112
+ visited.add(p);
113
+ const kids = childrenMap.get(p) ?? [];
114
+ for (const kid of kids) {
115
+ const info = processInfo.get(kid);
116
+ if (info && info.cpuPct > cpuThreshold)
117
+ return true;
118
+ stack.push(kid);
119
+ }
120
+ }
121
+ return false;
122
+ }
123
+ /**
124
+ * Check whether a command string contains a given binary name in executable
125
+ * position (first or second token — covers `node /path/to/codex ...`).
126
+ * Mirrors abtop's `cmd_has_binary`.
127
+ */
128
+ function cmdHasBinary(cmd, name) {
129
+ const tokens = cmd.trim().split(/\s+/).slice(0, 2);
130
+ return tokens.some((tok) => {
131
+ const base = tok.split('/').pop() ?? tok;
132
+ return base === name;
133
+ });
134
+ }
135
+ // ─── Listening ports ──────────────────────────────────────────────────────────
136
+ /**
137
+ * Return a map of pid → port[] by parsing `lsof -i -P -n -sTCP:LISTEN`.
138
+ * Mirrors abtop's `get_listening_ports` (non-Linux path).
139
+ */
140
+ function getListeningPorts() {
141
+ const map = new Map();
142
+ let stdout;
143
+ try {
144
+ stdout = (0, child_process_1.execSync)('lsof -i -P -n -sTCP:LISTEN', {
145
+ encoding: 'utf8',
146
+ stdio: ['ignore', 'pipe', 'ignore'],
147
+ });
148
+ }
149
+ catch {
150
+ return map;
151
+ }
152
+ for (const line of stdout.split('\n').slice(1)) {
153
+ const parts = line.trim().split(/\s+/);
154
+ // COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME (LISTEN)
155
+ if (parts.length < 9)
156
+ continue;
157
+ if (parts[7] !== 'TCP')
158
+ continue;
159
+ if (!line.includes('(LISTEN)'))
160
+ continue;
161
+ const pid = parseInt(parts[1], 10);
162
+ if (isNaN(pid))
163
+ continue;
164
+ // NAME field: *:3000 or 127.0.0.1:3000
165
+ const addr = parts[8] ?? '';
166
+ const portStr = addr.split(':').pop();
167
+ if (!portStr)
168
+ continue;
169
+ const port = parseInt(portStr, 10);
170
+ if (isNaN(port))
171
+ continue;
172
+ const ports = map.get(pid) ?? [];
173
+ if (!ports.includes(port))
174
+ ports.push(port);
175
+ map.set(pid, ports);
176
+ }
177
+ return map;
178
+ }
179
+ // ─── Git status ───────────────────────────────────────────────────────────────
180
+ /**
181
+ * Run `git -C <cwd> status --porcelain` and return [added, modified] counts.
182
+ * Mirrors abtop's `collect_git_stats`.
183
+ */
184
+ function collectGitStats(cwd) {
185
+ // Validate cwd is an absolute path before using as a working directory
186
+ if (!cwd || !path.isAbsolute(cwd))
187
+ return { added: 0, modified: 0 };
188
+ let stdout;
189
+ try {
190
+ stdout = (0, child_process_1.execFileSync)('git', ['status', '--porcelain'], {
191
+ cwd,
192
+ encoding: 'utf8',
193
+ stdio: ['ignore', 'pipe', 'ignore'],
194
+ });
195
+ }
196
+ catch {
197
+ return { added: 0, modified: 0 };
198
+ }
199
+ let added = 0;
200
+ let modified = 0;
201
+ for (const line of stdout.split('\n')) {
202
+ if (line.length < 2)
203
+ continue;
204
+ const code = line.slice(0, 2);
205
+ if (code.includes('?') || code.includes('A'))
206
+ added++;
207
+ else if (code.includes('M'))
208
+ modified++;
209
+ }
210
+ return { added, modified };
211
+ }
212
+ // ─── Open file paths via lsof ─────────────────────────────────────────────────
213
+ /**
214
+ * Map each PID to the list of file paths it has open.
215
+ * Uses `lsof -F ftn -p<pid1> -p<pid2> ...` (field output).
216
+ * Mirrors abtop's `map_pid_to_lsof_open_paths`.
217
+ */
218
+ function mapPidsToOpenPaths(pids) {
219
+ const map = new Map();
220
+ if (pids.length === 0)
221
+ return map;
222
+ // Build args array: per-PID filters use separate -p flags
223
+ const lsofArgs = ['-F', 'ftn', ...pids.map((p) => `-p${p}`)];
224
+ let stdout;
225
+ try {
226
+ stdout = (0, child_process_1.execFileSync)('lsof', lsofArgs, {
227
+ encoding: 'utf8',
228
+ stdio: ['ignore', 'pipe', 'ignore'],
229
+ });
230
+ }
231
+ catch {
232
+ return map;
233
+ }
234
+ let currentPid = null;
235
+ let currentFd = '';
236
+ for (const line of stdout.split('\n')) {
237
+ if (line.startsWith('p')) {
238
+ currentPid = parseInt(line.slice(1), 10) || null;
239
+ if (currentPid != null && !map.has(currentPid))
240
+ map.set(currentPid, []);
241
+ currentFd = '';
242
+ }
243
+ else if (line.startsWith('f')) {
244
+ currentFd = line.slice(1);
245
+ }
246
+ else if (line.startsWith('n') && currentPid != null) {
247
+ const name = line.slice(1);
248
+ if (!name || name.startsWith('['))
249
+ continue;
250
+ const paths = map.get(currentPid) ?? [];
251
+ paths.push(name);
252
+ if (!map.has(currentPid))
253
+ map.set(currentPid, paths);
254
+ }
255
+ }
256
+ return map;
257
+ }
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ /**
3
+ * Rate limit file reader for Claude Code.
4
+ *
5
+ * Mirrors abtop/src/collector/rate_limit.rs.
6
+ *
7
+ * Claude Code rate limits are NOT in the transcript JSONL. They are written
8
+ * by the StatusLine hook installed via `abtop --setup` (or `waymark --setup`).
9
+ *
10
+ * File: ~/.claude/abtop-rate-limits.json
11
+ * Format:
12
+ * {
13
+ * "source": "claude",
14
+ * "five_hour": { "used_percentage": 35.0, "resets_at": 1774715000 },
15
+ * "seven_day": { "used_percentage": 12.0, "resets_at": 1775320000 },
16
+ * "updated_at": 1774714400
17
+ * }
18
+ */
19
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ var desc = Object.getOwnPropertyDescriptor(m, k);
22
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
23
+ desc = { enumerable: true, get: function() { return m[k]; } };
24
+ }
25
+ Object.defineProperty(o, k2, desc);
26
+ }) : (function(o, m, k, k2) {
27
+ if (k2 === undefined) k2 = k;
28
+ o[k2] = m[k];
29
+ }));
30
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
31
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
32
+ }) : function(o, v) {
33
+ o["default"] = v;
34
+ });
35
+ var __importStar = (this && this.__importStar) || (function () {
36
+ var ownKeys = function(o) {
37
+ ownKeys = Object.getOwnPropertyNames || function (o) {
38
+ var ar = [];
39
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
40
+ return ar;
41
+ };
42
+ return ownKeys(o);
43
+ };
44
+ return function (mod) {
45
+ if (mod && mod.__esModule) return mod;
46
+ var result = {};
47
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
48
+ __setModuleDefault(result, mod);
49
+ return result;
50
+ };
51
+ })();
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.readClaudeRateLimits = readClaudeRateLimits;
54
+ exports.readCodexRateLimitCache = readCodexRateLimitCache;
55
+ exports.writeCodexRateLimitCache = writeCodexRateLimitCache;
56
+ const fs = __importStar(require("fs"));
57
+ const path = __importStar(require("path"));
58
+ const os = __importStar(require("os"));
59
+ const RATE_FILE_NAME = 'abtop-rate-limits.json';
60
+ /** Reject stale Claude rate limit data older than 10 minutes. */
61
+ const MAX_STALENESS_SECS = 600;
62
+ /**
63
+ * Read Claude rate limit info from all candidate config directories.
64
+ * Checks `~/.claude`, `CLAUDE_CONFIG_DIR` env var, and any extra dirs
65
+ * provided (e.g. discovered from running Claude process environments).
66
+ *
67
+ * Returns all valid, non-stale entries (typically zero or one).
68
+ */
69
+ function readClaudeRateLimits(extraDirs = []) {
70
+ const results = [];
71
+ const seen = new Set();
72
+ const candidates = [path.join(os.homedir(), '.claude')];
73
+ const envDir = process.env['CLAUDE_CONFIG_DIR'];
74
+ if (envDir)
75
+ candidates.push(envDir);
76
+ candidates.push(...extraDirs);
77
+ for (const dir of candidates) {
78
+ const resolved = path.resolve(dir);
79
+ if (seen.has(resolved))
80
+ continue;
81
+ seen.add(resolved);
82
+ if (!fs.existsSync(resolved))
83
+ continue;
84
+ const filePath = path.join(resolved, RATE_FILE_NAME);
85
+ const info = readRateFile(filePath, 'claude');
86
+ if (info)
87
+ results.push(info);
88
+ }
89
+ return results;
90
+ }
91
+ /**
92
+ * Read Codex rate limit from the shared cache file written by the collector.
93
+ * File: ~/.cache/abtop/codex-rate-limits.json
94
+ */
95
+ function readCodexRateLimitCache() {
96
+ const cacheDir = process.env['XDG_CACHE_HOME'] ?? path.join(os.homedir(), '.cache');
97
+ const filePath = path.join(cacheDir, 'abtop', 'codex-rate-limits.json');
98
+ return readRateFile(filePath, 'codex', /* checkStaleness */ false);
99
+ }
100
+ /**
101
+ * Atomically write Codex rate limit to the shared cache file.
102
+ * Mirrors abtop's `write_codex_cache`.
103
+ */
104
+ function writeCodexRateLimitCache(info) {
105
+ const cacheDir = process.env['XDG_CACHE_HOME'] ?? path.join(os.homedir(), '.cache');
106
+ const dir = path.join(cacheDir, 'abtop');
107
+ fs.mkdirSync(dir, { recursive: true });
108
+ const filePath = path.join(dir, 'codex-rate-limits.json');
109
+ const tmp = filePath + '.tmp';
110
+ const json = JSON.stringify({
111
+ source: 'codex',
112
+ five_hour: info.fiveHourPct != null
113
+ ? { used_percentage: info.fiveHourPct, resets_at: info.fiveHourResetsAt ?? 0 }
114
+ : null,
115
+ seven_day: info.sevenDayPct != null
116
+ ? { used_percentage: info.sevenDayPct, resets_at: info.sevenDayResetsAt ?? 0 }
117
+ : null,
118
+ updated_at: info.updatedAt ?? null,
119
+ });
120
+ try {
121
+ fs.writeFileSync(tmp, json);
122
+ fs.renameSync(tmp, filePath);
123
+ }
124
+ catch {
125
+ // Best-effort; ignore write failures
126
+ }
127
+ }
128
+ // ─── Internal helpers ─────────────────────────────────────────────────────────
129
+ function readRateFile(filePath, defaultSource, checkStaleness = true) {
130
+ let content;
131
+ try {
132
+ content = fs.readFileSync(filePath, 'utf8');
133
+ }
134
+ catch {
135
+ return null;
136
+ }
137
+ let file;
138
+ try {
139
+ file = JSON.parse(content);
140
+ }
141
+ catch {
142
+ return null;
143
+ }
144
+ // Must have at least one window
145
+ if (!file.five_hour && !file.seven_day)
146
+ return null;
147
+ // Reject stale Claude data
148
+ if (checkStaleness && file.updated_at != null) {
149
+ const nowSecs = Math.floor(Date.now() / 1000);
150
+ if (nowSecs - file.updated_at > MAX_STALENESS_SECS)
151
+ return null;
152
+ }
153
+ return {
154
+ source: file.source || defaultSource,
155
+ fiveHourPct: file.five_hour?.used_percentage,
156
+ fiveHourResetsAt: file.five_hour?.resets_at,
157
+ sevenDayPct: file.seven_day?.used_percentage,
158
+ sevenDayResetsAt: file.seven_day?.resets_at,
159
+ updatedAt: file.updated_at,
160
+ };
161
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ /**
3
+ * Secret pattern redaction utility.
4
+ *
5
+ * Mirrors abtop/src/collector/mod.rs `redact_secrets`.
6
+ * Best-effort: covers well-known prefixed tokens, not arbitrary high-entropy strings.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.redactSecrets = redactSecrets;
10
+ const SECRET_PATTERNS = [
11
+ // Anthropic / OpenAI / OpenRouter
12
+ 'sk-ant-', 'sk-proj-', 'sk-or-',
13
+ // Stripe
14
+ 'sk_live_', 'sk_test_', 'rk_live_', 'rk_test_',
15
+ // GitHub
16
+ 'ghp_', 'gho_', 'ghs_', 'ghr_', 'ghu_', 'github_pat_',
17
+ // GitLab
18
+ 'glpat-',
19
+ // Slack
20
+ 'xoxb-', 'xoxp-', 'xoxa-', 'xoxs-',
21
+ // AWS access key ids
22
+ 'AKIA', 'ASIA',
23
+ // Bearer-prefixed headers
24
+ 'Bearer ',
25
+ ];
26
+ /**
27
+ * Replace known secret prefixes and following non-whitespace chars with [REDACTED].
28
+ */
29
+ function redactSecrets(s) {
30
+ let result = s;
31
+ for (const pat of SECRET_PATTERNS) {
32
+ let pos = result.indexOf(pat);
33
+ while (pos !== -1) {
34
+ // Find end: next whitespace or end of string
35
+ let end = pos + pat.length;
36
+ while (end < result.length && !/\s/.test(result.charAt(end)))
37
+ end++;
38
+ result = result.slice(0, pos) + '[REDACTED]' + result.slice(end);
39
+ pos = result.indexOf(pat, pos + '[REDACTED]'.length);
40
+ }
41
+ }
42
+ return result;
43
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ /**
3
+ * Shared TypeScript types for the abtop-style agent monitor feature.
4
+ * These mirror the Rust structs in abtop/src/model/session.rs.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.CONTEXT_WINDOW_BY_MODEL = exports.MAX_FILE_ACCESSES = void 0;
8
+ exports.contextWindowForModel = contextWindowForModel;
9
+ exports.elapsedDisplay = elapsedDisplay;
10
+ /** Maximum file access entries kept per session. */
11
+ exports.MAX_FILE_ACCESSES = 1000;
12
+ /** Context window sizes by model name. */
13
+ exports.CONTEXT_WINDOW_BY_MODEL = {
14
+ 'claude-opus-4-6': 200000,
15
+ 'claude-opus-4-6[1m]': 1000000,
16
+ 'claude-sonnet-4-6': 200000,
17
+ 'claude-haiku-4-5': 200000,
18
+ 'claude-opus-4-5': 200000,
19
+ 'claude-sonnet-4-5': 200000,
20
+ 'claude-haiku-4': 200000,
21
+ 'claude-opus-4': 200000,
22
+ 'claude-sonnet-4': 200000,
23
+ // GitHub Copilot CLI supported models
24
+ 'gpt-5': 128000,
25
+ 'gpt-5-mini': 128000,
26
+ 'o3': 200000,
27
+ 'o4-mini': 200000,
28
+ };
29
+ /** Derive context window size from model name, falling back to 200k. */
30
+ function contextWindowForModel(model, maxSeen = 0) {
31
+ const known = exports.CONTEXT_WINDOW_BY_MODEL[model];
32
+ if (known)
33
+ return known;
34
+ // If we observed a turn with context > 200k, it must be a 1M model.
35
+ if (maxSeen > 200000)
36
+ return 1000000;
37
+ return 200000;
38
+ }
39
+ /** Elapsed time as a short human-readable string ("12s", "5m", "2h 10m"). */
40
+ function elapsedDisplay(startedAtMs) {
41
+ const secs = Math.floor((Date.now() - startedAtMs) / 1000);
42
+ if (secs < 60)
43
+ return `${secs}s`;
44
+ if (secs < 3600)
45
+ return `${Math.floor(secs / 60)}m`;
46
+ return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`;
47
+ }
@@ -57,7 +57,15 @@ const uuid_1 = require("uuid");
57
57
  const database_1 = require("../db/database");
58
58
  const engine_1 = require("../policies/engine");
59
59
  const slack_1 = require("../notifications/slack");
60
+ const agent_monitor_1 = require("./tools/agent-monitor");
60
61
  const SESSION_ID = (0, uuid_1.v4)();
62
+ // Agent monitor — fetch from the sibling API process on demand instead of
63
+ // running a second MultiCollector here. WAYMARK_PORT is set by `waymark start`
64
+ // when it spawns this MCP child; if absent we default to the v3.1+ default
65
+ // range start. fetchSnapshotFromApi() returns an empty snapshot when the API
66
+ // is unreachable, so the MCP tool calls degrade gracefully.
67
+ const API_PORT = parseInt(process.env.WAYMARK_PORT || '47000', 10);
68
+ const getAgentSnapshot = () => (0, agent_monitor_1.fetchSnapshotFromApi)(API_PORT);
61
69
  // Build PATH that includes the current node binary's directory.
62
70
  // process.execPath works regardless of version manager (nvm, Homebrew, system, etc.)
63
71
  function getUserPath() {
@@ -114,12 +122,18 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
114
122
  required: ['command'],
115
123
  },
116
124
  },
125
+ ...agent_monitor_1.AGENT_MONITOR_TOOL_DEFINITIONS,
117
126
  ],
118
127
  };
119
128
  });
120
129
  // Handle tool calls
121
130
  server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
122
131
  const { name, arguments: args } = request.params;
132
+ // Agent monitor tools — read-only, no logging needed.
133
+ // Snapshot is fetched from the sibling API process; empty if API unreachable.
134
+ const monitorResult = await (0, agent_monitor_1.handleAgentMonitorToolCall)(name, args, getAgentSnapshot);
135
+ if (monitorResult)
136
+ return monitorResult;
123
137
  const action_id = (0, uuid_1.v4)();
124
138
  const input_payload = JSON.stringify(args);
125
139
  if (name === 'write_file') {