seclaw-agent 0.1.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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +668 -0
  3. package/SECURITY.md +253 -0
  4. package/assets/logo.png +0 -0
  5. package/dist/agent/context.d.ts +37 -0
  6. package/dist/agent/context.d.ts.map +1 -0
  7. package/dist/agent/context.js +211 -0
  8. package/dist/agent/context.js.map +1 -0
  9. package/dist/agent/docker_sandbox.d.ts +41 -0
  10. package/dist/agent/docker_sandbox.d.ts.map +1 -0
  11. package/dist/agent/docker_sandbox.js +239 -0
  12. package/dist/agent/docker_sandbox.js.map +1 -0
  13. package/dist/agent/loop.d.ts +86 -0
  14. package/dist/agent/loop.d.ts.map +1 -0
  15. package/dist/agent/loop.js +858 -0
  16. package/dist/agent/loop.js.map +1 -0
  17. package/dist/agent/memory.d.ts +21 -0
  18. package/dist/agent/memory.d.ts.map +1 -0
  19. package/dist/agent/memory.js +128 -0
  20. package/dist/agent/memory.js.map +1 -0
  21. package/dist/agent/security/execution_audit.d.ts +17 -0
  22. package/dist/agent/security/execution_audit.d.ts.map +1 -0
  23. package/dist/agent/security/execution_audit.js +126 -0
  24. package/dist/agent/security/execution_audit.js.map +1 -0
  25. package/dist/agent/security/input_validation/entity.d.ts +57 -0
  26. package/dist/agent/security/input_validation/entity.d.ts.map +1 -0
  27. package/dist/agent/security/input_validation/entity.js +121 -0
  28. package/dist/agent/security/input_validation/entity.js.map +1 -0
  29. package/dist/agent/security/input_validation/index.d.ts +114 -0
  30. package/dist/agent/security/input_validation/index.d.ts.map +1 -0
  31. package/dist/agent/security/input_validation/index.js +971 -0
  32. package/dist/agent/security/input_validation/index.js.map +1 -0
  33. package/dist/agent/security/input_validation/lattice.d.ts +33 -0
  34. package/dist/agent/security/input_validation/lattice.d.ts.map +1 -0
  35. package/dist/agent/security/input_validation/lattice.js +61 -0
  36. package/dist/agent/security/input_validation/lattice.js.map +1 -0
  37. package/dist/agent/security/input_validation/program_graph.d.ts +51 -0
  38. package/dist/agent/security/input_validation/program_graph.d.ts.map +1 -0
  39. package/dist/agent/security/input_validation/program_graph.js +285 -0
  40. package/dist/agent/security/input_validation/program_graph.js.map +1 -0
  41. package/dist/agent/security/input_validation/security_policy.d.ts +29 -0
  42. package/dist/agent/security/input_validation/security_policy.d.ts.map +1 -0
  43. package/dist/agent/security/input_validation/security_policy.js +256 -0
  44. package/dist/agent/security/input_validation/security_policy.js.map +1 -0
  45. package/dist/agent/security/memory_audit.d.ts +14 -0
  46. package/dist/agent/security/memory_audit.d.ts.map +1 -0
  47. package/dist/agent/security/memory_audit.js +126 -0
  48. package/dist/agent/security/memory_audit.js.map +1 -0
  49. package/dist/agent/security/skill_audit.d.ts +15 -0
  50. package/dist/agent/security/skill_audit.d.ts.map +1 -0
  51. package/dist/agent/security/skill_audit.js +112 -0
  52. package/dist/agent/security/skill_audit.js.map +1 -0
  53. package/dist/agent/security/snapshot_and_rollback/base.d.ts +10 -0
  54. package/dist/agent/security/snapshot_and_rollback/base.d.ts.map +1 -0
  55. package/dist/agent/security/snapshot_and_rollback/base.js +10 -0
  56. package/dist/agent/security/snapshot_and_rollback/base.js.map +1 -0
  57. package/dist/agent/security/snapshot_and_rollback/docker_snapshot.d.ts +52 -0
  58. package/dist/agent/security/snapshot_and_rollback/docker_snapshot.d.ts.map +1 -0
  59. package/dist/agent/security/snapshot_and_rollback/docker_snapshot.js +358 -0
  60. package/dist/agent/security/snapshot_and_rollback/docker_snapshot.js.map +1 -0
  61. package/dist/agent/security/snapshot_and_rollback/index.d.ts +7 -0
  62. package/dist/agent/security/snapshot_and_rollback/index.d.ts.map +1 -0
  63. package/dist/agent/security/snapshot_and_rollback/index.js +450 -0
  64. package/dist/agent/security/snapshot_and_rollback/index.js.map +1 -0
  65. package/dist/agent/skills.d.ts +35 -0
  66. package/dist/agent/skills.d.ts.map +1 -0
  67. package/dist/agent/skills.js +235 -0
  68. package/dist/agent/skills.js.map +1 -0
  69. package/dist/agent/subagent.d.ts +39 -0
  70. package/dist/agent/subagent.d.ts.map +1 -0
  71. package/dist/agent/subagent.js +151 -0
  72. package/dist/agent/subagent.js.map +1 -0
  73. package/dist/agent/tools/base.d.ts +32 -0
  74. package/dist/agent/tools/base.d.ts.map +1 -0
  75. package/dist/agent/tools/base.js +91 -0
  76. package/dist/agent/tools/base.js.map +1 -0
  77. package/dist/agent/tools/cron.d.ts +46 -0
  78. package/dist/agent/tools/cron.d.ts.map +1 -0
  79. package/dist/agent/tools/cron.js +95 -0
  80. package/dist/agent/tools/cron.js.map +1 -0
  81. package/dist/agent/tools/filesystem.d.ts +102 -0
  82. package/dist/agent/tools/filesystem.d.ts.map +1 -0
  83. package/dist/agent/tools/filesystem.js +257 -0
  84. package/dist/agent/tools/filesystem.js.map +1 -0
  85. package/dist/agent/tools/message.d.ts +40 -0
  86. package/dist/agent/tools/message.d.ts.map +1 -0
  87. package/dist/agent/tools/message.js +55 -0
  88. package/dist/agent/tools/message.js.map +1 -0
  89. package/dist/agent/tools/registry.d.ts +16 -0
  90. package/dist/agent/tools/registry.d.ts.map +1 -0
  91. package/dist/agent/tools/registry.js +47 -0
  92. package/dist/agent/tools/registry.js.map +1 -0
  93. package/dist/agent/tools/shell.d.ts +40 -0
  94. package/dist/agent/tools/shell.d.ts.map +1 -0
  95. package/dist/agent/tools/shell.js +166 -0
  96. package/dist/agent/tools/shell.js.map +1 -0
  97. package/dist/agent/tools/spawn.d.ts +30 -0
  98. package/dist/agent/tools/spawn.d.ts.map +1 -0
  99. package/dist/agent/tools/spawn.js +50 -0
  100. package/dist/agent/tools/spawn.js.map +1 -0
  101. package/dist/agent/tools/web.d.ts +59 -0
  102. package/dist/agent/tools/web.d.ts.map +1 -0
  103. package/dist/agent/tools/web.js +167 -0
  104. package/dist/agent/tools/web.js.map +1 -0
  105. package/dist/bus/events.d.ts +31 -0
  106. package/dist/bus/events.d.ts.map +1 -0
  107. package/dist/bus/events.js +28 -0
  108. package/dist/bus/events.js.map +1 -0
  109. package/dist/bus/queue.d.ts +32 -0
  110. package/dist/bus/queue.d.ts.map +1 -0
  111. package/dist/bus/queue.js +104 -0
  112. package/dist/bus/queue.js.map +1 -0
  113. package/dist/channels/base.d.ts +25 -0
  114. package/dist/channels/base.d.ts.map +1 -0
  115. package/dist/channels/base.js +54 -0
  116. package/dist/channels/base.js.map +1 -0
  117. package/dist/channels/dingtalk.d.ts +31 -0
  118. package/dist/channels/dingtalk.d.ts.map +1 -0
  119. package/dist/channels/dingtalk.js +177 -0
  120. package/dist/channels/dingtalk.js.map +1 -0
  121. package/dist/channels/discord.d.ts +30 -0
  122. package/dist/channels/discord.d.ts.map +1 -0
  123. package/dist/channels/discord.js +197 -0
  124. package/dist/channels/discord.js.map +1 -0
  125. package/dist/channels/email.d.ts +41 -0
  126. package/dist/channels/email.d.ts.map +1 -0
  127. package/dist/channels/email.js +210 -0
  128. package/dist/channels/email.js.map +1 -0
  129. package/dist/channels/feishu.d.ts +32 -0
  130. package/dist/channels/feishu.d.ts.map +1 -0
  131. package/dist/channels/feishu.js +109 -0
  132. package/dist/channels/feishu.js.map +1 -0
  133. package/dist/channels/manager.d.ts +24 -0
  134. package/dist/channels/manager.d.ts.map +1 -0
  135. package/dist/channels/manager.js +205 -0
  136. package/dist/channels/manager.js.map +1 -0
  137. package/dist/channels/mochat.d.ts +38 -0
  138. package/dist/channels/mochat.d.ts.map +1 -0
  139. package/dist/channels/mochat.js +201 -0
  140. package/dist/channels/mochat.js.map +1 -0
  141. package/dist/channels/qq.d.ts +40 -0
  142. package/dist/channels/qq.d.ts.map +1 -0
  143. package/dist/channels/qq.js +280 -0
  144. package/dist/channels/qq.js.map +1 -0
  145. package/dist/channels/slack.d.ts +27 -0
  146. package/dist/channels/slack.d.ts.map +1 -0
  147. package/dist/channels/slack.js +118 -0
  148. package/dist/channels/slack.js.map +1 -0
  149. package/dist/channels/telegram.d.ts +31 -0
  150. package/dist/channels/telegram.d.ts.map +1 -0
  151. package/dist/channels/telegram.js +218 -0
  152. package/dist/channels/telegram.js.map +1 -0
  153. package/dist/channels/whatsapp.d.ts +29 -0
  154. package/dist/channels/whatsapp.d.ts.map +1 -0
  155. package/dist/channels/whatsapp.js +117 -0
  156. package/dist/channels/whatsapp.js.map +1 -0
  157. package/dist/cli/commands.d.ts +8 -0
  158. package/dist/cli/commands.d.ts.map +1 -0
  159. package/dist/cli/commands.js +537 -0
  160. package/dist/cli/commands.js.map +1 -0
  161. package/dist/config/loader.d.ts +24 -0
  162. package/dist/config/loader.d.ts.map +1 -0
  163. package/dist/config/loader.js +182 -0
  164. package/dist/config/loader.js.map +1 -0
  165. package/dist/config/schema.d.ts +2921 -0
  166. package/dist/config/schema.d.ts.map +1 -0
  167. package/dist/config/schema.js +257 -0
  168. package/dist/config/schema.js.map +1 -0
  169. package/dist/cron/service.d.ts +38 -0
  170. package/dist/cron/service.d.ts.map +1 -0
  171. package/dist/cron/service.js +336 -0
  172. package/dist/cron/service.js.map +1 -0
  173. package/dist/cron/types.d.ts +46 -0
  174. package/dist/cron/types.d.ts.map +1 -0
  175. package/dist/cron/types.js +6 -0
  176. package/dist/cron/types.js.map +1 -0
  177. package/dist/heartbeat/service.d.ts +26 -0
  178. package/dist/heartbeat/service.d.ts.map +1 -0
  179. package/dist/heartbeat/service.js +142 -0
  180. package/dist/heartbeat/service.js.map +1 -0
  181. package/dist/index.d.ts +7 -0
  182. package/dist/index.d.ts.map +1 -0
  183. package/dist/index.js +14 -0
  184. package/dist/index.js.map +1 -0
  185. package/dist/providers/base.d.ts +38 -0
  186. package/dist/providers/base.d.ts.map +1 -0
  187. package/dist/providers/base.js +21 -0
  188. package/dist/providers/base.js.map +1 -0
  189. package/dist/providers/litellm_provider.d.ts +35 -0
  190. package/dist/providers/litellm_provider.d.ts.map +1 -0
  191. package/dist/providers/litellm_provider.js +205 -0
  192. package/dist/providers/litellm_provider.js.map +1 -0
  193. package/dist/providers/registry.d.ts +44 -0
  194. package/dist/providers/registry.d.ts.map +1 -0
  195. package/dist/providers/registry.js +252 -0
  196. package/dist/providers/registry.js.map +1 -0
  197. package/dist/providers/transcription.d.ts +10 -0
  198. package/dist/providers/transcription.d.ts.map +1 -0
  199. package/dist/providers/transcription.js +83 -0
  200. package/dist/providers/transcription.js.map +1 -0
  201. package/dist/session/manager.d.ts +35 -0
  202. package/dist/session/manager.d.ts.map +1 -0
  203. package/dist/session/manager.js +193 -0
  204. package/dist/session/manager.js.map +1 -0
  205. package/dist/utils/helpers.d.ts +15 -0
  206. package/dist/utils/helpers.d.ts.map +1 -0
  207. package/dist/utils/helpers.js +100 -0
  208. package/dist/utils/helpers.js.map +1 -0
  209. package/dist/utils/logger.d.ts +7 -0
  210. package/dist/utils/logger.d.ts.map +1 -0
  211. package/dist/utils/logger.js +25 -0
  212. package/dist/utils/logger.js.map +1 -0
  213. package/package.json +58 -0
  214. package/templates/AGENTS.md +51 -0
  215. package/templates/HEARTBEAT.md +16 -0
  216. package/templates/SOUL.md +36 -0
  217. package/templates/TOOLS.md +150 -0
  218. package/templates/USER.md +17 -0
  219. package/templates/memory/MEMORY.md +23 -0
@@ -0,0 +1,858 @@
1
+ "use strict";
2
+ /**
3
+ * Agent loop - TypeScript port of seclaw/agent/loop.py
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ var __importDefault = (this && this.__importDefault) || function (mod) {
39
+ return (mod && mod.__esModule) ? mod : { "default": mod };
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.AgentLoop = void 0;
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
45
+ const logger_1 = __importDefault(require("../utils/logger"));
46
+ const events_1 = require("../bus/events");
47
+ const context_1 = require("./context");
48
+ const registry_1 = require("./tools/registry");
49
+ const filesystem_1 = require("./tools/filesystem");
50
+ const shell_1 = require("./tools/shell");
51
+ const web_1 = require("./tools/web");
52
+ const message_1 = require("./tools/message");
53
+ const spawn_1 = require("./tools/spawn");
54
+ const cron_1 = require("./tools/cron");
55
+ const memory_1 = require("./memory");
56
+ const subagent_1 = require("./subagent");
57
+ const manager_1 = require("../session/manager");
58
+ const input_validation_1 = require("./security/input_validation");
59
+ const docker_snapshot_1 = require("./security/snapshot_and_rollback/docker_snapshot");
60
+ const index_1 = require("./security/snapshot_and_rollback/index");
61
+ const skill_audit_1 = require("./security/skill_audit");
62
+ const execution_audit_1 = require("./security/execution_audit");
63
+ const memory_audit_1 = require("./security/memory_audit");
64
+ class AgentLoop {
65
+ bus;
66
+ provider;
67
+ workspace;
68
+ model;
69
+ maxIterations;
70
+ temperature;
71
+ maxTokens;
72
+ memoryWindow;
73
+ braveApiKey;
74
+ execConfig;
75
+ cronService;
76
+ restrictToWorkspace;
77
+ dockerSandbox;
78
+ securityConfig;
79
+ onReady;
80
+ readySignaled = false;
81
+ readyPromise;
82
+ resolveReady;
83
+ context;
84
+ sessions;
85
+ tools;
86
+ subagents;
87
+ running = false;
88
+ security;
89
+ dockerSnapshot = null;
90
+ constructor(opts) {
91
+ this.bus = opts.bus;
92
+ this.provider = opts.provider;
93
+ this.workspace = opts.workspace;
94
+ this.model = opts.model ?? opts.provider.getDefaultModel();
95
+ this.maxIterations = opts.maxIterations ?? 20;
96
+ this.temperature = opts.temperature ?? 0.7;
97
+ this.maxTokens = opts.maxTokens ?? 4096;
98
+ this.memoryWindow = opts.memoryWindow ?? 50;
99
+ this.braveApiKey = opts.braveApiKey;
100
+ this.execConfig = opts.execConfig ?? {};
101
+ this.cronService = opts.cronService;
102
+ this.restrictToWorkspace = opts.restrictToWorkspace ?? false;
103
+ this.dockerSandbox = opts.dockerSandbox;
104
+ this.securityConfig = opts.securityConfig ?? {};
105
+ this.onReady = opts.onReady;
106
+ this.readyPromise = new Promise((resolve) => {
107
+ this.resolveReady = resolve;
108
+ });
109
+ const containerWorkspace = this.dockerSandbox?.workspaceContainer;
110
+ const pathTranslator = this.dockerSandbox
111
+ ? (p) => this.dockerSandbox.hostToContainer(p)
112
+ : undefined;
113
+ this.context = new context_1.ContextBuilder({
114
+ workspace: this.workspace,
115
+ containerWorkspace,
116
+ pathTranslator,
117
+ dockerSandbox: this.dockerSandbox
118
+ ? { image: this.dockerSandbox.image }
119
+ : undefined,
120
+ });
121
+ this.sessions = opts.sessionManager ?? new manager_1.SessionManager(this.workspace);
122
+ this.tools = new registry_1.ToolRegistry();
123
+ this.subagents = new subagent_1.SubagentManager({
124
+ provider: this.provider,
125
+ workspace: this.workspace,
126
+ bus: this.bus,
127
+ model: this.model,
128
+ braveApiKey: this.braveApiKey,
129
+ execConfig: this.execConfig,
130
+ restrictToWorkspace: this.restrictToWorkspace,
131
+ });
132
+ this._registerDefaultTools();
133
+ // Initialize security validator
134
+ this.security = new input_validation_1.SecurityValidator({
135
+ provider: this.provider,
136
+ model: this.model,
137
+ toolRegistry: this.tools,
138
+ workspace: this.workspace,
139
+ prohibitedCommands: this.securityConfig.prohibitedCommands ?? [],
140
+ });
141
+ // Build DockerSnapshotManager
142
+ if (this.dockerSandbox) {
143
+ const _hostBackend = (0, index_1.getBackend)();
144
+ const _snapshotEnabled = this.dockerSandbox.snapshotEnabled ?? false;
145
+ const _hostDirs = (this.dockerSandbox.extraMounts ?? []).map((m) => m.split(":")[0]);
146
+ this.dockerSnapshot = _snapshotEnabled
147
+ ? new docker_snapshot_1.DockerSnapshotManager({
148
+ containerName: this.dockerSandbox.containerName,
149
+ workspace: this.workspace,
150
+ hostBackend: _hostBackend?.isAvailable() ? _hostBackend : null,
151
+ hostDirs: _hostDirs,
152
+ maxSnapshots: this.dockerSandbox.snapshotMax ?? 10,
153
+ })
154
+ : null;
155
+ }
156
+ else {
157
+ this.dockerSnapshot = null;
158
+ }
159
+ }
160
+ _registerDefaultTools() {
161
+ const allowedDir = this.restrictToWorkspace ? this.workspace : undefined;
162
+ const sandbox = this.dockerSandbox ?? undefined;
163
+ this.tools.register(new filesystem_1.ReadFileTool({ allowedDir, dockerSandbox: sandbox }));
164
+ this.tools.register(new filesystem_1.WriteFileTool({ allowedDir, dockerSandbox: sandbox }));
165
+ this.tools.register(new filesystem_1.EditFileTool({ allowedDir, dockerSandbox: sandbox }));
166
+ this.tools.register(new filesystem_1.ListDirTool({ allowedDir, dockerSandbox: sandbox }));
167
+ const workingDir = sandbox?.workspaceContainer ?? this.workspace;
168
+ this.tools.register(new shell_1.ExecTool({
169
+ workingDir,
170
+ timeout: this.execConfig.timeout,
171
+ restrictToWorkspace: this.restrictToWorkspace,
172
+ dockerSandbox: sandbox,
173
+ }));
174
+ this.tools.register(new web_1.WebSearchTool({ apiKey: this.braveApiKey }));
175
+ this.tools.register(new web_1.WebFetchTool());
176
+ const messageTool = new message_1.MessageTool({
177
+ sendCallback: (msg) => this.bus.publishOutbound(msg),
178
+ });
179
+ this.tools.register(messageTool);
180
+ const spawnTool = new spawn_1.SpawnTool(this.subagents);
181
+ this.tools.register(spawnTool);
182
+ if (this.cronService) {
183
+ this.tools.register(new cron_1.CronTool(this.cronService));
184
+ }
185
+ }
186
+ _setToolContext(channel, chatId) {
187
+ const messageTool = this.tools.get("message");
188
+ if (messageTool instanceof message_1.MessageTool)
189
+ messageTool.setContext(channel, chatId);
190
+ const spawnTool = this.tools.get("spawn");
191
+ if (spawnTool instanceof spawn_1.SpawnTool)
192
+ spawnTool.setContext(channel, chatId);
193
+ const cronTool = this.tools.get("cron");
194
+ if (cronTool instanceof cron_1.CronTool)
195
+ cronTool.setContext(channel, chatId);
196
+ }
197
+ async run() {
198
+ this.running = true;
199
+ logger_1.default.info("Agent loop started");
200
+ // Reuse the same pending promise to avoid accumulating dangling waiters
201
+ // in AsyncQueue when Promise.race timeout fires before a message arrives.
202
+ let pending = null;
203
+ let readyAnnounced = false;
204
+ while (this.running) {
205
+ try {
206
+ if (!pending) {
207
+ pending = this.bus.consumeInbound();
208
+ if (!readyAnnounced) {
209
+ readyAnnounced = true;
210
+ this._signalReady();
211
+ }
212
+ }
213
+ const msg = await Promise.race([
214
+ pending.then((m) => { pending = null; return m; }),
215
+ new Promise((res) => setTimeout(() => res(null), 1000)),
216
+ ]);
217
+ if (!msg)
218
+ continue;
219
+ try {
220
+ const response = await this._processMessage(msg);
221
+ if (response)
222
+ await this.bus.publishOutbound(response);
223
+ }
224
+ catch (e) {
225
+ logger_1.default.error(`Error processing message: ${e}`);
226
+ await this.bus.publishOutbound((0, events_1.makeOutboundMessage)({
227
+ channel: msg.channel,
228
+ chatId: msg.chatId,
229
+ content: `Sorry, I encountered an error: ${String(e)}`,
230
+ }));
231
+ }
232
+ }
233
+ catch {
234
+ // Timeout or other error — continue
235
+ }
236
+ }
237
+ }
238
+ stop() {
239
+ this.running = false;
240
+ logger_1.default.info("Agent loop stopping");
241
+ }
242
+ _signalReady() {
243
+ if (this.readySignaled)
244
+ return;
245
+ this.readySignaled = true;
246
+ this.resolveReady();
247
+ if (this.onReady) {
248
+ try {
249
+ this.onReady();
250
+ }
251
+ catch (e) {
252
+ logger_1.default.warn(`Agent onReady callback error: ${e}`);
253
+ }
254
+ }
255
+ }
256
+ async waitUntilReady(timeoutMs = 0) {
257
+ if (this.readySignaled)
258
+ return;
259
+ if (timeoutMs <= 0) {
260
+ await this.readyPromise;
261
+ return;
262
+ }
263
+ await Promise.race([
264
+ this.readyPromise,
265
+ new Promise((_, reject) => {
266
+ setTimeout(() => reject(new Error(`Agent readiness timeout after ${timeoutMs}ms`)), timeoutMs);
267
+ }),
268
+ ]);
269
+ }
270
+ _secondsSinceLastUserMessage(session) {
271
+ for (let i = session.messages.length - 1; i >= 0; i--) {
272
+ const item = session.messages[i];
273
+ if (item["role"] !== "user")
274
+ continue;
275
+ const rawTs = item["timestamp"];
276
+ if (!rawTs)
277
+ continue;
278
+ try {
279
+ let tsText = String(rawTs);
280
+ if (tsText.endsWith("Z"))
281
+ tsText = tsText.slice(0, -1) + "+00:00";
282
+ const parsed = new Date(tsText);
283
+ return Math.max(0, (Date.now() - parsed.getTime()) / 1000);
284
+ }
285
+ catch {
286
+ continue;
287
+ }
288
+ }
289
+ return null;
290
+ }
291
+ _isToolOutputSummary(content) {
292
+ return String(content ?? "").startsWith("[Concise summary of earlier tool output]");
293
+ }
294
+ async _llmSummarizeToolOutput(content) {
295
+ const text = String(content ?? "").trim();
296
+ if (!text)
297
+ return "[Concise summary of earlier tool output] (empty output)";
298
+ if (this._isToolOutputSummary(text))
299
+ return text;
300
+ const prompt = "Summarize this tool output in 1-2 short sentences for future agent context. " +
301
+ "Keep only decisive facts: status, key findings, important paths/values, and errors. " +
302
+ `No markdown or bullet points.\n\nTool output:\n${text.slice(0, 5000)}`;
303
+ try {
304
+ const response = await this.provider.chat([
305
+ { role: "system", content: "You compress tool outputs into concise factual summaries." },
306
+ { role: "user", content: prompt },
307
+ ], { model: this.model });
308
+ const summary = (response.content ?? "").replace(/\s+/g, " ").trim();
309
+ if (summary)
310
+ return `[Concise summary of earlier tool output] ${summary.slice(0, 360)}`;
311
+ }
312
+ catch (e) {
313
+ logger_1.default.warn(`LLM tool-output summary failed, using fallback: ${e}`);
314
+ }
315
+ const normalized = text.replace(/\s+/g, " ");
316
+ if (normalized.length <= 280)
317
+ return `[Concise summary of earlier tool output] ${normalized}`;
318
+ return `[Concise summary of earlier tool output] ${normalized.slice(0, 200)} ... ${normalized.slice(-60)}`;
319
+ }
320
+ async _appendToolResultWithIncrementalCompression(execMessages, toolCallId, toolName, result, latestFullToolIdx) {
321
+ if (latestFullToolIdx !== null && latestFullToolIdx >= 0 && latestFullToolIdx < execMessages.length) {
322
+ const prev = execMessages[latestFullToolIdx];
323
+ if (prev["role"] === "tool") {
324
+ const summarized = await this._llmSummarizeToolOutput(prev["content"]);
325
+ execMessages[latestFullToolIdx] = { ...prev, content: summarized };
326
+ }
327
+ }
328
+ this.context.addToolResult(execMessages, toolCallId, toolName, String(result));
329
+ return [execMessages, execMessages.length - 1];
330
+ }
331
+ async _takeSnapshotIfEnabled(msg, key, label) {
332
+ if (!this.dockerSnapshot || !this.dockerSandbox)
333
+ return;
334
+ const dockerSandbox = this.dockerSandbox;
335
+ const minInterval = Math.max(0, this.securityConfig.dockerSandbox?.snapshotMinIntervalSeconds ?? 0);
336
+ const session = this.sessions.getOrCreate(key);
337
+ const secondsSinceLast = this._secondsSinceLastUserMessage(session);
338
+ const shouldSnapshot = minInterval === 0 || secondsSinceLast === null || secondsSinceLast >= minInterval;
339
+ if (!shouldSnapshot) {
340
+ logger_1.default.info(`Skip snapshot due to interval threshold: gap=${secondsSinceLast?.toFixed(1)}s < min=${minInterval}s`);
341
+ return;
342
+ }
343
+ await this.bus.publishOutbound((0, events_1.makeOutboundMessage)({
344
+ channel: msg.channel,
345
+ chatId: msg.chatId,
346
+ content: "🛟 Generating snapshot, Please wait a minute ...",
347
+ metadata: { keepTyping: true },
348
+ }));
349
+ await new Promise((resolve) => setImmediate(resolve));
350
+ let snapTag = null;
351
+ try {
352
+ snapTag = await this.dockerSnapshot.takeSnapshotAsync(label, (tag) => dockerSandbox.buildRunCmd(tag));
353
+ }
354
+ catch (e) {
355
+ logger_1.default.warn(`Snapshot failed: ${e}`);
356
+ }
357
+ if (snapTag) {
358
+ const manifestPath = this.dockerSnapshot.getManifestPath();
359
+ await this.bus.publishOutbound((0, events_1.makeOutboundMessage)({
360
+ channel: msg.channel,
361
+ chatId: msg.chatId,
362
+ content: `✅ Snapshot created successfully: ${snapTag}\nSnapshot cleanup location: ${manifestPath}`,
363
+ metadata: { keepTyping: true },
364
+ }));
365
+ }
366
+ else {
367
+ await this.bus.publishOutbound((0, events_1.makeOutboundMessage)({
368
+ channel: msg.channel,
369
+ chatId: msg.chatId,
370
+ content: "⚠️ Snapshot creation failed, continuing.",
371
+ metadata: { keepTyping: true },
372
+ }));
373
+ }
374
+ }
375
+ async _buildConfirmationMessage(opts) {
376
+ const { toolName, toolArgs, messages, securityReason } = opts;
377
+ const historyLines = [];
378
+ for (const m of messages) {
379
+ const role = String(m["role"] ?? "");
380
+ const content = String(m["content"] ?? "");
381
+ if (role === "system")
382
+ continue;
383
+ if (role === "tool") {
384
+ const snippet = content.slice(0, 200) + (content.length > 200 ? "..." : "");
385
+ historyLines.push(`[tool result ${String(m["tool_call_id"] ?? "?")}]: ${snippet}`);
386
+ }
387
+ else if (role === "assistant") {
388
+ const tcs = m["tool_calls"] ?? [];
389
+ for (const tc of tcs) {
390
+ const fn = tc["function"] ?? {};
391
+ historyLines.push(`[assistant called]: ${String(fn["name"] ?? "")}(${String(fn["arguments"] ?? "").slice(0, 150)})`);
392
+ }
393
+ if (content)
394
+ historyLines.push(`[assistant]: ${content.slice(0, 200)}`);
395
+ }
396
+ else {
397
+ historyLines.push(`[${role}]: ${content.slice(0, 200)}`);
398
+ }
399
+ }
400
+ const executionHistory = historyLines.slice(-30).join("\n");
401
+ const argsSummary = JSON.stringify(toolArgs).slice(0, 400);
402
+ const prompt = `You are a security assistant helping a user understand why their AI agent is pausing for confirmation.
403
+
404
+ The agent was about to call a tool but security validation flagged it.
405
+
406
+ Tool name: ${toolName}
407
+ Tool arguments (truncated): ${argsSummary}
408
+
409
+ Execution history (most recent at bottom):
410
+ ${executionHistory}
411
+
412
+ Pending Reason (internal reason): ${securityReason}
413
+
414
+ Write a clear, concise confirmation request in natural language (2-4 short paragraphs):
415
+ 1. Indicate the specific command (tool call) that will be executed ("tool name", "parameters", or "shell command").
416
+ 2. Explain what the agent has done and why it decided to execute the current command.
417
+ 3. Explain the pending reason / security concern in as much detail as possible — what risk or anomaly was detected.
418
+ 4. End with a direct yes/no question asking the user whether to proceed.
419
+
420
+ ## Output format (no more than 200 words):
421
+ Pending Tool Call: <tool-call-command>
422
+ Explanation: <the task execution context and why the agent is calling this tool>
423
+ Pending Reason: <detailed explanation of the detected pending reason / security risk>
424
+ Confirmation Request: <a direct yes/no question asking the user whether to proceed>
425
+ `;
426
+ try {
427
+ const resp = await this.provider.chat([
428
+ { role: "system", content: "You are a security assistant composing user-friendly confirmation requests." },
429
+ { role: "user", content: prompt },
430
+ ], { model: this.model });
431
+ const text = (resp.content ?? "").trim();
432
+ if (text)
433
+ return text;
434
+ }
435
+ catch (e) {
436
+ logger_1.default.warn(`_buildConfirmationMessage LLM call failed: ${e}`);
437
+ }
438
+ return `Pending Tool Call: ${toolName} ${argsSummary}\n\nExplanation: The agent selected this step to continue your requested task based on recent execution context.\n\nPending Reason: ${securityReason}\n\nConfirmation Request: Do you want to proceed with this action? (yes/no)`;
439
+ }
440
+ async _processMessage(msg, sessionKeyOverride) {
441
+ if (msg.channel === "system")
442
+ return this._processSystemMessage(msg);
443
+ const preview = msg.content.length > 80 ? msg.content.slice(0, 80) + "..." : msg.content;
444
+ logger_1.default.info(`Processing message from ${msg.channel}:${msg.senderId}: ${preview}`);
445
+ const key = sessionKeyOverride ?? (0, events_1.sessionKey)(msg);
446
+ const session = this.sessions.getOrCreate(key);
447
+ // Handle slash commands
448
+ const rawCmd = msg.content.trim().replace(/^@/, "").trim();
449
+ const cmd = rawCmd.toLowerCase();
450
+ if (cmd === "/new") {
451
+ const msgsBefore = [...session.messages];
452
+ (0, manager_1.clearSession)(session);
453
+ this.sessions.save(session);
454
+ this.sessions.invalidate(session.key);
455
+ this._consolidateMemory(session, true, msgsBefore).catch(() => { });
456
+ return (0, events_1.makeOutboundMessage)({
457
+ channel: msg.channel,
458
+ chatId: msg.chatId,
459
+ content: "New session started. Memory consolidation in progress.",
460
+ });
461
+ }
462
+ if (cmd === "/help") {
463
+ return (0, events_1.makeOutboundMessage)({
464
+ channel: msg.channel,
465
+ chatId: msg.chatId,
466
+ content: "🦾 seclaw commands:\n/new — Start a new conversation\n/skill_audit — Audit loaded skills for security risks\n/memory_audit — Audit stored memory for security risks\n/take_snapshot [label] — Manually create a snapshot\n/snapshot_list — List all available snapshots\n/snapshot_restore <TAG> — Restore a snapshot by tag\n/help — Show available commands",
467
+ });
468
+ }
469
+ if (cmd === "/skill_audit") {
470
+ await this._takeSnapshotIfEnabled(msg, key, `${key}: /skill_audit`);
471
+ return (0, skill_audit_1.auditSkills)({
472
+ skillsLoader: this.context.skills,
473
+ provider: this.provider,
474
+ model: this.model,
475
+ workspace: this.workspace,
476
+ msg,
477
+ });
478
+ }
479
+ if (cmd === "/memory_audit") {
480
+ await this._takeSnapshotIfEnabled(msg, key, `${key}: /memory_audit`);
481
+ return (0, memory_audit_1.auditMemory)({
482
+ workspace: this.workspace,
483
+ provider: this.provider,
484
+ model: this.model,
485
+ msg,
486
+ });
487
+ }
488
+ if (cmd === "/snapshot_list") {
489
+ if (!this.dockerSnapshot) {
490
+ return (0, events_1.makeOutboundMessage)({ channel: msg.channel, chatId: msg.chatId, content: "⚠️ Snapshot feature is not enabled." });
491
+ }
492
+ const snapshots = this.dockerSnapshot.listSnapshots();
493
+ if (!snapshots.length) {
494
+ return (0, events_1.makeOutboundMessage)({ channel: msg.channel, chatId: msg.chatId, content: "No snapshots found." });
495
+ }
496
+ const snapshotLines = [`📦 Available snapshots (${snapshots.length} total):\n`];
497
+ for (let si = 0; si < snapshots.length; si++) {
498
+ const s = snapshots[si];
499
+ snapshotLines.push(`${si}. [${String(s["timestamp"] ?? "?")}] ${String(s["tag"] ?? "?")}\n ${String(s["label"] ?? "")}`);
500
+ }
501
+ return (0, events_1.makeOutboundMessage)({ channel: msg.channel, chatId: msg.chatId, content: snapshotLines.join("\n") });
502
+ }
503
+ if (cmd === "/take_snapshot" || cmd.startsWith("/take_snapshot ")) {
504
+ if (!this.dockerSnapshot || !this.dockerSandbox) {
505
+ return (0, events_1.makeOutboundMessage)({ channel: msg.channel, chatId: msg.chatId, content: "⚠️ Snapshot feature is not enabled." });
506
+ }
507
+ const labelArg = rawCmd.replace(/^\/take_snapshot\b/i, "").trim();
508
+ const label = labelArg || `${key}: manual /take_snapshot`;
509
+ await this.bus.publishOutbound((0, events_1.makeOutboundMessage)({
510
+ channel: msg.channel,
511
+ chatId: msg.chatId,
512
+ content: "🛟 Generating snapshot, please wait a moment ...",
513
+ metadata: { keepTyping: true },
514
+ }));
515
+ await new Promise((resolve) => setImmediate(resolve));
516
+ let snapTag = null;
517
+ try {
518
+ snapTag = await this.dockerSnapshot.takeSnapshotAsync(label, (tag) => this.dockerSandbox.buildRunCmd(tag));
519
+ }
520
+ catch (e) {
521
+ logger_1.default.warn(`Manual snapshot failed: ${e}`);
522
+ }
523
+ if (!snapTag) {
524
+ return (0, events_1.makeOutboundMessage)({ channel: msg.channel, chatId: msg.chatId, content: "❌ Snapshot creation failed." });
525
+ }
526
+ const manifestPath = this.dockerSnapshot.getManifestPath();
527
+ return (0, events_1.makeOutboundMessage)({
528
+ channel: msg.channel,
529
+ chatId: msg.chatId,
530
+ content: `✅ Snapshot created successfully: ${snapTag}\nSnapshot cleanup location: ${manifestPath}`,
531
+ });
532
+ }
533
+ if (cmd.startsWith("/snapshot_restore")) {
534
+ if (!this.dockerSnapshot) {
535
+ return (0, events_1.makeOutboundMessage)({ channel: msg.channel, chatId: msg.chatId, content: "⚠️ Snapshot feature is not enabled." });
536
+ }
537
+ const dockerSandbox = this.dockerSandbox;
538
+ const cmdParts = rawCmd.split(/\s+/, 2);
539
+ const snapTag = cmdParts[1]?.trim() ?? "";
540
+ if (!snapTag) {
541
+ return (0, events_1.makeOutboundMessage)({ channel: msg.channel, chatId: msg.chatId, content: "⚠️ Please provide a tag. Usage: /snapshot_restore <TAG>" });
542
+ }
543
+ try {
544
+ this.dockerSnapshot.restoreSnapshot(snapTag, dockerSandbox ? (tag) => dockerSandbox.buildRunCmd(tag) : undefined);
545
+ return (0, events_1.makeOutboundMessage)({ channel: msg.channel, chatId: msg.chatId, content: `✅ Restored to snapshot: ${snapTag}` });
546
+ }
547
+ catch (e) {
548
+ return (0, events_1.makeOutboundMessage)({ channel: msg.channel, chatId: msg.chatId, content: `❌ Restore failed: ${e}` });
549
+ }
550
+ }
551
+ // Take a Docker snapshot before processing each user message
552
+ await this._takeSnapshotIfEnabled(msg, key, `${key}: ${msg.content.slice(0, 60)}`);
553
+ // Update tool contexts
554
+ this._setToolContext(msg.channel, msg.chatId);
555
+ let messages = this.context.buildMessages({
556
+ history: (0, manager_1.getHistory)(session, this.memoryWindow),
557
+ currentMessage: msg.content,
558
+ media: msg.media,
559
+ channel: msg.channel,
560
+ chatId: msg.chatId,
561
+ });
562
+ let execMessages = JSON.parse(JSON.stringify(messages));
563
+ let latestFullToolIdx = null;
564
+ for (let i = execMessages.length - 1; i >= 0; i--) {
565
+ if (execMessages[i]["role"] === "tool") {
566
+ latestFullToolIdx = i;
567
+ break;
568
+ }
569
+ }
570
+ // Resume execution if previous message was a user confirmation response
571
+ const confirmationMarker = /USER_CONFIRMATION_REQUEST/i;
572
+ const shouldResume = (messages.length >= 2
573
+ && messages[messages.length - 2]["role"] === "assistant"
574
+ && confirmationMarker.test(String(messages[messages.length - 2]["content"] ?? "")));
575
+ if (shouldResume) {
576
+ const resumePath = path.join(path.dirname(this.workspace), "security", "EXECUTION_RESUME.json");
577
+ try {
578
+ const resumed = JSON.parse(fs.readFileSync(resumePath, "utf-8"));
579
+ resumed.push({ role: "user", content: msg.content });
580
+ messages = resumed;
581
+ execMessages = JSON.parse(JSON.stringify(messages));
582
+ latestFullToolIdx = null;
583
+ for (let i = execMessages.length - 1; i >= 0; i--) {
584
+ if (execMessages[i]["role"] === "tool") {
585
+ latestFullToolIdx = i;
586
+ break;
587
+ }
588
+ }
589
+ }
590
+ catch (e) {
591
+ logger_1.default.warn(`Failed to load resume execution state: ${e}`);
592
+ }
593
+ }
594
+ // Analyze task for security validation
595
+ if (this.securityConfig.inputValidationEnabled) {
596
+ const execConversations = messages.filter((m) => m["role"] !== "system");
597
+ const userTask = JSON.stringify(execConversations);
598
+ await this.security.analyzeTask(userTask);
599
+ logger_1.default.info(`Security validation initialized`);
600
+ logger_1.default.info(`\n${this.security.getTrajectorySummary()}`);
601
+ }
602
+ // Agent loop
603
+ let iteration = 0;
604
+ let finalContent = null;
605
+ const toolsUsed = [];
606
+ while (iteration < this.maxIterations) {
607
+ iteration++;
608
+ const response = await this.provider.chat(execMessages, { tools: this.tools.getDefinitions(), model: this.model });
609
+ if (response.toolCalls.length > 0) {
610
+ const toolCallDicts = response.toolCalls.map((tc) => ({
611
+ id: tc.id,
612
+ type: "function",
613
+ function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },
614
+ }));
615
+ this.context.addAssistantMessage(messages, response.content, toolCallDicts, response.reasoningContent ?? undefined);
616
+ this.context.addAssistantMessage(execMessages, response.content, toolCallDicts, response.reasoningContent ?? undefined);
617
+ for (const toolCall of response.toolCalls) {
618
+ toolsUsed.push(toolCall.name);
619
+ const argsStr = JSON.stringify(toolCall.arguments).slice(0, 200);
620
+ logger_1.default.info(`Tool call: ${toolCall.name}(${argsStr})`);
621
+ // Security validation gate
622
+ let isValid = true;
623
+ let securityReason = "Input validation disabled";
624
+ if (this.securityConfig.inputValidationEnabled) {
625
+ [isValid, securityReason] = await this.security.validateToolCall(toolCall.name, toolCall.arguments);
626
+ }
627
+ if (!isValid) {
628
+ logger_1.default.warn(`Tool call requires user confirmation: ${toolCall.name}`);
629
+ const blockedResult = "⚠️ Tool call blocked by security validation. Waiting for user confirmation.";
630
+ this.context.addToolResult(messages, toolCall.id, toolCall.name, blockedResult);
631
+ [execMessages, latestFullToolIdx] = await this._appendToolResultWithIncrementalCompression(execMessages, toolCall.id, toolCall.name, blockedResult, latestFullToolIdx);
632
+ const rawReason = securityReason.replace(/^USER_CONFIRMATION_REQUEST:\s*/, "");
633
+ const confirmMsg = await this._buildConfirmationMessage({
634
+ toolName: toolCall.name,
635
+ toolArgs: toolCall.arguments,
636
+ messages,
637
+ securityReason: rawReason,
638
+ });
639
+ const fullConfirmation = `🟡 **USER_CONFIRMATION_REQUEST**\n\n${confirmMsg}`;
640
+ messages.push({ role: "assistant", content: fullConfirmation });
641
+ execMessages.push({ role: "assistant", content: fullConfirmation });
642
+ const resumePath = path.join(path.dirname(this.workspace), "security", "EXECUTION_RESUME.json");
643
+ fs.mkdirSync(path.dirname(resumePath), { recursive: true });
644
+ fs.writeFileSync(resumePath, JSON.stringify(execMessages, null, 4), "utf-8");
645
+ logger_1.default.info(`Execution state saved to ${resumePath}`);
646
+ finalContent = fullConfirmation;
647
+ break;
648
+ }
649
+ const result = await this.tools.execute(toolCall.name, toolCall.arguments);
650
+ // Guard model: detect and sanitize prompt injection in tool output
651
+ let safeResult = String(result);
652
+ if (this.securityConfig.outputValidationEnabled) {
653
+ const [sanitized, injectionDetected, detectionReason] = await this.security.detectAndSanitizeOutput(toolCall.name, result);
654
+ if (injectionDetected) {
655
+ logger_1.default.warn(`🛡️ Guard model sanitized output from ${toolCall.name}: ${detectionReason}`);
656
+ safeResult = `[Security Notice: Potential prompt injection detected and removed]\n\n${sanitized}`;
657
+ }
658
+ }
659
+ // Record observation for information flow tracking
660
+ this.security.recordObservation(toolCall.name, safeResult);
661
+ this.context.addToolResult(messages, toolCall.id, toolCall.name, safeResult);
662
+ [execMessages, latestFullToolIdx] = await this._appendToolResultWithIncrementalCompression(execMessages, toolCall.id, toolCall.name, safeResult, latestFullToolIdx);
663
+ }
664
+ // If user confirmation is needed, break iteration loop
665
+ if (finalContent !== null)
666
+ break;
667
+ // Save execution log if needed
668
+ const logEnabled = this.securityConfig.executionLogEnabled;
669
+ const logStep = this.securityConfig.executionLogStep ?? 5;
670
+ if (logEnabled && iteration % logStep === 0) {
671
+ const logName = `trajectory_${session.key}.json`;
672
+ const logDir = path.join(path.dirname(this.workspace), "security", "execution_logs");
673
+ fs.mkdirSync(logDir, { recursive: true });
674
+ fs.writeFileSync(path.join(logDir, logName), JSON.stringify(messages, null, 2), "utf-8");
675
+ }
676
+ }
677
+ else {
678
+ finalContent = response.content ?? null;
679
+ break;
680
+ }
681
+ }
682
+ if (finalContent === null)
683
+ finalContent = "I've completed processing but have no response to give.";
684
+ const responsePreview = finalContent.length > 120 ? finalContent.slice(0, 120) + "..." : finalContent;
685
+ logger_1.default.info(`Response to ${msg.channel}:${msg.senderId}: ${responsePreview}`);
686
+ messages.push({ role: "assistant", content: finalContent });
687
+ if (this.securityConfig.executionLogEnabled) {
688
+ const logName = `trajectory_${session.key}_${new Date().toISOString().replace(/[:.]/g, "")}.json`;
689
+ const logDir = path.join(path.dirname(this.workspace), "security", "execution_logs");
690
+ fs.mkdirSync(logDir, { recursive: true });
691
+ fs.writeFileSync(path.join(logDir, logName), JSON.stringify(messages, null, 2), "utf-8");
692
+ }
693
+ (0, manager_1.addMessage)(session, "user", msg.content);
694
+ (0, manager_1.addMessage)(session, "assistant", finalContent, toolsUsed.length ? { tools_used: toolsUsed } : {});
695
+ this.sessions.save(session);
696
+ // Launch post-execution risk audit in background
697
+ if (this.securityConfig.postExecutionAuditEnabled && toolsUsed.length > 0) {
698
+ (0, execution_audit_1.auditExecution)({
699
+ sessionKey: session.key,
700
+ messages,
701
+ toolsUsed,
702
+ provider: this.provider,
703
+ model: this.model,
704
+ workspace: this.workspace,
705
+ bus: this.bus,
706
+ channel: msg.channel,
707
+ chatId: msg.chatId,
708
+ }).catch(() => { });
709
+ }
710
+ if (session.messages.length > this.memoryWindow) {
711
+ this._consolidateMemory(session).catch(() => { });
712
+ }
713
+ return (0, events_1.makeOutboundMessage)({
714
+ channel: msg.channel,
715
+ chatId: msg.chatId,
716
+ content: finalContent,
717
+ metadata: msg.metadata ?? {},
718
+ });
719
+ }
720
+ async _processSystemMessage(msg) {
721
+ logger_1.default.info(`Processing system message from ${msg.senderId}`);
722
+ let originChannel = "cli";
723
+ let originChatId = msg.chatId;
724
+ if (msg.chatId.includes(":")) {
725
+ [originChannel, originChatId] = msg.chatId.split(":", 2);
726
+ }
727
+ const sessionKey = `${originChannel}:${originChatId}`;
728
+ const session = this.sessions.getOrCreate(sessionKey);
729
+ this._setToolContext(originChannel, originChatId);
730
+ let messages = this.context.buildMessages({
731
+ history: (0, manager_1.getHistory)(session),
732
+ currentMessage: msg.content,
733
+ channel: originChannel,
734
+ chatId: originChatId,
735
+ });
736
+ let execMessages = messages.map((m) => ({ ...m }));
737
+ let latestFullToolIdx = null;
738
+ let iteration = 0;
739
+ let finalContent = null;
740
+ while (iteration < this.maxIterations) {
741
+ iteration++;
742
+ const response = await this.provider.chat(execMessages, { tools: this.tools.getDefinitions(), model: this.model });
743
+ if (response.toolCalls.length > 0) {
744
+ const toolCallDicts = response.toolCalls.map((tc) => ({
745
+ id: tc.id,
746
+ type: "function",
747
+ function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },
748
+ }));
749
+ this.context.addAssistantMessage(messages, response.content, toolCallDicts);
750
+ this.context.addAssistantMessage(execMessages, response.content, toolCallDicts);
751
+ for (const tc of response.toolCalls) {
752
+ const result = await this.tools.execute(tc.name, tc.arguments);
753
+ this.context.addToolResult(messages, tc.id, tc.name, result);
754
+ [execMessages, latestFullToolIdx] = await this._appendToolResultWithIncrementalCompression(execMessages, tc.id, tc.name, result, latestFullToolIdx);
755
+ }
756
+ }
757
+ else {
758
+ finalContent = response.content ?? null;
759
+ break;
760
+ }
761
+ }
762
+ if (finalContent === null)
763
+ finalContent = "Background task completed.";
764
+ (0, manager_1.addMessage)(session, "user", `[System: ${msg.senderId}] ${msg.content}`);
765
+ (0, manager_1.addMessage)(session, "assistant", finalContent);
766
+ this.sessions.save(session);
767
+ return (0, events_1.makeOutboundMessage)({ channel: originChannel, chatId: originChatId, content: finalContent });
768
+ }
769
+ async _consolidateMemory(session, archiveAll = false, messagesOverride) {
770
+ const memory = new memory_1.MemoryStore(this.workspace);
771
+ const keepCount = this.memoryWindow / 2;
772
+ let oldMessages;
773
+ if (archiveAll) {
774
+ oldMessages = messagesOverride ?? session.messages;
775
+ logger_1.default.info(`Memory consolidation (archive_all): ${oldMessages.length} total messages archived`);
776
+ }
777
+ else {
778
+ if (session.messages.length <= keepCount)
779
+ return;
780
+ const lastConsolidated = session.lastConsolidated ?? 0;
781
+ const toProcess = session.messages.length - lastConsolidated;
782
+ if (toProcess <= 0)
783
+ return;
784
+ oldMessages = session.messages.slice(lastConsolidated, -keepCount);
785
+ if (oldMessages.length === 0)
786
+ return;
787
+ logger_1.default.info(`Memory consolidation: ${session.messages.length} total, ${oldMessages.length} new to process`);
788
+ }
789
+ const lines = [];
790
+ for (const m of oldMessages) {
791
+ if (!m["content"])
792
+ continue;
793
+ const tools = m["tools_used"] ? ` [tools: ${m["tools_used"].join(", ")}]` : "";
794
+ lines.push(`[${String(m["timestamp"] ?? "?").slice(0, 16)}] ${String(m["role"]).toUpperCase()}${tools}: ${m["content"]}`);
795
+ }
796
+ const conversation = lines.join("\n");
797
+ const currentMemory = memory.readLongTerm();
798
+ const prompt = `You are a memory consolidation agent. Process this conversation and return a JSON object with exactly two keys:
799
+
800
+ 1. "history_entry": A paragraph (2-5 sentences) summarizing the key events/decisions/topics. Start with a timestamp like [YYYY-MM-DD HH:MM]. Include enough detail to be useful when found by grep search later.
801
+
802
+ 2. "memory_update": The updated long-term memory content. Add any new facts: user location, preferences, personal info, habits, project context, technical decisions, tools/services used. If nothing new, return the existing content unchanged.
803
+
804
+ ## Current Long-term Memory
805
+ ${currentMemory || "(empty)"}
806
+
807
+ ## Conversation to Process
808
+ ${conversation}
809
+
810
+ Respond with ONLY valid JSON, no markdown fences.`;
811
+ try {
812
+ const response = await this.provider.chat([
813
+ { role: "system", content: "You are a memory consolidation agent. Respond only with valid JSON." },
814
+ { role: "user", content: prompt },
815
+ ], { model: this.model });
816
+ let text = (response.content ?? "").trim();
817
+ if (!text)
818
+ return;
819
+ if (text.startsWith("```"))
820
+ text = text.split("\n").slice(1).join("\n").split("```")[0].trim();
821
+ let result;
822
+ try {
823
+ result = JSON.parse(text);
824
+ }
825
+ catch {
826
+ // Attempt a simple repair: extract JSON object pattern
827
+ const match = text.match(/\{[\s\S]*\}/);
828
+ if (!match)
829
+ return;
830
+ try {
831
+ result = JSON.parse(match[0]);
832
+ }
833
+ catch {
834
+ return;
835
+ }
836
+ }
837
+ if (result["history_entry"])
838
+ memory.appendHistory(result["history_entry"]);
839
+ if (result["memory_update"] && result["memory_update"] !== currentMemory) {
840
+ memory.writeLongTerm(result["memory_update"]);
841
+ }
842
+ if (!archiveAll) {
843
+ session.lastConsolidated = session.messages.length - keepCount;
844
+ }
845
+ logger_1.default.info("Memory consolidation done");
846
+ }
847
+ catch (e) {
848
+ logger_1.default.error(`Memory consolidation failed: ${e}`);
849
+ }
850
+ }
851
+ async processDirect(content, sessionKey = "cli:direct", channel = "cli", chatId = "direct") {
852
+ const msg = (0, events_1.makeInboundMessage)({ channel, senderId: "user", chatId, content });
853
+ const response = await this._processMessage(msg, sessionKey);
854
+ return response?.content ?? "";
855
+ }
856
+ }
857
+ exports.AgentLoop = AgentLoop;
858
+ //# sourceMappingURL=loop.js.map