skimpyclaw 0.3.14 → 0.4.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.
Files changed (222) hide show
  1. package/README.md +47 -37
  2. package/dist/__tests__/adapter-types.test.d.ts +4 -0
  3. package/dist/__tests__/adapter-types.test.js +63 -0
  4. package/dist/__tests__/anthropic-adapter.test.d.ts +4 -0
  5. package/dist/__tests__/anthropic-adapter.test.js +264 -0
  6. package/dist/__tests__/api.test.js +0 -1
  7. package/dist/__tests__/cli.integration.test.js +2 -4
  8. package/dist/__tests__/cli.test.js +0 -1
  9. package/dist/__tests__/code-agents-notifications.test.js +137 -0
  10. package/dist/__tests__/code-agents-parser.test.js +19 -1
  11. package/dist/__tests__/code-agents-preflight.test.js +3 -28
  12. package/dist/__tests__/code-agents-utils.test.js +34 -9
  13. package/dist/__tests__/code-agents-worktrees.test.js +116 -0
  14. package/dist/__tests__/codex-adapter.test.js +184 -0
  15. package/dist/__tests__/codex-auth.test.js +66 -0
  16. package/dist/__tests__/codex-provider-gating.test.js +35 -0
  17. package/dist/__tests__/codex-unified-loop.test.js +111 -0
  18. package/dist/__tests__/config-security.test.js +127 -0
  19. package/dist/__tests__/config.test.js +23 -0
  20. package/dist/__tests__/context-manager.test.js +243 -164
  21. package/dist/__tests__/cron-run.test.js +250 -0
  22. package/dist/__tests__/cron.test.js +12 -38
  23. package/dist/__tests__/digests.test.js +67 -0
  24. package/dist/__tests__/discord-attachments.test.js +211 -0
  25. package/dist/__tests__/discord-docs.test.d.ts +1 -0
  26. package/dist/__tests__/discord-docs.test.js +27 -0
  27. package/dist/__tests__/discord-thread-agents.test.d.ts +1 -0
  28. package/dist/__tests__/discord-thread-agents.test.js +115 -0
  29. package/dist/__tests__/discord-thread-context.test.d.ts +1 -0
  30. package/dist/__tests__/discord-thread-context.test.js +42 -0
  31. package/dist/__tests__/doctor.formatters.test.js +4 -4
  32. package/dist/__tests__/doctor.index.test.js +1 -1
  33. package/dist/__tests__/doctor.runner.test.js +3 -15
  34. package/dist/__tests__/env-sanitizer.test.d.ts +1 -0
  35. package/dist/__tests__/env-sanitizer.test.js +45 -0
  36. package/dist/__tests__/exec-approval.test.js +61 -0
  37. package/dist/__tests__/fetch-tool.test.d.ts +1 -0
  38. package/dist/__tests__/fetch-tool.test.js +85 -0
  39. package/dist/__tests__/gateway-status-auth.test.d.ts +1 -0
  40. package/dist/__tests__/gateway-status-auth.test.js +72 -0
  41. package/dist/__tests__/heartbeat.test.js +3 -3
  42. package/dist/__tests__/interactive-sessions.test.d.ts +1 -0
  43. package/dist/__tests__/interactive-sessions.test.js +96 -0
  44. package/dist/__tests__/langfuse.test.js +6 -18
  45. package/dist/__tests__/model-selection.test.js +3 -4
  46. package/dist/__tests__/providers-init.test.js +2 -8
  47. package/dist/__tests__/providers-routing.test.js +1 -1
  48. package/dist/__tests__/providers-utils.test.js +13 -3
  49. package/dist/__tests__/sessions.test.js +14 -10
  50. package/dist/__tests__/setup.test.js +12 -29
  51. package/dist/__tests__/skills.test.js +10 -7
  52. package/dist/__tests__/stream-formatter.test.d.ts +1 -0
  53. package/dist/__tests__/stream-formatter.test.js +114 -0
  54. package/dist/__tests__/token-efficiency.test.js +131 -15
  55. package/dist/__tests__/tool-loop.test.d.ts +4 -0
  56. package/dist/__tests__/tool-loop.test.js +505 -0
  57. package/dist/__tests__/tools.test.js +101 -276
  58. package/dist/__tests__/utils.test.d.ts +1 -0
  59. package/dist/__tests__/utils.test.js +14 -0
  60. package/dist/__tests__/voice.test.js +21 -0
  61. package/dist/agent.js +35 -4
  62. package/dist/api.js +113 -37
  63. package/dist/channels/discord/attachments.d.ts +50 -0
  64. package/dist/channels/discord/attachments.js +137 -0
  65. package/dist/channels/discord/delegation.d.ts +5 -0
  66. package/dist/channels/discord/delegation.js +136 -0
  67. package/dist/channels/discord/handlers.js +694 -7
  68. package/dist/channels/discord/index.d.ts +16 -1
  69. package/dist/channels/discord/index.js +64 -1
  70. package/dist/channels/discord/thread-agents.d.ts +54 -0
  71. package/dist/channels/discord/thread-agents.js +323 -0
  72. package/dist/channels/discord/threads.d.ts +58 -0
  73. package/dist/channels/discord/threads.js +192 -0
  74. package/dist/channels/discord/types.js +4 -2
  75. package/dist/channels/discord/utils.d.ts +16 -0
  76. package/dist/channels/discord/utils.js +86 -6
  77. package/dist/channels/telegram/index.d.ts +1 -1
  78. package/dist/channels/telegram/types.js +1 -1
  79. package/dist/channels/telegram/utils.js +9 -3
  80. package/dist/channels.d.ts +1 -1
  81. package/dist/cli.js +20 -400
  82. package/dist/code-agents/executor.d.ts +1 -1
  83. package/dist/code-agents/executor.js +101 -45
  84. package/dist/code-agents/index.d.ts +2 -7
  85. package/dist/code-agents/index.js +111 -80
  86. package/dist/code-agents/interactive-resume.d.ts +6 -0
  87. package/dist/code-agents/interactive-resume.js +98 -0
  88. package/dist/code-agents/interactive-sessions.d.ts +20 -0
  89. package/dist/code-agents/interactive-sessions.js +132 -0
  90. package/dist/code-agents/parser.js +5 -1
  91. package/dist/code-agents/registry.d.ts +7 -1
  92. package/dist/code-agents/registry.js +11 -23
  93. package/dist/code-agents/stream-formatter.d.ts +8 -0
  94. package/dist/code-agents/stream-formatter.js +92 -0
  95. package/dist/code-agents/types.d.ts +16 -24
  96. package/dist/code-agents/utils.d.ts +35 -11
  97. package/dist/code-agents/utils.js +349 -95
  98. package/dist/code-agents/worktrees.d.ts +37 -0
  99. package/dist/code-agents/worktrees.js +116 -0
  100. package/dist/config.d.ts +2 -4
  101. package/dist/config.js +123 -23
  102. package/dist/cron.d.ts +1 -6
  103. package/dist/cron.js +175 -82
  104. package/dist/dashboard/assets/index-B345aOO-.js +65 -0
  105. package/dist/dashboard/assets/index-ZWK4dalJ.css +1 -0
  106. package/dist/dashboard/index.html +2 -2
  107. package/dist/digests.d.ts +1 -0
  108. package/dist/digests.js +132 -42
  109. package/dist/doctor/checks.d.ts +0 -3
  110. package/dist/doctor/checks.js +1 -108
  111. package/dist/doctor/runner.js +1 -4
  112. package/dist/env-sanitizer.d.ts +2 -0
  113. package/dist/env-sanitizer.js +61 -0
  114. package/dist/exec-approval.d.ts +11 -1
  115. package/dist/exec-approval.js +17 -4
  116. package/dist/gateway.d.ts +3 -1
  117. package/dist/gateway.js +17 -7
  118. package/dist/heartbeat.js +1 -6
  119. package/dist/langfuse.js +3 -29
  120. package/dist/model-selection.js +3 -1
  121. package/dist/providers/adapter.d.ts +118 -0
  122. package/dist/providers/adapter.js +6 -0
  123. package/dist/providers/adapters/anthropic-adapter.d.ts +22 -0
  124. package/dist/providers/adapters/anthropic-adapter.js +204 -0
  125. package/dist/providers/adapters/codex-adapter.d.ts +26 -0
  126. package/dist/providers/adapters/codex-adapter.js +203 -0
  127. package/dist/providers/anthropic.d.ts +1 -0
  128. package/dist/providers/anthropic.js +10 -272
  129. package/dist/providers/codex.d.ts +21 -0
  130. package/dist/providers/codex.js +149 -330
  131. package/dist/providers/content.d.ts +1 -1
  132. package/dist/providers/content.js +2 -2
  133. package/dist/providers/context-manager.d.ts +18 -6
  134. package/dist/providers/context-manager.js +199 -223
  135. package/dist/providers/index.d.ts +9 -1
  136. package/dist/providers/index.js +73 -64
  137. package/dist/providers/loop-utils.d.ts +20 -0
  138. package/dist/providers/loop-utils.js +30 -0
  139. package/dist/providers/tool-loop.d.ts +12 -0
  140. package/dist/providers/tool-loop.js +251 -0
  141. package/dist/providers/utils.d.ts +19 -3
  142. package/dist/providers/utils.js +100 -29
  143. package/dist/secure-store.d.ts +8 -0
  144. package/dist/secure-store.js +80 -0
  145. package/dist/service.js +3 -28
  146. package/dist/sessions.d.ts +3 -0
  147. package/dist/sessions.js +147 -18
  148. package/dist/setup-templates.js +13 -25
  149. package/dist/setup.d.ts +10 -6
  150. package/dist/setup.js +84 -292
  151. package/dist/skills.js +3 -11
  152. package/dist/tools/agent-delegation.d.ts +19 -0
  153. package/dist/tools/agent-delegation.js +49 -0
  154. package/dist/tools/bash-tool.js +89 -34
  155. package/dist/tools/definitions.d.ts +199 -302
  156. package/dist/tools/definitions.js +70 -123
  157. package/dist/tools/execute-context.d.ts +13 -4
  158. package/dist/tools/fetch-tool.js +109 -13
  159. package/dist/tools/file-tools.js +7 -1
  160. package/dist/tools.d.ts +7 -7
  161. package/dist/tools.js +133 -151
  162. package/dist/types.d.ts +37 -30
  163. package/dist/utils.js +4 -6
  164. package/dist/voice.d.ts +1 -1
  165. package/dist/voice.js +17 -4
  166. package/package.json +33 -23
  167. package/templates/TOOLS.md +0 -27
  168. package/dist/__tests__/audit.test.js +0 -122
  169. package/dist/__tests__/code-agents-orchestrator.test.js +0 -216
  170. package/dist/__tests__/code-agents-sandbox.test.js +0 -163
  171. package/dist/__tests__/orchestrator.test.js +0 -425
  172. package/dist/__tests__/sandbox-bridge.test.js +0 -116
  173. package/dist/__tests__/sandbox-manager.test.js +0 -144
  174. package/dist/__tests__/sandbox-mount-security.test.js +0 -139
  175. package/dist/__tests__/sandbox-runtime.test.js +0 -176
  176. package/dist/__tests__/subagent.test.js +0 -240
  177. package/dist/__tests__/telegram.test.js +0 -42
  178. package/dist/code-agents/orchestrator.d.ts +0 -29
  179. package/dist/code-agents/orchestrator.js +0 -694
  180. package/dist/code-agents/worktree.d.ts +0 -40
  181. package/dist/code-agents/worktree.js +0 -215
  182. package/dist/dashboard/assets/index-BoTHPby4.js +0 -65
  183. package/dist/dashboard/assets/index-D4mufvBg.css +0 -1
  184. package/dist/dashboard.d.ts +0 -8
  185. package/dist/dashboard.js +0 -4071
  186. package/dist/discord.d.ts +0 -8
  187. package/dist/discord.js +0 -792
  188. package/dist/mcp-context-a8c.d.ts +0 -13
  189. package/dist/mcp-context-a8c.js +0 -34
  190. package/dist/orchestrator.d.ts +0 -15
  191. package/dist/orchestrator.js +0 -676
  192. package/dist/providers/openai.d.ts +0 -10
  193. package/dist/providers/openai.js +0 -355
  194. package/dist/sandbox/bridge.d.ts +0 -5
  195. package/dist/sandbox/bridge.js +0 -63
  196. package/dist/sandbox/index.d.ts +0 -5
  197. package/dist/sandbox/index.js +0 -4
  198. package/dist/sandbox/manager.d.ts +0 -7
  199. package/dist/sandbox/manager.js +0 -100
  200. package/dist/sandbox/mount-security.d.ts +0 -12
  201. package/dist/sandbox/mount-security.js +0 -122
  202. package/dist/sandbox/runtime.d.ts +0 -39
  203. package/dist/sandbox/runtime.js +0 -192
  204. package/dist/sandbox-utils.d.ts +0 -6
  205. package/dist/sandbox-utils.js +0 -36
  206. package/dist/subagent.d.ts +0 -19
  207. package/dist/subagent.js +0 -407
  208. package/dist/telegram.d.ts +0 -2
  209. package/dist/telegram.js +0 -11
  210. package/dist/tools/browser-tool.d.ts +0 -3
  211. package/dist/tools/browser-tool.js +0 -266
  212. package/sandbox/Dockerfile +0 -40
  213. /package/dist/__tests__/{audit.test.d.ts → code-agents-notifications.test.d.ts} +0 -0
  214. /package/dist/__tests__/{code-agents-orchestrator.test.d.ts → code-agents-worktrees.test.d.ts} +0 -0
  215. /package/dist/__tests__/{code-agents-sandbox.test.d.ts → codex-adapter.test.d.ts} +0 -0
  216. /package/dist/__tests__/{orchestrator.test.d.ts → codex-auth.test.d.ts} +0 -0
  217. /package/dist/__tests__/{sandbox-bridge.test.d.ts → codex-provider-gating.test.d.ts} +0 -0
  218. /package/dist/__tests__/{sandbox-manager.test.d.ts → codex-unified-loop.test.d.ts} +0 -0
  219. /package/dist/__tests__/{sandbox-mount-security.test.d.ts → config-security.test.d.ts} +0 -0
  220. /package/dist/__tests__/{sandbox-runtime.test.d.ts → cron-run.test.d.ts} +0 -0
  221. /package/dist/__tests__/{subagent.test.d.ts → digests.test.d.ts} +0 -0
  222. /package/dist/__tests__/{telegram.test.d.ts → discord-attachments.test.d.ts} +0 -0
@@ -0,0 +1,61 @@
1
+ /** Env var name patterns that should never be exposed to model-executed commands. */
2
+ const SENSITIVE_ENV_PATTERNS = [
3
+ /api.?key/i, /token/i, /secret/i, /password/i, /credential/i,
4
+ /^ANTHROPIC_/i, /^OPENAI_/i, /^CLAUDE/i, /^CODEX_/i, /^MINIMAX_/i,
5
+ /^KIMI_/i, /^TOGETHER_/i, /^GROQ_/i, /^OPENROUTER_/i,
6
+ ];
7
+ /** Env vars that match SENSITIVE_ENV_PATTERNS but should be kept (e.g. tool auth). */
8
+ const SENSITIVE_ENV_ALLOWLIST = new Set(['GH_TOKEN']);
9
+ /** Common tool paths that may be missing when launched as a service/daemon. */
10
+ const EXTRA_PATH_DIRS = ['/opt/homebrew/bin', '/opt/homebrew/sbin', '/usr/local/bin'];
11
+ const CRON_ENV_ALLOWLIST = new Set([
12
+ 'HOME',
13
+ 'USER',
14
+ 'LOGNAME',
15
+ 'SHELL',
16
+ 'PATH',
17
+ 'TMPDIR',
18
+ 'LANG',
19
+ 'LC_ALL',
20
+ 'LC_CTYPE',
21
+ 'TZ',
22
+ 'TERM',
23
+ 'PWD',
24
+ 'SHLVL',
25
+ 'GH_TOKEN',
26
+ ]);
27
+ export function sanitizeExecEnv() {
28
+ const env = { ...process.env };
29
+ for (const key of Object.keys(env)) {
30
+ if (!SENSITIVE_ENV_ALLOWLIST.has(key) && SENSITIVE_ENV_PATTERNS.some(p => p.test(key))) {
31
+ delete env[key];
32
+ }
33
+ }
34
+ const currentPath = env.PATH || '';
35
+ const missing = EXTRA_PATH_DIRS.filter(d => !currentPath.includes(d));
36
+ if (missing.length > 0) {
37
+ env.PATH = currentPath ? `${currentPath}:${missing.join(':')}` : missing.join(':');
38
+ }
39
+ return env;
40
+ }
41
+ export function sanitizeCronEnv() {
42
+ const env = {};
43
+ for (const [key, value] of Object.entries(process.env)) {
44
+ if (value === undefined)
45
+ continue;
46
+ if (CRON_ENV_ALLOWLIST.has(key) || key.startsWith('SKIMPYCLAW_')) {
47
+ env[key] = value;
48
+ }
49
+ }
50
+ for (const key of Object.keys(env)) {
51
+ if (!SENSITIVE_ENV_ALLOWLIST.has(key) && SENSITIVE_ENV_PATTERNS.some(p => p.test(key))) {
52
+ delete env[key];
53
+ }
54
+ }
55
+ const currentPath = env.PATH || '';
56
+ const missing = EXTRA_PATH_DIRS.filter(d => !currentPath.includes(d));
57
+ if (missing.length > 0) {
58
+ env.PATH = currentPath ? `${currentPath}:${missing.join(':')}` : missing.join(':');
59
+ }
60
+ return env;
61
+ }
@@ -17,7 +17,14 @@ export interface ExecApprovalConfig {
17
17
  ttlMs?: number;
18
18
  requireForTiers?: number[];
19
19
  }
20
- export type ApprovalStatus = 'pending' | 'approved' | 'denied' | 'expired';
20
+ export type ApprovalStatus = 'pending' | 'approved' | 'denied' | 'expired' | 'consumed';
21
+ /** A single status transition record (append-only). */
22
+ export interface ApprovalHistoryEntry {
23
+ from: ApprovalStatus;
24
+ to: ApprovalStatus;
25
+ at: Date;
26
+ by?: string;
27
+ }
21
28
  export interface PendingApproval {
22
29
  id: string;
23
30
  command: string;
@@ -32,6 +39,8 @@ export interface PendingApproval {
32
39
  resolvedAt?: Date;
33
40
  /** Channel context metadata — where the request originated */
34
41
  channelMeta?: ApprovalChannelMeta;
42
+ /** Append-only history of status transitions */
43
+ history: ApprovalHistoryEntry[];
35
44
  }
36
45
  /** Metadata about the channel/chat where an approval request originated */
37
46
  export interface ApprovalChannelMeta {
@@ -83,6 +92,7 @@ export declare function denyRequest(id: string, deniedBy?: string): boolean;
83
92
  export declare function findApprovedRequest(command: string, cwd?: string): PendingApproval | undefined;
84
93
  /**
85
94
  * Mark an approved request as consumed (after successful execution).
95
+ * Preserves the approval in the registry with status 'consumed' for history.
86
96
  */
87
97
  export declare function consumeApproval(id: string): void;
88
98
  /**
@@ -274,6 +274,7 @@ export function createApprovalRequest(command, cwd, classification, config, chan
274
274
  expiresAt: new Date(now.getTime() + ttlMs),
275
275
  status: 'pending',
276
276
  channelMeta,
277
+ history: [],
277
278
  };
278
279
  approvals.set(approval.id, approval);
279
280
  emitEvent('created', approval);
@@ -314,9 +315,11 @@ export function approveRequest(id, approvedBy) {
314
315
  const approval = approvals.get(id);
315
316
  if (!approval || approval.status !== 'pending')
316
317
  return false;
318
+ const now = new Date();
319
+ approval.history.push({ from: approval.status, to: 'approved', at: now, by: approvedBy });
317
320
  approval.status = 'approved';
318
321
  approval.approvedBy = approvedBy;
319
- approval.resolvedAt = new Date();
322
+ approval.resolvedAt = now;
320
323
  emitEvent('approved', approval);
321
324
  return true;
322
325
  }
@@ -328,9 +331,11 @@ export function denyRequest(id, deniedBy) {
328
331
  const approval = approvals.get(id);
329
332
  if (!approval || approval.status !== 'pending')
330
333
  return false;
334
+ const now = new Date();
335
+ approval.history.push({ from: approval.status, to: 'denied', at: now, by: deniedBy });
331
336
  approval.status = 'denied';
332
337
  approval.deniedBy = deniedBy;
333
- approval.resolvedAt = new Date();
338
+ approval.resolvedAt = now;
334
339
  emitEvent('denied', approval);
335
340
  return true;
336
341
  }
@@ -351,9 +356,14 @@ export function findApprovedRequest(command, cwd) {
351
356
  }
352
357
  /**
353
358
  * Mark an approved request as consumed (after successful execution).
359
+ * Preserves the approval in the registry with status 'consumed' for history.
354
360
  */
355
361
  export function consumeApproval(id) {
356
- approvals.delete(id);
362
+ const approval = approvals.get(id);
363
+ if (!approval)
364
+ return;
365
+ approval.history.push({ from: approval.status, to: 'consumed', at: new Date() });
366
+ approval.status = 'consumed';
357
367
  }
358
368
  /**
359
369
  * Expire pending approvals past their TTL.
@@ -362,8 +372,10 @@ export function cleanupExpired() {
362
372
  const now = Date.now();
363
373
  for (const [id, approval] of approvals) {
364
374
  if (approval.status === 'pending' && approval.expiresAt.getTime() <= now) {
375
+ const resolvedAt = new Date(now);
376
+ approval.history.push({ from: 'pending', to: 'expired', at: resolvedAt });
365
377
  approval.status = 'expired';
366
- approval.resolvedAt = new Date(now);
378
+ approval.resolvedAt = resolvedAt;
367
379
  emitEvent('expired', approval);
368
380
  }
369
381
  }
@@ -413,6 +425,7 @@ export function waitForApproval(id, timeoutMs) {
413
425
  createdAt: new Date(),
414
426
  expiresAt: new Date(),
415
427
  status: 'expired',
428
+ history: [],
416
429
  });
417
430
  }
418
431
  }, timeoutMs);
package/dist/gateway.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { FastifyInstance } from 'fastify';
2
- import type { Config } from './types.js';
2
+ import type { Config, ThinkingLevel } from './types.js';
3
3
  export declare function setGatewayConfig(cfg: Config): void;
4
4
  export declare function createGateway(cfg: Config): Promise<FastifyInstance>;
5
5
  export declare function getCurrentModel(): string;
6
6
  export declare function setCurrentModel(model: string): void;
7
+ export declare function getCurrentThinking(): ThinkingLevel | undefined;
8
+ export declare function setCurrentThinking(thinking: ThinkingLevel | undefined): void;
7
9
  export declare function getLastMessage(): Date | undefined;
8
10
  export declare function setLastMessage(date: Date): void;
package/dist/gateway.js CHANGED
@@ -21,13 +21,16 @@ let config;
21
21
  let startTime;
22
22
  let lastMessage;
23
23
  let currentModel;
24
+ let currentThinking;
24
25
  export function setGatewayConfig(cfg) {
25
26
  config = cfg;
26
27
  }
27
28
  export async function createGateway(cfg) {
28
29
  config = cfg;
29
30
  startTime = new Date();
30
- currentModel = cfg.agents.list[cfg.agents.default]?.model || 'claude-sonnet-4-5';
31
+ const defaultAgent = cfg.agents.list[cfg.agents.default];
32
+ currentModel = defaultAgent?.model || 'claude-sonnet-4-5';
33
+ currentThinking = defaultAgent?.thinking;
31
34
  const fastify = Fastify({
32
35
  logger: {
33
36
  level: 'info',
@@ -54,6 +57,7 @@ export async function createGateway(cfg) {
54
57
  uptime: Date.now() - startTime.getTime(),
55
58
  agent: config.agents.default,
56
59
  model: currentModel,
60
+ thinking: currentThinking,
57
61
  lastMessage,
58
62
  cronJobs: jobs.map(j => ({
59
63
  id: j.id,
@@ -71,7 +75,7 @@ export async function createGateway(cfg) {
71
75
  try {
72
76
  const response = await runAgentTurn(config.agents.default, message, config, model || currentModel, undefined, undefined, {
73
77
  channel: 'gateway',
74
- metadata: { ip: request.ip },
78
+ metadata: { ip: request.ip, thinkingOverride: currentThinking },
75
79
  });
76
80
  lastMessage = new Date();
77
81
  return { response };
@@ -109,15 +113,15 @@ export async function createGateway(cfg) {
109
113
  // Ensure dashboard token exists
110
114
  const dashboardToken = ensureDashboardToken(config);
111
115
  console.log(`[dashboard] URL: http://localhost:${config.gateway.port}/dashboard`);
112
- // Auth guard for gateway write endpoints (same token as dashboard)
116
+ // Auth guard for sensitive gateway endpoints (same token as dashboard).
113
117
  const PROTECTED_ROUTES = new Set(['/message', '/model', '/reload']);
114
118
  fastify.addHook('onRequest', async (request, reply) => {
115
119
  const url = request.url;
116
- // Protect write endpoints + cron trigger
117
- if (!PROTECTED_ROUTES.has(url) && !url.startsWith('/cron/'))
120
+ const isProtected = PROTECTED_ROUTES.has(url) ||
121
+ url.startsWith('/cron/') ||
122
+ url === '/status';
123
+ if (!isProtected)
118
124
  return;
119
- if (request.method === 'GET')
120
- return; // GET /health, GET /status are fine
121
125
  if (!dashboardToken)
122
126
  return; // No token configured, allow access
123
127
  if (!validateBearerToken(dashboardToken, request.headers.authorization)) {
@@ -140,6 +144,12 @@ export function getCurrentModel() {
140
144
  export function setCurrentModel(model) {
141
145
  currentModel = model;
142
146
  }
147
+ export function getCurrentThinking() {
148
+ return currentThinking;
149
+ }
150
+ export function setCurrentThinking(thinking) {
151
+ currentThinking = thinking;
152
+ }
143
153
  export function getLastMessage() {
144
154
  return lastMessage;
145
155
  }
package/dist/heartbeat.js CHANGED
@@ -7,7 +7,6 @@ import { join } from 'path';
7
7
  import { homedir } from 'os';
8
8
  import { runAgentTurn } from './agent.js';
9
9
  import { resolveAllowedPaths } from './config.js';
10
- import { pruneIdle, SANDBOX_DEFAULTS } from './sandbox/index.js';
11
10
  import { getActiveChannelId, isActiveChannelSilenced, sendActiveChannelProactiveMessage, } from './channels.js';
12
11
  let heartbeatTimer = null;
13
12
  let running = false;
@@ -15,9 +14,7 @@ function getHeartbeatTools(config) {
15
14
  if (config.heartbeat.tools) {
16
15
  return {
17
16
  ...config.heartbeat.tools,
18
- allowedPaths: config.heartbeat.tools.allowedPaths?.length
19
- ? config.heartbeat.tools.allowedPaths
20
- : resolveAllowedPaths(config),
17
+ allowedPaths: resolveAllowedPaths(config, config.heartbeat.tools.allowedPaths),
21
18
  };
22
19
  }
23
20
  return {
@@ -75,8 +72,6 @@ export function stopHeartbeat() {
75
72
  }
76
73
  }
77
74
  export async function runHeartbeatCheck(config) {
78
- // Prune idle sandbox containers
79
- pruneIdle(config.sandbox?.idleTimeoutMs ?? SANDBOX_DEFAULTS.idleTimeoutMs ?? 3_600_000).catch(() => { });
80
75
  if (running) {
81
76
  console.log('[heartbeat] Skipping — previous check still running');
82
77
  return 'Skipped — previous check still running';
package/dist/langfuse.js CHANGED
@@ -13,28 +13,14 @@ export const MODEL_PRICING = {
13
13
  'claude-haiku-4-5': { inputPerMTok: 0.25, outputPerMTok: 1.25 },
14
14
  'claude-haiku-4': { inputPerMTok: 1.0, outputPerMTok: 5.0 },
15
15
  'claude-opus-4': { inputPerMTok: 15.0, outputPerMTok: 75.0 },
16
- // OpenAI models (https://developers.openai.com/api/docs/pricing)
17
- 'gpt-4o': { inputPerMTok: 2.5, outputPerMTok: 10.0 },
18
- 'gpt-4o-mini': { inputPerMTok: 0.15, outputPerMTok: 0.6 },
19
- 'gpt-4.1': { inputPerMTok: 2.0, outputPerMTok: 8.0 },
20
- 'gpt-4.1-mini': { inputPerMTok: 0.4, outputPerMTok: 1.6 },
21
- 'gpt-4.1-nano': { inputPerMTok: 0.1, outputPerMTok: 0.4 },
22
- 'gpt-4-turbo': { inputPerMTok: 10.0, outputPerMTok: 30.0 },
23
- 'gpt-3.5-turbo': { inputPerMTok: 0.5, outputPerMTok: 1.5 },
24
16
  // Codex pricing
25
17
  'gpt-5.1-codex': { inputPerMTok: 1.25, outputPerMTok: 10.0 },
26
18
  'gpt-5.2-codex': { inputPerMTok: 1.75, outputPerMTok: 14.0 },
27
19
  'gpt-5.3-codex': { inputPerMTok: 1.75, outputPerMTok: 14.0 },
20
+ 'gpt-5.5': { inputPerMTok: 5.0, outputPerMTok: 30.0 },
28
21
  'codex-5.1': { inputPerMTok: 1.25, outputPerMTok: 10.0 },
29
22
  'codex-5.2': { inputPerMTok: 1.75, outputPerMTok: 14.0 },
30
23
  'codex-5.3': { inputPerMTok: 1.75, outputPerMTok: 14.0 },
31
- // MiniMax models (https://platform.minimax.io/docs/pricing/pay-as-you-go)
32
- 'minimax-m2.1': { inputPerMTok: 0.3, outputPerMTok: 1.2 },
33
- 'minimax-m2.5': { inputPerMTok: 0.3, outputPerMTok: 1.2 },
34
- // Kimi/Moonshot models (https://platform.moonshot.ai/docs/pricing/chat.en-US)
35
- // kimi-k2.5 series - using cache miss pricing for input (non-cached)
36
- 'kimi-k2.5': { inputPerMTok: 0.6, outputPerMTok: 3.0 },
37
- 'kimi-for-coding': { inputPerMTok: 0.6, outputPerMTok: 3.0 }
38
24
  };
39
25
  /** Map of common aliases/shorthand to canonical model IDs in MODEL_PRICING */
40
26
  const MODEL_ALIAS_MAP = {
@@ -47,24 +33,12 @@ const MODEL_ALIAS_MAP = {
47
33
  'claude-opus': 'claude-opus-4',
48
34
  'claude-3.5-sonnet': 'claude-3-5-sonnet',
49
35
  'claude-3-opus': 'claude-3-opus',
50
- // OpenAI aliases
51
- gpt4o: 'gpt-4o',
52
- 'gpt4o-mini': 'gpt-4o-mini',
53
- 'gpt4.1': 'gpt-4.1',
54
- 'gpt4.1-mini': 'gpt-4.1-mini',
55
- 'gpt4.1-nano': 'gpt-4.1-nano',
56
- 'gpt4-turbo': 'gpt-4-turbo',
57
- 'gpt35-turbo': 'gpt-3.5-turbo',
58
- 'gpt-3.5': 'gpt-3.5-turbo',
59
36
  'gpt-codex': 'gpt-5.3-codex',
60
37
  'codex5.1': 'gpt-5.1-codex',
61
38
  'codex5.2': 'gpt-5.2-codex',
62
39
  'codex5.3': 'gpt-5.3-codex',
63
- codex: 'gpt-5.3-codex',
64
- // MiniMax aliases
65
- minimax: 'minimax-m2.5',
66
- // Kimi/Moonshot aliases
67
- kimi: 'kimi-k2.5'
40
+ 'codex5.5': 'gpt-5.5',
41
+ codex: 'gpt-5.5',
68
42
  };
69
43
  /**
70
44
  * Calculate the USD cost of a model invocation based on token usage.
@@ -34,7 +34,9 @@ export function resolveModelSelection(input, config) {
34
34
  if (FULL_MODEL_SPEC_RE.test(value)) {
35
35
  return { ok: true, resolved: resolveModel(value, config) };
36
36
  }
37
- if (BARE_MODEL_ID_RE.test(value) && /[-.]/.test(value)) {
37
+ // Accept bare model IDs only if they contain a version-like pattern (digits after a hyphen/dot)
38
+ // e.g. "claude-sonnet-4-5", "gpt-5.3-codex" — but NOT "claude-opuis"
39
+ if (BARE_MODEL_ID_RE.test(value) && /[-.]/.test(value) && /\d/.test(value)) {
38
40
  return { ok: true, resolved: resolveModel(value, config) };
39
41
  }
40
42
  if (!SAFE_MODEL_INPUT_RE.test(value) || value.includes('/')) {
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Provider adapter interface for the unified tool loop.
3
+ * Each provider implements this to handle API-specific concerns
4
+ * while sharing the same agentic loop orchestration logic.
5
+ */
6
+ import type { ChatMessage, ChatOptions, Config } from '../types.js';
7
+ import type { ContextManagementConfig } from './context-manager.js';
8
+ import type { ExecuteToolContext } from '../tools.js';
9
+ /** Normalized representation of a model response within the tool loop. */
10
+ export interface NormalizedResponse {
11
+ /** Whether the model wants to call tools (vs. returning a final answer) */
12
+ hasToolCalls: boolean;
13
+ /** Tool calls extracted from the response */
14
+ toolCalls: NormalizedToolCall[];
15
+ /** Text content from the response (final answer when hasToolCalls=false) */
16
+ textContent: string;
17
+ /** Raw usage data from the provider */
18
+ usage?: {
19
+ inputTokens: number;
20
+ outputTokens: number;
21
+ cacheReadTokens?: number;
22
+ cacheCreationTokens?: number;
23
+ };
24
+ /** Cost details for this response */
25
+ cost?: {
26
+ input: number;
27
+ output: number;
28
+ total: number;
29
+ };
30
+ /** Raw response object (provider-specific, for appending to message history) */
31
+ rawResponse: unknown;
32
+ }
33
+ export interface NormalizedToolCall {
34
+ /** Unique ID for this tool call (tool_use_id, call_id, toolCall.id) */
35
+ id: string;
36
+ /** Tool name */
37
+ name: string;
38
+ /** Parsed arguments */
39
+ args: Record<string, any>;
40
+ /** Raw arguments string (for logging) */
41
+ rawArgs: string;
42
+ }
43
+ export interface CompactionResult<T> {
44
+ /** Updated messages array */
45
+ messages: T[];
46
+ /** Whether compaction actually occurred */
47
+ compacted: boolean;
48
+ /** Compaction method used (if any) */
49
+ method?: 'llm' | 'truncation';
50
+ }
51
+ /**
52
+ * Provider-specific message container.
53
+ * Each adapter defines its own internal message format.
54
+ */
55
+ export interface ProviderMessages {
56
+ /** The mutable message array (format depends on provider) */
57
+ messages: any[];
58
+ /** System prompt / instructions (extracted once, reused per call) */
59
+ systemParam?: any;
60
+ }
61
+ /**
62
+ * Message format helper for generic compaction.
63
+ * Each provider implements this to teach the compaction engine
64
+ * how to inspect/truncate/serialize its message format.
65
+ */
66
+ export interface MessageFormatHelper<T> {
67
+ /** Check if an item is a tool result (to be truncated during fallback compaction). */
68
+ isToolResult(item: T): boolean;
69
+ /** Truncate a tool result item's content to maxChars. Returns a new item (no mutation). */
70
+ truncateToolResult(item: T, maxChars: number): T;
71
+ /** Serialize a list of items into a human-readable transcript for LLM summarization. */
72
+ serialize(items: T[]): string;
73
+ /** Build a summary message that replaces compacted head messages. */
74
+ buildSummaryMessage(summary: string): T;
75
+ }
76
+ /**
77
+ * Adapter interface that each provider implements.
78
+ * Separates provider-specific API details from shared tool loop logic.
79
+ */
80
+ export interface ProviderAdapter {
81
+ readonly name: string;
82
+ /** Check whether this provider is initialized and ready to handle requests. */
83
+ isAvailable(): boolean;
84
+ /** Simple chat (no tool loop). Returns the model's text response. */
85
+ chat(messages: ChatMessage[], options: ChatOptions, config: Config): Promise<string>;
86
+ /** Provider-specific tool discovery options (e.g. disable MCP for non-Anthropic providers). */
87
+ getToolDefinitionOptions?(toolContext?: ExecuteToolContext, config?: Config): {
88
+ includeMcp?: boolean;
89
+ };
90
+ /** Build the initial API messages from ChatMessage[] (strip system, format content) */
91
+ buildMessages(messages: ChatMessage[], options: ChatOptions, config: Config): ProviderMessages;
92
+ /** Build tool definitions in provider-native format */
93
+ buildToolDefs(toolDefs: any[], config: Config): any[];
94
+ /** Make one API call with tools. Returns normalized response. */
95
+ call(messages: ProviderMessages, toolDefs: any[], options: ChatOptions, config: Config): Promise<NormalizedResponse>;
96
+ /** Append the assistant's raw response to the message history */
97
+ appendAssistantResponse(messages: ProviderMessages, rawResponse: unknown): void;
98
+ /** Append a single tool result to the message history.
99
+ * For providers that batch tool results (e.g. Anthropic), use appendToolResults instead. */
100
+ appendToolResult(messages: ProviderMessages, toolCallId: string, result: string, isError?: boolean): void;
101
+ /** Append multiple tool results as a single message (for providers that batch).
102
+ * Default implementation calls appendToolResult for each. */
103
+ appendToolResults?(messages: ProviderMessages, results: {
104
+ toolCallId: string;
105
+ result: string;
106
+ isError?: boolean;
107
+ }[]): void;
108
+ /**
109
+ * Optional hook called when the model's final response has no text but tool calls were made.
110
+ * Allows providers (e.g. Codex) to make an additional API call to elicit a text summary.
111
+ * Returns the finalized text, or undefined to use the default fallback.
112
+ */
113
+ onEmptyFinalResponse?(providerMessages: ProviderMessages, toolDefs: any[], options: ChatOptions, config: Config): Promise<string | undefined>;
114
+ /** Compact messages when context grows too large */
115
+ compactMessages(messages: ProviderMessages, config: ContextManagementConfig | undefined, iteration: number, fullConfig?: Config): Promise<CompactionResult<any>>;
116
+ /** Record usage/cost to the usage tracking system */
117
+ recordUsage(model: string, usage: unknown, trigger?: string, agentId?: string): void;
118
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Provider adapter interface for the unified tool loop.
3
+ * Each provider implements this to handle API-specific concerns
4
+ * while sharing the same agentic loop orchestration logic.
5
+ */
6
+ export {};
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Anthropic provider adapter for the unified tool loop.
3
+ */
4
+ import type { ChatMessage, ChatOptions, Config } from '../../types.js';
5
+ import type { ProviderAdapter, ProviderMessages, NormalizedResponse, CompactionResult } from '../adapter.js';
6
+ export declare class AnthropicAdapter implements ProviderAdapter {
7
+ readonly name = "anthropic";
8
+ isAvailable(): boolean;
9
+ chat(messages: ChatMessage[], options: ChatOptions, config: Config): Promise<string>;
10
+ buildMessages(messages: ChatMessage[], options: ChatOptions, config: Config): ProviderMessages;
11
+ buildToolDefs(toolDefs: any[], config: Config): any[];
12
+ call(providerMessages: ProviderMessages, toolDefs: any[], options: ChatOptions, config: Config): Promise<NormalizedResponse>;
13
+ appendAssistantResponse(providerMessages: ProviderMessages, rawResponse: unknown): void;
14
+ appendToolResult(providerMessages: ProviderMessages, toolCallId: string, result: string, isError?: boolean): void;
15
+ appendToolResults(providerMessages: ProviderMessages, results: {
16
+ toolCallId: string;
17
+ result: string;
18
+ isError?: boolean;
19
+ }[]): void;
20
+ compactMessages(providerMessages: ProviderMessages, config: any, iteration: number, fullConfig?: Config): Promise<CompactionResult<any>>;
21
+ recordUsage(model: string, usage: unknown, trigger?: string, agentId?: string): void;
22
+ }