@0xmaxma/claude-gateway 1.8.3 → 1.8.5

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.
Files changed (88) hide show
  1. package/README.md +58 -1
  2. package/config.template.json +5 -3
  3. package/dist/agent/model-catalog.d.ts.map +1 -1
  4. package/dist/agent/model-catalog.js +5 -13
  5. package/dist/agent/model-catalog.js.map +1 -1
  6. package/dist/agent/runner.d.ts +1 -1
  7. package/dist/agent/runner.d.ts.map +1 -1
  8. package/dist/agent/runner.js +22 -3
  9. package/dist/agent/runner.js.map +1 -1
  10. package/dist/api/apps-router.d.ts.map +1 -1
  11. package/dist/api/apps-router.js +31 -9
  12. package/dist/api/apps-router.js.map +1 -1
  13. package/dist/api/gateway-router.js +1 -1
  14. package/dist/api/gateway-router.js.map +1 -1
  15. package/dist/api/share-router.d.ts +8 -0
  16. package/dist/api/share-router.d.ts.map +1 -1
  17. package/dist/api/share-router.js +100 -10
  18. package/dist/api/share-router.js.map +1 -1
  19. package/dist/apps/agent-manager.d.ts +35 -2
  20. package/dist/apps/agent-manager.d.ts.map +1 -1
  21. package/dist/apps/agent-manager.js +131 -15
  22. package/dist/apps/agent-manager.js.map +1 -1
  23. package/dist/apps/installer.d.ts +41 -1
  24. package/dist/apps/installer.d.ts.map +1 -1
  25. package/dist/apps/installer.js +52 -3
  26. package/dist/apps/installer.js.map +1 -1
  27. package/dist/cli/commands/debug-bundle.d.ts.map +1 -1
  28. package/dist/cli/commands/debug-bundle.js +9 -52
  29. package/dist/cli/commands/debug-bundle.js.map +1 -1
  30. package/dist/cli/commands/gateway.d.ts +6 -1
  31. package/dist/cli/commands/gateway.d.ts.map +1 -1
  32. package/dist/cli/commands/gateway.js +14 -3
  33. package/dist/cli/commands/gateway.js.map +1 -1
  34. package/dist/cli/commands/logs.d.ts +37 -0
  35. package/dist/cli/commands/logs.d.ts.map +1 -0
  36. package/dist/cli/commands/logs.js +429 -0
  37. package/dist/cli/commands/logs.js.map +1 -0
  38. package/dist/cli/index.d.ts.map +1 -1
  39. package/dist/cli/index.js +21 -4
  40. package/dist/cli/index.js.map +1 -1
  41. package/dist/cli/logs-dir.d.ts +69 -0
  42. package/dist/cli/logs-dir.d.ts.map +1 -0
  43. package/dist/cli/logs-dir.js +166 -0
  44. package/dist/cli/logs-dir.js.map +1 -0
  45. package/dist/cli/output.d.ts +34 -0
  46. package/dist/cli/output.d.ts.map +1 -1
  47. package/dist/cli/output.js +56 -10
  48. package/dist/cli/output.js.map +1 -1
  49. package/dist/config/claude-settings.d.ts +41 -0
  50. package/dist/config/claude-settings.d.ts.map +1 -0
  51. package/dist/config/claude-settings.js +102 -0
  52. package/dist/config/claude-settings.js.map +1 -0
  53. package/dist/config/watcher.d.ts.map +1 -1
  54. package/dist/config/watcher.js +5 -0
  55. package/dist/config/watcher.js.map +1 -1
  56. package/dist/index.js +36 -1
  57. package/dist/index.js.map +1 -1
  58. package/dist/logger.d.ts +52 -1
  59. package/dist/logger.d.ts.map +1 -1
  60. package/dist/logger.js +264 -2
  61. package/dist/logger.js.map +1 -1
  62. package/dist/session/process.d.ts +43 -1
  63. package/dist/session/process.d.ts.map +1 -1
  64. package/dist/session/process.js +154 -9
  65. package/dist/session/process.js.map +1 -1
  66. package/dist/share/share-store.d.ts +45 -4
  67. package/dist/share/share-store.d.ts.map +1 -1
  68. package/dist/share/share-store.js +127 -23
  69. package/dist/share/share-store.js.map +1 -1
  70. package/dist/shell/bypass-dialog.d.ts +104 -12
  71. package/dist/shell/bypass-dialog.d.ts.map +1 -1
  72. package/dist/shell/bypass-dialog.js +146 -14
  73. package/dist/shell/bypass-dialog.js.map +1 -1
  74. package/dist/shell/claude-pty-shell.js +29 -4
  75. package/dist/shell/claude-pty-shell.js.map +1 -1
  76. package/dist/shell/screen.d.ts +15 -11
  77. package/dist/shell/screen.d.ts.map +1 -1
  78. package/dist/shell/screen.js +42 -33
  79. package/dist/shell/screen.js.map +1 -1
  80. package/dist/types.d.ts +20 -0
  81. package/dist/types.d.ts.map +1 -1
  82. package/mcp/server.ts +2 -2
  83. package/mcp/tools/image/module.ts +10 -3
  84. package/mcp/tools/share-file/module.ts +169 -0
  85. package/mcp/tools/shared/share-client.ts +8 -4
  86. package/mcp/tools/telegram/receiver-server.ts +12 -2
  87. package/package.json +1 -1
  88. package/mcp/tools/share-image/module.ts +0 -120
package/dist/logger.js CHANGED
@@ -33,16 +33,201 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.LOGS_DEFAULTS = void 0;
37
+ exports.isLogLevel = isLogLevel;
38
+ exports.configureLogging = configureLogging;
39
+ exports.loggingConfig = loggingConfig;
40
+ exports.resetLoggingForTests = resetLoggingForTests;
36
41
  exports.createLogger = createLogger;
42
+ exports.sweepOldLogs = sweepOldLogs;
43
+ exports.startLogRetentionSweep = startLogRetentionSweep;
37
44
  const fs = __importStar(require("fs"));
38
45
  const path = __importStar(require("path"));
46
+ /** Ordering for the level gate. Only the relative order matters. */
47
+ const LEVEL_RANK = { debug: 10, info: 20, warn: 30, error: 40 };
48
+ function isLogLevel(value) {
49
+ return typeof value === 'string' && value in LEVEL_RANK;
50
+ }
51
+ /**
52
+ * Defaults applied when `gateway.logs` is absent or partial.
53
+ *
54
+ * `level: 'info'` is the load-bearing one. Session processes log every stream
55
+ * event at debug (src/session/process.ts), which on a live host measured as
56
+ * 19,995 debug lines to 5 info lines in a single 217 MB file — so `debug`
57
+ * on-by-default was, in practice, the whole log directory. Rotation alone would
58
+ * not have helped: it bounds what is *kept*, not what is *written*.
59
+ */
60
+ exports.LOGS_DEFAULTS = {
61
+ level: 'info',
62
+ maxFileBytes: 16 * 1024 * 1024,
63
+ maxFiles: 3,
64
+ retentionDays: 14,
65
+ };
66
+ let active = { ...exports.LOGS_DEFAULTS };
67
+ /**
68
+ * Install the process-wide logging policy. Called once at boot, before the
69
+ * first logger exists.
70
+ *
71
+ * Policy is module state rather than a per-logger argument because there is one
72
+ * of it per process, and `createLogger()` has 13 call sites — several of which
73
+ * (routers, the session process) have no access to the gateway config. Threading
74
+ * an options bag through all of them would put the same value in 13 places and
75
+ * let them drift.
76
+ */
77
+ function configureLogging(cfg) {
78
+ const level = isLogLevel(cfg?.level) ? cfg.level : exports.LOGS_DEFAULTS.level;
79
+ active = {
80
+ level,
81
+ maxFileBytes: positiveOrDefault(cfg?.maxFileBytes, exports.LOGS_DEFAULTS.maxFileBytes),
82
+ maxFiles: nonNegativeOrDefault(cfg?.maxFiles, exports.LOGS_DEFAULTS.maxFiles),
83
+ retentionDays: nonNegativeOrDefault(cfg?.retentionDays, exports.LOGS_DEFAULTS.retentionDays),
84
+ };
85
+ return { ...active };
86
+ }
87
+ /** The policy currently in force (a copy — callers must not mutate it). */
88
+ function loggingConfig() {
89
+ return { ...active };
90
+ }
91
+ /** Test hook: restore defaults and forget per-file state. */
92
+ function resetLoggingForTests() {
93
+ active = { ...exports.LOGS_DEFAULTS };
94
+ fileSizes.clear();
95
+ writeFailureReported = false;
96
+ }
97
+ /** 0 is meaningful for `maxFiles`/`retentionDays` (= disabled) but not for a
98
+ * size threshold, where it would rotate on every line. */
99
+ function positiveOrDefault(value, fallback) {
100
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
101
+ }
102
+ function nonNegativeOrDefault(value, fallback) {
103
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? Math.floor(value) : fallback;
104
+ }
105
+ /**
106
+ * Live size per log file, keyed by absolute path.
107
+ *
108
+ * Kept at module scope rather than on the instance because one file can have
109
+ * several loggers writing to it — `createLogger(agentConfig.id, …)` is called
110
+ * from both the boot path and the runner constructor. Per-instance counters
111
+ * would each see only their own share of the file and rotate late. Seeded from
112
+ * `statSync` once per file, then maintained by arithmetic so the common path
113
+ * costs no syscall.
114
+ *
115
+ * One entry per log file, and session logs create a new file each. The
116
+ * retention sweep deletes its entry along with the file, which bounds the map
117
+ * for any configuration that keeps retention on; with `retentionDays: 0` it
118
+ * grows with the session count, at roughly a path string per session. That is
119
+ * small enough not to be worth a second eviction mechanism whose only job would
120
+ * be to duplicate the sweep.
121
+ */
122
+ const fileSizes = new Map();
123
+ /**
124
+ * A failed append is latched and reported once per process.
125
+ *
126
+ * This used to be a bare `catch {}` — a full disk or a permission change
127
+ * silently stopped all file logging, which is precisely the failure that makes
128
+ * the next incident undiagnosable. Reporting every occurrence would be its own
129
+ * denial of service (the failing call is in the logger), so it is reported once
130
+ * and then suppressed.
131
+ */
132
+ let writeFailureReported = false;
133
+ function reportWriteFailure(file, err) {
134
+ if (writeFailureReported)
135
+ return;
136
+ writeFailureReported = true;
137
+ const reason = err?.code ?? err?.message ?? String(err);
138
+ process.stderr.write(`[logger] cannot write to ${file} (${reason}) — file logging is degraded for the rest of this process. ` +
139
+ 'Further write failures will not be reported.\n');
140
+ }
141
+ /**
142
+ * Remove every rotated generation at or above `maxFiles`.
143
+ *
144
+ * Deleting only `<file>.<maxFiles>` would be dead code: `renameSync` clobbers
145
+ * its destination, so the top generation is overwritten by the shift below
146
+ * anyway — but only while `maxFiles` stays where it was. Lower it in the config
147
+ * (5 → 2) and generations 2..5 are orphaned: nothing renames onto them, and the
148
+ * age sweep is the only thing that would ever collect them. A rotation happens
149
+ * once per `maxFileBytes`, so it can afford one readdir to stay bounded.
150
+ */
151
+ function pruneGenerations(file, maxFiles) {
152
+ const dir = path.dirname(file);
153
+ const prefix = `${path.basename(file)}.`;
154
+ let names;
155
+ try {
156
+ names = fs.readdirSync(dir);
157
+ }
158
+ catch {
159
+ return; // best-effort
160
+ }
161
+ for (const name of names) {
162
+ if (!name.startsWith(prefix))
163
+ continue;
164
+ const gen = Number(name.slice(prefix.length));
165
+ if (!Number.isInteger(gen) || gen < maxFiles)
166
+ continue;
167
+ try {
168
+ fs.rmSync(path.join(dir, name), { force: true });
169
+ }
170
+ catch {
171
+ /* best-effort */
172
+ }
173
+ }
174
+ }
175
+ /** `<name>.log` → `<name>.log.1`, dropping generations past `maxFiles`.
176
+ *
177
+ * Synchronous and best-effort by design: it runs inside a logging call, so it
178
+ * must neither block on I/O the caller did not ask for nor throw into code
179
+ * that was only trying to log. A rotation that fails leaves the live file in
180
+ * place and logging simply continues into it. */
181
+ function rotate(file, maxFiles) {
182
+ if (maxFiles <= 0) {
183
+ // No generations kept: the live file is the only file, so start it over.
184
+ try {
185
+ fs.unlinkSync(file);
186
+ }
187
+ catch {
188
+ /* best-effort */
189
+ }
190
+ return;
191
+ }
192
+ pruneGenerations(file, maxFiles);
193
+ for (let gen = maxFiles - 1; gen >= 1; gen--) {
194
+ try {
195
+ fs.renameSync(`${file}.${gen}`, `${file}.${gen + 1}`);
196
+ }
197
+ catch {
198
+ /* a generation that does not exist yet is the normal case */
199
+ }
200
+ }
201
+ try {
202
+ fs.renameSync(file, `${file}.1`);
203
+ }
204
+ catch {
205
+ /* best-effort */
206
+ }
207
+ }
39
208
  class AgentLogger {
40
209
  constructor(agentId, logDir) {
41
210
  this.agentId = agentId;
42
211
  fs.mkdirSync(logDir, { recursive: true });
43
212
  this.logFilePath = path.join(logDir, `${agentId}.log`);
44
213
  }
214
+ currentSize() {
215
+ const known = fileSizes.get(this.logFilePath);
216
+ if (known !== undefined)
217
+ return known;
218
+ let size = 0;
219
+ try {
220
+ size = fs.statSync(this.logFilePath).size;
221
+ }
222
+ catch {
223
+ size = 0; // not created yet
224
+ }
225
+ fileSizes.set(this.logFilePath, size);
226
+ return size;
227
+ }
45
228
  log(level, message, data) {
229
+ if (LEVEL_RANK[level] < LEVEL_RANK[active.level])
230
+ return;
46
231
  const entry = {
47
232
  ts: new Date().toISOString(),
48
233
  agentId: this.agentId,
@@ -55,11 +240,18 @@ class AgentLogger {
55
240
  // Write to stdout (pretty-printed for readability)
56
241
  process.stdout.write(pretty + '\n');
57
242
  // Write to log file (compact, one entry per line)
243
+ const bytes = Buffer.byteLength(line, 'utf-8') + 1;
58
244
  try {
245
+ if (active.maxFileBytes > 0 && this.currentSize() + bytes > active.maxFileBytes) {
246
+ rotate(this.logFilePath, active.maxFiles);
247
+ fileSizes.set(this.logFilePath, 0);
248
+ }
59
249
  fs.appendFileSync(this.logFilePath, line + '\n', 'utf-8');
250
+ fileSizes.set(this.logFilePath, this.currentSize() + bytes);
60
251
  }
61
- catch {
62
- // If we can't write to the log file, just continue
252
+ catch (err) {
253
+ // Logging must never throw into a caller, but it must not vanish either.
254
+ reportWriteFailure(this.logFilePath, err);
63
255
  }
64
256
  }
65
257
  info(message, data) {
@@ -78,4 +270,74 @@ class AgentLogger {
78
270
  function createLogger(agentId, logDir) {
79
271
  return new AgentLogger(agentId, logDir);
80
272
  }
273
+ /**
274
+ * Delete log files (live and rotated generations) last modified more than
275
+ * `retentionDays` ago. Returns the paths removed.
276
+ *
277
+ * Age, not count, is what bounds a directory of *session* logs: each session
278
+ * gets its own `<agent>:session:<uuid>.log` that is never written again once the
279
+ * session ends, so `maxFiles` — which only prunes generations of one stream —
280
+ * never touches them.
281
+ */
282
+ function sweepOldLogs(logDir, retentionDays, now = Date.now()) {
283
+ const removed = [];
284
+ if (!(retentionDays > 0))
285
+ return removed; // 0 = keep forever
286
+ const cutoff = now - retentionDays * 24 * 60 * 60 * 1000;
287
+ let names;
288
+ try {
289
+ names = fs.readdirSync(logDir);
290
+ }
291
+ catch {
292
+ return removed; // no directory yet, or unreadable — nothing to sweep
293
+ }
294
+ for (const name of names) {
295
+ // `.log` and its rotated generations (`.log.1`), and nothing else: the
296
+ // directory is not exclusively ours to delete from.
297
+ if (!/\.log(\.\d+)?$/.test(name))
298
+ continue;
299
+ const full = path.join(logDir, name);
300
+ try {
301
+ if (fs.statSync(full).mtimeMs >= cutoff)
302
+ continue;
303
+ fs.rmSync(full, { force: true });
304
+ fileSizes.delete(full);
305
+ removed.push(full);
306
+ }
307
+ catch {
308
+ /* best-effort — a file that vanished or cannot be read is not fatal */
309
+ }
310
+ }
311
+ return removed;
312
+ }
313
+ /**
314
+ * Sweep now, then once a day. Returns a stop function.
315
+ *
316
+ * Modelled on `AppInstaller.startBackupCleanup()`: the timer is unref'd so it
317
+ * never keeps the process alive, and a failing sweep is swallowed rather than
318
+ * escalated — retention is housekeeping, not a reason to take the gateway down.
319
+ *
320
+ * The timer is created unconditionally, including when retention is currently
321
+ * off. `retentionDays` is read on each run rather than captured, so a policy
322
+ * reloaded from `gateway.logs` takes effect at the next sweep — and a timer
323
+ * that was never started because retention happened to be 0 at boot could not
324
+ * do that. A disabled sweep costs one no-op call a day.
325
+ */
326
+ function startLogRetentionSweep(logDir, onSwept) {
327
+ const run = () => {
328
+ try {
329
+ const removed = sweepOldLogs(logDir, active.retentionDays);
330
+ if (removed.length > 0)
331
+ onSwept?.(removed);
332
+ }
333
+ catch {
334
+ /* best-effort */
335
+ }
336
+ };
337
+ run();
338
+ const timer = setInterval(run, 24 * 60 * 60 * 1000);
339
+ if (typeof timer.unref === 'function')
340
+ timer.unref();
341
+ return () => clearInterval(timer);
342
+ }
81
343
  //# sourceMappingURL=logger.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,oCAEC;AAlED,uCAAyB;AACzB,2CAA6B;AAa7B,MAAM,WAAW;IAIf,YAAY,OAAe,EAAE,MAAc;QACzC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,OAAO,MAAM,CAAC,CAAC;IACzD,CAAC;IAEO,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,IAA8B;QAC1E,MAAM,KAAK,GAAa;YACtB,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC5B,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,KAAK;YACL,OAAO;YACP,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxC,CAAC;QAEF,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAE9C,mDAAmD;QACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;QAEpC,kDAAkD;QAClD,IAAI,CAAC;YACH,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5D,CAAC;QAAC,MAAM,CAAC;YACP,mDAAmD;QACrD,CAAC;IACH,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAA8B;QAClD,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAA8B;QAClD,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAA8B;QACnD,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAA8B;QACnD,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;CACF;AAED,SAAgB,YAAY,CAAC,OAAe,EAAE,MAAc;IAC1D,OAAO,IAAI,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AAC1C,CAAC"}
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"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"}
@@ -87,10 +87,52 @@ export declare class SessionProcess extends EventEmitter {
87
87
  private setupRestartWatcher;
88
88
  private buildInitialPrompt;
89
89
  /**
90
- * Read stdio MCP servers from Claude Code's user-scoped config (~/.claude/settings.json).
90
+ * Read stdio MCP servers from Claude Code's user-scoped config
91
+ * (`$CLAUDE_CONFIG_DIR/settings.json`, else `~/.claude/settings.json`).
91
92
  * Returns empty object if file doesn't exist, can't be parsed, or has no mcpServers.
92
93
  */
93
94
  private readUserScopedMcp;
95
+ /**
96
+ * Resolve the Claude credentials handed to an app-agent container this spawn.
97
+ *
98
+ * Container agents used to receive their credentials as a read-only bind mount
99
+ * of the host ~/.claude/settings.json. A Docker *file* bind mount pins an
100
+ * inode rather than a path, and Claude Code rewrites that file by atomic
101
+ * rename — so the first host settings change left every long-lived container
102
+ * reading a deleted inode. Claude Code silently ignores a settings.json whose
103
+ * inode is unlinked, so the agent stayed healthy and answered every real user
104
+ * "Not logged in · Please run /login" until someone recreated the container.
105
+ *
106
+ * That mechanism was verified directly rather than inferred: with content held
107
+ * byte-identical (same md5) and only the link count differing, a container
108
+ * reading the unlinked inode reported "Not logged in" and sent no request,
109
+ * while the same file at nlink=1 authenticated normally.
110
+ *
111
+ * Resolving by path here is immune to that: the container is handed the
112
+ * current credentials every time a session subprocess is spawned. Because the
113
+ * unlinked file is *ignored* rather than merely stale, the forwarded env is
114
+ * what the CLI ends up using — so a container stuck on a stale mount recovers
115
+ * on its next session spawn, with no container recreate. Note the cadence is
116
+ * per spawn, not per turn: one `docker exec` serves a whole session and later
117
+ * turns are written to its stdin, so a rotated credential reaches a live
118
+ * session only when it is respawned (idle reap, restart or config change).
119
+ *
120
+ * settings.json wins over the gateway's own environment. That matches Claude
121
+ * Code, which applies the file's `env` block over whatever it inherited (also
122
+ * verified: with both sources set to different endpoints, every request went
123
+ * to the file's), and it keeps the live file as the single source of truth — a
124
+ * long-lived gateway started with an exported token would otherwise pin the
125
+ * container to a credential that can never be rotated without restarting the
126
+ * service.
127
+ *
128
+ * Credentials resolve as a GROUP, not key by key. If the file declares an
129
+ * identity at all, the gateway's environment must not contribute a *different*
130
+ * one: forwarding a file OAuth token alongside an environment
131
+ * ANTHROPIC_API_KEY would hand the container two identities and let the CLI
132
+ * choose between them silently. A base URL only selects an endpoint, so it is
133
+ * not part of that group and keeps its own independent fallback.
134
+ */
135
+ private resolveContainerAuthEnv;
94
136
  /**
95
137
  * Read stdio MCP servers from Claude Code's project-scoped config (~/.claude.json).
96
138
  * Looks up projects[workspace].mcpServers for the agent's workspace path.
@@ -1 +1 @@
1
- {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/session/process.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAKtC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGtD,OAAO,EAAmC,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAYvC,eAAO,MAAM,oBAAoB,KAAK,CAAC;AA+EvC,eAAO,MAAM,yBAAyB,oDAAoD,CAAC;AAE3F;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,CAAC,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAMR;AAuCD,qBAAa,cAAe,SAAQ,YAAY;IAC9C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAc;IAC7C,cAAc,SAAc;IAQ5B,OAAO,CAAC,6BAA6B,CAAuB;IAG5D,OAAO,CAAC,iBAAiB,CAAqC;IAM9D,OAAO,CAAC,2BAA2B,CAAS;IAC5C,OAAO,CAAC,mBAAmB,CAA8C;IACzE,QAAQ,CAAC,SAAS,SAAc;IAChC,qFAAqF;IACrF,OAAO,EAAE,WAAW,GAAG,UAAU,CAAc;IAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;IAMvB,YAAY,EAAE,MAAM,CAAwB;IAM5C,aAAa,EAAE,OAAO,CAAS;IAC/B,YAAY,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAQ;IAC1G,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAAK;IAKzB,OAAO,CAAC,WAAW,CAAK;IAOxB,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAS;IAUhC,OAAO,CAAC,OAAO,CAAS;IAMxB,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,cAAc,CAAmC;IACzD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,SAAS,UAAS;IAIlB,OAAO,CAAC,eAAe,CAAK;IAE5B,UAAU,SAAM;IAChB,OAAO,CAAC,qBAAqB,CAAK;IAKlC,OAAO,CAAC,qBAAqB,CAAqB;IAMlD,OAAO,CAAC,aAAa,CAAY;IACjC,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,YAAY,CAAM;IAE1B,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,aAAa,CAAC,CAAyB;IAC/C,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,WAAW,CAAC,CAAgC;IACpD,OAAO,CAAC,aAAa,CAAS;IAE9B,OAAO,CAAC,oBAAoB,CAAC,CAAS;gBAGpC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,aAAa,EAC5B,YAAY,EAAE,YAAY,EAC1B,MAAM,CAAC,EAAE,MAAM;IAmBjB;;;OAGG;IACH,OAAO,KAAK,SAAS,GAEpB;IAED,OAAO,CAAC,aAAa;IAMrB,wFAAwF;IACxF,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,0EAA0E;IAC1E,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,gEAAgE;IAChE,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,OAAO,CAAC,cAAc;IAchB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;YA8Bb,kBAAkB;IA4DhC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAUzB;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,cAAc;IAsHtB,8FAA8F;IAC9F,OAAO,CAAC,kBAAkB;IAY1B,qFAAqF;IACrF,OAAO,CAAC,kBAAkB;IAQ1B;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;IAY/B,OAAO,CAAC,SAAS;IAsCjB,OAAO,CAAC,MAAM,CAAC,gBAAgB;YAOjB,YAAY;IA+gB1B,OAAO,CAAC,eAAe;IA4DvB;;;;;OAKG;IACH,MAAM,CAAC,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAI5D;;;;;OAKG;IACH,OAAO,CAAC,4BAA4B;IAmBpC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IA+B/B;;;;;;OAMG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAmB/C;;;;;;;OAOG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAkBhC,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IAqB1D,IAAI,YAAY,IAAI,OAAO,CAA6B;IAExD,OAAO,CAAC,wBAAwB;IAYhC,OAAO,CAAC,iCAAiC;IAczC,OAAO,CAAC,4BAA4B;IAOpC;;;;;;;;OAQG;IACH,4BAA4B,IAAI,OAAO;IA8BvC,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAwCpC,SAAS,IAAI,OAAO;IAWpB;;;;;;OAMG;IACH,oBAAoB,IAAI,OAAO;IAM/B,kBAAkB,IAAI,IAAI;IAQ1B;;;;;;OAMG;IACH,iBAAiB,IAAI,OAAO;IAI5B;;;;;;;;;OASG;IACH,kBAAkB,IAAI,OAAO;IAI7B,KAAK,IAAI,IAAI;IAIb,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAI/B;;;;;;;;OAQG;IACH,kCAAkC,IAAI,OAAO;IAK7C,SAAS,IAAI,OAAO;IAOd,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CA6B5B"}
1
+ {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/session/process.ts"],"names":[],"mappings":"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"}
@@ -51,8 +51,35 @@ const logger_1 = require("../logger");
51
51
  const pty_stream_registry_1 = require("../shell/pty-stream-registry");
52
52
  const screen_1 = require("../shell/screen");
53
53
  const claude_bin_1 = require("./claude-bin");
54
+ const claude_settings_1 = require("../config/claude-settings");
54
55
  const tool_labels_1 = require("../utils/tool-labels");
55
56
  exports.MAX_HISTORY_MESSAGES = 50;
57
+ /**
58
+ * Claude credential / API-routing env vars forwarded from the host into an
59
+ * app-agent container. Deliberately narrow — auth and endpoint only. Model and
60
+ * behaviour settings come from the agent's own config, not the host env.
61
+ *
62
+ * The keys that prove an identity, as opposed to merely selecting an endpoint.
63
+ * These resolve as a group so a container is never handed two credentials from
64
+ * two different sources — see resolveContainerAuthEnv().
65
+ */
66
+ const CONTAINER_CREDENTIAL_KEYS = [
67
+ 'CLAUDE_CODE_OAUTH_TOKEN',
68
+ 'ANTHROPIC_API_KEY',
69
+ 'ANTHROPIC_AUTH_TOKEN',
70
+ ];
71
+ /**
72
+ * The credential keys plus the endpoint selector. Derived from
73
+ * CONTAINER_CREDENTIAL_KEYS rather than repeated, so the two lists cannot drift:
74
+ * a credential added to one is automatically forwarded by the other.
75
+ *
76
+ * ANTHROPIC_BASE_URL is not a credential — it only selects an endpoint — so it
77
+ * sits outside the group that resolves atomically in resolveContainerAuthEnv().
78
+ */
79
+ const CONTAINER_AUTH_ENV_KEYS = [
80
+ ...CONTAINER_CREDENTIAL_KEYS,
81
+ 'ANTHROPIC_BASE_URL',
82
+ ];
56
83
  // The MCP reply tools that deliver an agent's user-facing message to a channel.
57
84
  // Their text lives in the tool_use `input.text`, not in an assistant text block,
58
85
  // so it is never captured by `assistantBuffer` — the reply is delivered to the
@@ -426,18 +453,117 @@ class SessionProcess extends events_1.EventEmitter {
426
453
  };
427
454
  }
428
455
  /**
429
- * Read stdio MCP servers from Claude Code's user-scoped config (~/.claude/settings.json).
456
+ * Read stdio MCP servers from Claude Code's user-scoped config
457
+ * (`$CLAUDE_CONFIG_DIR/settings.json`, else `~/.claude/settings.json`).
430
458
  * Returns empty object if file doesn't exist, can't be parsed, or has no mcpServers.
431
459
  */
432
460
  readUserScopedMcp() {
433
- try {
434
- const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
435
- const parsed = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
436
- return parsed?.mcpServers ?? {};
437
- }
438
- catch {
461
+ const servers = (0, claude_settings_1.readClaudeSettings)()?.mcpServers;
462
+ if (!servers || typeof servers !== 'object' || Array.isArray(servers))
439
463
  return {};
464
+ return servers;
465
+ }
466
+ /**
467
+ * Resolve the Claude credentials handed to an app-agent container this spawn.
468
+ *
469
+ * Container agents used to receive their credentials as a read-only bind mount
470
+ * of the host ~/.claude/settings.json. A Docker *file* bind mount pins an
471
+ * inode rather than a path, and Claude Code rewrites that file by atomic
472
+ * rename — so the first host settings change left every long-lived container
473
+ * reading a deleted inode. Claude Code silently ignores a settings.json whose
474
+ * inode is unlinked, so the agent stayed healthy and answered every real user
475
+ * "Not logged in · Please run /login" until someone recreated the container.
476
+ *
477
+ * That mechanism was verified directly rather than inferred: with content held
478
+ * byte-identical (same md5) and only the link count differing, a container
479
+ * reading the unlinked inode reported "Not logged in" and sent no request,
480
+ * while the same file at nlink=1 authenticated normally.
481
+ *
482
+ * Resolving by path here is immune to that: the container is handed the
483
+ * current credentials every time a session subprocess is spawned. Because the
484
+ * unlinked file is *ignored* rather than merely stale, the forwarded env is
485
+ * what the CLI ends up using — so a container stuck on a stale mount recovers
486
+ * on its next session spawn, with no container recreate. Note the cadence is
487
+ * per spawn, not per turn: one `docker exec` serves a whole session and later
488
+ * turns are written to its stdin, so a rotated credential reaches a live
489
+ * session only when it is respawned (idle reap, restart or config change).
490
+ *
491
+ * settings.json wins over the gateway's own environment. That matches Claude
492
+ * Code, which applies the file's `env` block over whatever it inherited (also
493
+ * verified: with both sources set to different endpoints, every request went
494
+ * to the file's), and it keeps the live file as the single source of truth — a
495
+ * long-lived gateway started with an exported token would otherwise pin the
496
+ * container to a credential that can never be rotated without restarting the
497
+ * service.
498
+ *
499
+ * Credentials resolve as a GROUP, not key by key. If the file declares an
500
+ * identity at all, the gateway's environment must not contribute a *different*
501
+ * one: forwarding a file OAuth token alongside an environment
502
+ * ANTHROPIC_API_KEY would hand the container two identities and let the CLI
503
+ * choose between them silently. A base URL only selects an endpoint, so it is
504
+ * not part of that group and keeps its own independent fallback.
505
+ */
506
+ resolveContainerAuthEnv() {
507
+ // Resolved through the shared helper so an operator who relocated the config
508
+ // with CLAUDE_CONFIG_DIR is honoured here too. Hardcoding ~/.claude would
509
+ // read a path that does not exist and forward nothing — reproducing the very
510
+ // "Not logged in" failure this method was written to prevent.
511
+ const fileEnv = (0, claude_settings_1.claudeSettingsEnv)();
512
+ // Presence, not validity: a credential key spelled out in settings.json is
513
+ // authoritative for the whole group even when its value is malformed, so a
514
+ // broken token can never silently fall back to a different identity.
515
+ const fileDeclaresCredential = CONTAINER_CREDENTIAL_KEYS.some((k) => k in fileEnv);
516
+ const resolved = {};
517
+ const dropped = [];
518
+ for (const key of CONTAINER_AUTH_ENV_KEYS) {
519
+ let value;
520
+ let origin;
521
+ if (key in fileEnv) {
522
+ value = fileEnv[key];
523
+ origin = 'settings.json';
524
+ }
525
+ else if (fileDeclaresCredential && CONTAINER_CREDENTIAL_KEYS.includes(key)) {
526
+ continue; // the file owns the identity — don't mix in another one
527
+ }
528
+ else {
529
+ value = process.env[key];
530
+ origin = 'gateway env';
531
+ }
532
+ // settings.json is untrusted input: forward only a clean, non-empty
533
+ // string. A NUL byte throws at spawn, and a newline is never legitimate
534
+ // in a credential or a base URL.
535
+ if (value === undefined)
536
+ continue; // simply not set anywhere — not a defect
537
+ if (typeof value !== 'string') {
538
+ dropped.push({ key, reason: `${origin} value is ${typeof value}, not a string` });
539
+ continue;
540
+ }
541
+ if (value.length === 0) {
542
+ dropped.push({ key, reason: `${origin} value is empty` });
543
+ continue;
544
+ }
545
+ if (/[\0\r\n]/.test(value)) {
546
+ dropped.push({ key, reason: `${origin} value contains a NUL or newline` });
547
+ continue;
548
+ }
549
+ resolved[key] = value;
550
+ }
551
+ // Never fail silently. The bug this whole path exists to fix presented as a
552
+ // container that looked healthy while answering real users "Not logged in",
553
+ // so a spawn that forwards no credential at all must say so — otherwise a
554
+ // malformed token in settings.json reproduces the original symptom with the
555
+ // original silence. `dropped` names the reason without ever logging a value.
556
+ const forwardedCredentials = CONTAINER_CREDENTIAL_KEYS.filter((k) => k in resolved);
557
+ if (forwardedCredentials.length === 0) {
558
+ this.logger.warn('No Claude credential could be resolved for this container agent — it will likely report "Not logged in"', { sessionId: this.sessionId, dropped, checked: CONTAINER_AUTH_ENV_KEYS });
440
559
  }
560
+ else if (dropped.length > 0) {
561
+ this.logger.debug('Some container auth env vars were not forwarded', {
562
+ sessionId: this.sessionId,
563
+ dropped,
564
+ });
565
+ }
566
+ return resolved;
441
567
  }
442
568
  /**
443
569
  * Read stdio MCP servers from Claude Code's project-scoped config (~/.claude.json).
@@ -547,6 +673,11 @@ class SessionProcess extends events_1.EventEmitter {
547
673
  IMAGE_API_KEY: process.env.IMAGE_API_KEY ?? process.env.ANTHROPIC_AUTH_TOKEN ?? '',
548
674
  IMAGE_DISABLED: process.env.IMAGE_DISABLED ?? '',
549
675
  IMAGE_POLL_TIMEOUT_MS: process.env.IMAGE_POLL_TIMEOUT_MS ?? '',
676
+ // #444: the share bridge's env vars were renamed IMAGE_SHARE_* ->
677
+ // SHARE_* when it stopped being image-only. Forward BOTH so an
678
+ // operator who set either name keeps working; the MCP side reads
679
+ // the neutral name first and falls back to the legacy one.
680
+ SHARE_MAX_REFS: process.env.SHARE_MAX_REFS ?? '',
550
681
  IMAGE_SHARE_MAX_REFS: process.env.IMAGE_SHARE_MAX_REFS ?? '',
551
682
  // Short-lived public media URLs (LINE image delivery). line_image
552
683
  // mints a share token via the gateway share bridge (using
@@ -732,12 +863,25 @@ class SessionProcess extends events_1.EventEmitter {
732
863
  const containerEnv = {
733
864
  HOME: os.homedir(),
734
865
  CLAUDE_WORKSPACE: '/workspace',
735
- TELEGRAM_BOT_TOKEN: this.agentConfig.telegram?.botToken ?? '',
736
866
  GATEWAY_RESTART_SIGNAL_PATH: containerRestartPath,
737
867
  };
738
868
  if (process.env.GATEWAY_API_URL)
739
869
  containerEnv.GATEWAY_API_URL = process.env.GATEWAY_API_URL;
740
- const dockerEnvFlags = Object.entries(containerEnv).flatMap(([k, v]) => ['-e', `${k}=${v}`]);
870
+ // Secrets are forwarded by NAME only (`-e KEY`, no `=value`), which makes
871
+ // Docker read the value from this process's own environment (set on the spawn
872
+ // below). The value therefore never lands on the docker argv, where any local
873
+ // user could read it out of `ps`: /proc/<pid>/cmdline is world-readable on a
874
+ // stock kernel, whereas /proc/<pid>/environ is owner-only.
875
+ //
876
+ // TELEGRAM_BOT_TOKEN travels by name for exactly the same reason as the
877
+ // Claude credentials — it is a bearer token for the agent's whole bot
878
+ // account. It is already placed in the spawn env below, unconditionally.
879
+ const containerAuthEnv = isAppAgent ? this.resolveContainerAuthEnv() : {};
880
+ const byNameKeys = ['TELEGRAM_BOT_TOKEN', ...Object.keys(containerAuthEnv)];
881
+ const dockerEnvFlags = [
882
+ ...Object.entries(containerEnv).flatMap(([k, v]) => ['-e', `${k}=${v}`]),
883
+ ...byNameKeys.flatMap((k) => ['-e', k]),
884
+ ];
741
885
  const spawnArgs = isAppAgent
742
886
  ? [
743
887
  'exec', '--workdir', '/workspace', '--user', String(containerUid), '-i',
@@ -761,6 +905,7 @@ class SessionProcess extends events_1.EventEmitter {
761
905
  const proc = (0, child_process_1.spawn)(spawnBin, spawnArgs, {
762
906
  env: {
763
907
  ...process.env,
908
+ ...containerAuthEnv,
764
909
  ...(hardenedPath ? { PATH: hardenedPath } : {}),
765
910
  CLAUDE_WORKSPACE: isAppAgent ? '/workspace' : this.agentConfig.workspace,
766
911
  TELEGRAM_BOT_TOKEN: this.agentConfig.telegram?.botToken ?? '',