@ch4p/cli 0.1.5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,313 @@
1
+ import {
2
+ CanvasSessionManager,
3
+ CanvasTool,
4
+ GatewayServer,
5
+ PairingManager,
6
+ SessionManager
7
+ } from "./chunk-WYXCGS55.js";
8
+ import {
9
+ DefaultSecurityPolicy,
10
+ NativeEngine,
11
+ ProviderRegistry,
12
+ createClaudeCliEngine,
13
+ createCodexCliEngine,
14
+ createMemoryBackend,
15
+ createObserver
16
+ } from "./chunk-VJATFD4D.js";
17
+ import {
18
+ LoadSkillTool,
19
+ ToolRegistry
20
+ } from "./chunk-MABLWEGE.js";
21
+ import {
22
+ SkillRegistry
23
+ } from "./chunk-6BURGD2Y.js";
24
+ import {
25
+ AgentLoop
26
+ } from "./chunk-UNF4S4CA.js";
27
+ import {
28
+ generateId
29
+ } from "./chunk-YSCX2QQQ.js";
30
+ import {
31
+ getLogsDir,
32
+ loadConfig
33
+ } from "./chunk-AORLXQHZ.js";
34
+ import {
35
+ BOLD,
36
+ DIM,
37
+ GREEN,
38
+ RED,
39
+ RESET,
40
+ TEAL,
41
+ separator
42
+ } from "./chunk-NMGPBPNU.js";
43
+
44
+ // src/commands/canvas.ts
45
+ import { resolve, dirname } from "path";
46
+ import { fileURLToPath } from "url";
47
+ import { execSync } from "child_process";
48
+ async function canvas(args) {
49
+ let config;
50
+ try {
51
+ config = loadConfig();
52
+ } catch (err) {
53
+ const message = err instanceof Error ? err.message : String(err);
54
+ console.error(`
55
+ ${RED}Failed to load config:${RESET} ${message}`);
56
+ console.error(` ${DIM}Run ${TEAL}ch4p onboard${DIM} to set up ch4p.${RESET}
57
+ `);
58
+ process.exitCode = 1;
59
+ return;
60
+ }
61
+ let port = config.canvas?.port ?? config.gateway.port ?? 4800;
62
+ let autoOpen = true;
63
+ for (let i = 0; i < args.length; i++) {
64
+ if (args[i] === "--port" && args[i + 1]) {
65
+ const parsed = parseInt(args[i + 1], 10);
66
+ if (!isNaN(parsed) && parsed > 0 && parsed <= 65535) {
67
+ port = parsed;
68
+ }
69
+ }
70
+ if (args[i] === "--no-open") {
71
+ autoOpen = false;
72
+ }
73
+ }
74
+ const host = "127.0.0.1";
75
+ const __dirname = dirname(fileURLToPath(import.meta.url));
76
+ const staticDir = resolve(__dirname, "..", "..", "..", "apps", "web", "dist");
77
+ const sessionManager = new SessionManager();
78
+ const pairingManager = config.canvas?.requirePairing ? new PairingManager() : void 0;
79
+ const canvasSessionManager = new CanvasSessionManager();
80
+ const engine = createCanvasEngine(config);
81
+ if (!engine) {
82
+ console.error(`
83
+ ${RED}No LLM engine available.${RESET}`);
84
+ console.error(` ${DIM}Ensure an API key is configured. Run ${TEAL}ch4p onboard${DIM}.${RESET}
85
+ `);
86
+ process.exitCode = 1;
87
+ return;
88
+ }
89
+ const obsCfg = {
90
+ observers: config.observability.observers ?? ["console"],
91
+ logLevel: config.observability.logLevel ?? "info",
92
+ logPath: `${getLogsDir()}/canvas.jsonl`
93
+ };
94
+ const observer = createObserver(obsCfg);
95
+ let memoryBackend;
96
+ try {
97
+ const memCfg = {
98
+ backend: config.memory.backend,
99
+ vectorWeight: config.memory.vectorWeight,
100
+ keywordWeight: config.memory.keywordWeight,
101
+ embeddingProvider: config.memory.embeddingProvider,
102
+ openaiApiKey: config.providers?.openai?.apiKey || void 0
103
+ };
104
+ memoryBackend = createMemoryBackend(memCfg);
105
+ } catch {
106
+ }
107
+ let skillRegistry;
108
+ try {
109
+ if (config.skills?.enabled && config.skills?.paths?.length) {
110
+ skillRegistry = SkillRegistry.createFromPaths(config.skills.paths);
111
+ }
112
+ } catch {
113
+ }
114
+ const sessionId = generateId(16);
115
+ const defaultSessionConfig = {
116
+ engineId: config.engines?.default ?? "native",
117
+ model: config.agent.model,
118
+ provider: config.agent.provider,
119
+ systemPrompt: "You are ch4p, an AI assistant with an interactive canvas workspace. You can render visual components on the canvas using the canvas_render tool. Available component types: card, chart, form, button, text_field, data_table, code_block, markdown, image, progress, status. Components are placed at (x, y) positions on a spatial canvas. You can connect components with directional edges to show relationships. Use the canvas to create rich, visual responses when appropriate."
120
+ };
121
+ const server = new GatewayServer({
122
+ port,
123
+ host,
124
+ sessionManager,
125
+ pairingManager,
126
+ canvasSessionManager,
127
+ staticDir,
128
+ defaultSessionConfig,
129
+ onCanvasConnection: (connSessionId, bridge) => {
130
+ wireCanvasSession(
131
+ connSessionId,
132
+ bridge,
133
+ canvasSessionManager,
134
+ engine,
135
+ config,
136
+ observer,
137
+ memoryBackend,
138
+ skillRegistry,
139
+ defaultSessionConfig
140
+ );
141
+ }
142
+ });
143
+ console.log(`
144
+ ${TEAL}${BOLD}ch4p Canvas${RESET}`);
145
+ console.log(separator());
146
+ console.log("");
147
+ try {
148
+ await server.start();
149
+ } catch (err) {
150
+ const message = err instanceof Error ? err.message : String(err);
151
+ console.error(` ${RED}Failed to start server:${RESET} ${message}`);
152
+ process.exitCode = 1;
153
+ return;
154
+ }
155
+ const addr = server.getAddress();
156
+ const bindDisplay = addr ? `${addr.host}:${addr.port}` : `${host}:${port}`;
157
+ const url = `http://${bindDisplay}/?session=${sessionId}`;
158
+ console.log(` ${GREEN}${BOLD}Server listening${RESET} on ${bindDisplay}`);
159
+ console.log(` ${BOLD}Session${RESET} ${sessionId}`);
160
+ console.log(` ${BOLD}Engine${RESET} ${engine.name}`);
161
+ console.log(` ${BOLD}Static dir${RESET} ${DIM}${staticDir}${RESET}`);
162
+ console.log("");
163
+ console.log(` ${BOLD}Routes:${RESET}`);
164
+ console.log(` ${DIM} WS /ws/:sessionId - WebSocket canvas connection${RESET}`);
165
+ console.log(` ${DIM} GET /health - liveness probe${RESET}`);
166
+ console.log(` ${DIM} GET /* - static files (web UI)${RESET}`);
167
+ console.log("");
168
+ if (pairingManager) {
169
+ const code = pairingManager.generateCode("Canvas startup");
170
+ console.log(` ${BOLD}Pairing code:${RESET} ${TEAL}${BOLD}${code.code}${RESET}`);
171
+ console.log(` ${DIM}Add ?token=YOUR_TOKEN to the URL after pairing.${RESET}`);
172
+ console.log("");
173
+ }
174
+ console.log(` ${GREEN}${BOLD}Canvas URL:${RESET} ${TEAL}${url}${RESET}`);
175
+ console.log("");
176
+ if (autoOpen) {
177
+ try {
178
+ const platform = process.platform;
179
+ const openCmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
180
+ execSync(`${openCmd} "${url}"`, { stdio: "ignore" });
181
+ console.log(` ${DIM}Browser opened.${RESET}`);
182
+ } catch {
183
+ console.log(` ${DIM}Couldn't auto-open browser. Open the URL above manually.${RESET}`);
184
+ }
185
+ }
186
+ console.log(` ${DIM}Press Ctrl+C to stop.${RESET}
187
+ `);
188
+ await new Promise((resolvePromise) => {
189
+ const shutdown = async () => {
190
+ console.log(`
191
+ ${DIM}Shutting down canvas...${RESET}`);
192
+ canvasSessionManager.endAll();
193
+ if (memoryBackend) {
194
+ try {
195
+ await memoryBackend.close();
196
+ } catch {
197
+ }
198
+ }
199
+ await server.stop();
200
+ await observer.flush?.();
201
+ console.log(` ${DIM}Goodbye!${RESET}
202
+ `);
203
+ resolvePromise();
204
+ };
205
+ process.on("SIGINT", () => void shutdown());
206
+ process.on("SIGTERM", () => void shutdown());
207
+ });
208
+ }
209
+ function createCanvasEngine(config) {
210
+ const engineId = config.engines?.default ?? "native";
211
+ const engineConfig = config.engines?.available?.[engineId];
212
+ if (engineId === "claude-cli") {
213
+ try {
214
+ return createClaudeCliEngine({
215
+ command: engineConfig?.command ?? void 0,
216
+ cwd: engineConfig?.cwd ?? void 0,
217
+ timeout: engineConfig?.timeout ?? void 0
218
+ });
219
+ } catch {
220
+ }
221
+ }
222
+ if (engineId === "codex-cli") {
223
+ try {
224
+ return createCodexCliEngine({
225
+ command: engineConfig?.command ?? void 0,
226
+ cwd: engineConfig?.cwd ?? void 0,
227
+ timeout: engineConfig?.timeout ?? void 0
228
+ });
229
+ } catch {
230
+ }
231
+ }
232
+ const providerName = config.agent.provider;
233
+ const providerConfig = config.providers?.[providerName];
234
+ const apiKey = providerConfig?.apiKey;
235
+ if (providerName !== "ollama" && (!apiKey || apiKey.trim().length === 0)) {
236
+ return null;
237
+ }
238
+ try {
239
+ const provider = ProviderRegistry.createProvider({
240
+ id: providerName,
241
+ type: providerName,
242
+ ...providerConfig
243
+ });
244
+ return new NativeEngine({ provider, defaultModel: config.agent.model });
245
+ } catch {
246
+ return null;
247
+ }
248
+ }
249
+ function wireCanvasSession(sessionId, bridge, canvasSessionManager, engine, config, observer, memoryBackend, skillRegistry, defaultSessionConfig) {
250
+ const entry = canvasSessionManager.getSession(sessionId);
251
+ if (!entry) return;
252
+ const { canvasState, canvasChannel } = entry;
253
+ canvasChannel.start({ sessionId }).catch(() => {
254
+ });
255
+ canvasChannel.onMessage((msg) => {
256
+ void (async () => {
257
+ try {
258
+ if (msg.text.startsWith("[ABORT]")) {
259
+ return;
260
+ }
261
+ const session = new (await import("./dist-FVYO37XH.js")).Session({
262
+ sessionId,
263
+ ...defaultSessionConfig
264
+ });
265
+ const exclude = config.autonomy.level === "readonly" ? ["bash", "file_write", "file_edit", "delegate"] : ["delegate"];
266
+ if (!config.mesh?.enabled) {
267
+ exclude.push("mesh");
268
+ }
269
+ const tools = ToolRegistry.createDefault({ exclude });
270
+ tools.register(new CanvasTool());
271
+ if (skillRegistry && skillRegistry.size > 0) {
272
+ tools.register(new LoadSkillTool(skillRegistry));
273
+ }
274
+ const securityPolicy = new DefaultSecurityPolicy({
275
+ workspace: process.cwd(),
276
+ autonomyLevel: config.autonomy.level,
277
+ allowedCommands: config.autonomy.allowedCommands,
278
+ blockedPaths: config.security.blockedPaths
279
+ });
280
+ const toolContextExtensions = {
281
+ canvasState
282
+ };
283
+ if (config.search?.enabled && config.search.apiKey) {
284
+ toolContextExtensions.searchApiKey = config.search.apiKey;
285
+ toolContextExtensions.searchConfig = {
286
+ maxResults: config.search.maxResults,
287
+ country: config.search.country,
288
+ searchLang: config.search.searchLang
289
+ };
290
+ }
291
+ const loop = new AgentLoop(session, engine, tools.list(), observer, {
292
+ maxIterations: 30,
293
+ maxRetries: 2,
294
+ enableStateSnapshots: true,
295
+ memoryBackend,
296
+ securityPolicy,
297
+ toolContextExtensions
298
+ });
299
+ for await (const event of loop.run(msg.text)) {
300
+ bridge.handleAgentEvent(event);
301
+ }
302
+ } catch (err) {
303
+ bridge.handleAgentEvent({
304
+ type: "error",
305
+ error: err instanceof Error ? err : new Error(String(err))
306
+ });
307
+ }
308
+ })();
309
+ });
310
+ }
311
+ export {
312
+ canvas
313
+ };
@@ -0,0 +1,313 @@
1
+ import {
2
+ CanvasSessionManager,
3
+ CanvasTool,
4
+ GatewayServer,
5
+ PairingManager,
6
+ SessionManager
7
+ } from "./chunk-73DREVNL.js";
8
+ import {
9
+ DefaultSecurityPolicy,
10
+ NativeEngine,
11
+ ProviderRegistry,
12
+ createClaudeCliEngine,
13
+ createCodexCliEngine,
14
+ createMemoryBackend,
15
+ createObserver
16
+ } from "./chunk-VJATFD4D.js";
17
+ import {
18
+ LoadSkillTool,
19
+ ToolRegistry
20
+ } from "./chunk-MABLWEGE.js";
21
+ import {
22
+ SkillRegistry
23
+ } from "./chunk-6BURGD2Y.js";
24
+ import {
25
+ AgentLoop
26
+ } from "./chunk-UNF4S4CA.js";
27
+ import {
28
+ generateId
29
+ } from "./chunk-YSCX2QQQ.js";
30
+ import {
31
+ getLogsDir,
32
+ loadConfig
33
+ } from "./chunk-AORLXQHZ.js";
34
+ import {
35
+ BOLD,
36
+ DIM,
37
+ GREEN,
38
+ RED,
39
+ RESET,
40
+ TEAL,
41
+ separator
42
+ } from "./chunk-NMGPBPNU.js";
43
+
44
+ // src/commands/canvas.ts
45
+ import { resolve, dirname } from "path";
46
+ import { fileURLToPath } from "url";
47
+ import { execSync } from "child_process";
48
+ async function canvas(args) {
49
+ let config;
50
+ try {
51
+ config = loadConfig();
52
+ } catch (err) {
53
+ const message = err instanceof Error ? err.message : String(err);
54
+ console.error(`
55
+ ${RED}Failed to load config:${RESET} ${message}`);
56
+ console.error(` ${DIM}Run ${TEAL}ch4p onboard${DIM} to set up ch4p.${RESET}
57
+ `);
58
+ process.exitCode = 1;
59
+ return;
60
+ }
61
+ let port = config.canvas?.port ?? config.gateway.port ?? 4800;
62
+ let autoOpen = true;
63
+ for (let i = 0; i < args.length; i++) {
64
+ if (args[i] === "--port" && args[i + 1]) {
65
+ const parsed = parseInt(args[i + 1], 10);
66
+ if (!isNaN(parsed) && parsed > 0 && parsed <= 65535) {
67
+ port = parsed;
68
+ }
69
+ }
70
+ if (args[i] === "--no-open") {
71
+ autoOpen = false;
72
+ }
73
+ }
74
+ const host = "127.0.0.1";
75
+ const __dirname = dirname(fileURLToPath(import.meta.url));
76
+ const staticDir = resolve(__dirname, "..", "..", "..", "apps", "web", "dist");
77
+ const sessionManager = new SessionManager();
78
+ const pairingManager = config.canvas?.requirePairing ? new PairingManager() : void 0;
79
+ const canvasSessionManager = new CanvasSessionManager();
80
+ const engine = createCanvasEngine(config);
81
+ if (!engine) {
82
+ console.error(`
83
+ ${RED}No LLM engine available.${RESET}`);
84
+ console.error(` ${DIM}Ensure an API key is configured. Run ${TEAL}ch4p onboard${DIM}.${RESET}
85
+ `);
86
+ process.exitCode = 1;
87
+ return;
88
+ }
89
+ const obsCfg = {
90
+ observers: config.observability.observers ?? ["console"],
91
+ logLevel: config.observability.logLevel ?? "info",
92
+ logPath: `${getLogsDir()}/canvas.jsonl`
93
+ };
94
+ const observer = createObserver(obsCfg);
95
+ let memoryBackend;
96
+ try {
97
+ const memCfg = {
98
+ backend: config.memory.backend,
99
+ vectorWeight: config.memory.vectorWeight,
100
+ keywordWeight: config.memory.keywordWeight,
101
+ embeddingProvider: config.memory.embeddingProvider,
102
+ openaiApiKey: config.providers?.openai?.apiKey || void 0
103
+ };
104
+ memoryBackend = createMemoryBackend(memCfg);
105
+ } catch {
106
+ }
107
+ let skillRegistry;
108
+ try {
109
+ if (config.skills?.enabled && config.skills?.paths?.length) {
110
+ skillRegistry = SkillRegistry.createFromPaths(config.skills.paths);
111
+ }
112
+ } catch {
113
+ }
114
+ const sessionId = generateId(16);
115
+ const defaultSessionConfig = {
116
+ engineId: config.engines?.default ?? "native",
117
+ model: config.agent.model,
118
+ provider: config.agent.provider,
119
+ systemPrompt: "You are ch4p, an AI assistant with an interactive canvas workspace. You can render visual components on the canvas using the canvas_render tool. Available component types: card, chart, form, button, text_field, data_table, code_block, markdown, image, progress, status. Components are placed at (x, y) positions on a spatial canvas. You can connect components with directional edges to show relationships. Use the canvas to create rich, visual responses when appropriate."
120
+ };
121
+ const server = new GatewayServer({
122
+ port,
123
+ host,
124
+ sessionManager,
125
+ pairingManager,
126
+ canvasSessionManager,
127
+ staticDir,
128
+ defaultSessionConfig,
129
+ onCanvasConnection: (connSessionId, bridge) => {
130
+ wireCanvasSession(
131
+ connSessionId,
132
+ bridge,
133
+ canvasSessionManager,
134
+ engine,
135
+ config,
136
+ observer,
137
+ memoryBackend,
138
+ skillRegistry,
139
+ defaultSessionConfig
140
+ );
141
+ }
142
+ });
143
+ console.log(`
144
+ ${TEAL}${BOLD}ch4p Canvas${RESET}`);
145
+ console.log(separator());
146
+ console.log("");
147
+ try {
148
+ await server.start();
149
+ } catch (err) {
150
+ const message = err instanceof Error ? err.message : String(err);
151
+ console.error(` ${RED}Failed to start server:${RESET} ${message}`);
152
+ process.exitCode = 1;
153
+ return;
154
+ }
155
+ const addr = server.getAddress();
156
+ const bindDisplay = addr ? `${addr.host}:${addr.port}` : `${host}:${port}`;
157
+ const url = `http://${bindDisplay}/?session=${sessionId}`;
158
+ console.log(` ${GREEN}${BOLD}Server listening${RESET} on ${bindDisplay}`);
159
+ console.log(` ${BOLD}Session${RESET} ${sessionId}`);
160
+ console.log(` ${BOLD}Engine${RESET} ${engine.name}`);
161
+ console.log(` ${BOLD}Static dir${RESET} ${DIM}${staticDir}${RESET}`);
162
+ console.log("");
163
+ console.log(` ${BOLD}Routes:${RESET}`);
164
+ console.log(` ${DIM} WS /ws/:sessionId - WebSocket canvas connection${RESET}`);
165
+ console.log(` ${DIM} GET /health - liveness probe${RESET}`);
166
+ console.log(` ${DIM} GET /* - static files (web UI)${RESET}`);
167
+ console.log("");
168
+ if (pairingManager) {
169
+ const code = pairingManager.generateCode("Canvas startup");
170
+ console.log(` ${BOLD}Pairing code:${RESET} ${TEAL}${BOLD}${code.code}${RESET}`);
171
+ console.log(` ${DIM}Add ?token=YOUR_TOKEN to the URL after pairing.${RESET}`);
172
+ console.log("");
173
+ }
174
+ console.log(` ${GREEN}${BOLD}Canvas URL:${RESET} ${TEAL}${url}${RESET}`);
175
+ console.log("");
176
+ if (autoOpen) {
177
+ try {
178
+ const platform = process.platform;
179
+ const openCmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
180
+ execSync(`${openCmd} "${url}"`, { stdio: "ignore" });
181
+ console.log(` ${DIM}Browser opened.${RESET}`);
182
+ } catch {
183
+ console.log(` ${DIM}Couldn't auto-open browser. Open the URL above manually.${RESET}`);
184
+ }
185
+ }
186
+ console.log(` ${DIM}Press Ctrl+C to stop.${RESET}
187
+ `);
188
+ await new Promise((resolvePromise) => {
189
+ const shutdown = async () => {
190
+ console.log(`
191
+ ${DIM}Shutting down canvas...${RESET}`);
192
+ canvasSessionManager.endAll();
193
+ if (memoryBackend) {
194
+ try {
195
+ await memoryBackend.close();
196
+ } catch {
197
+ }
198
+ }
199
+ await server.stop();
200
+ await observer.flush?.();
201
+ console.log(` ${DIM}Goodbye!${RESET}
202
+ `);
203
+ resolvePromise();
204
+ };
205
+ process.on("SIGINT", () => void shutdown());
206
+ process.on("SIGTERM", () => void shutdown());
207
+ });
208
+ }
209
+ function createCanvasEngine(config) {
210
+ const engineId = config.engines?.default ?? "native";
211
+ const engineConfig = config.engines?.available?.[engineId];
212
+ if (engineId === "claude-cli") {
213
+ try {
214
+ return createClaudeCliEngine({
215
+ command: engineConfig?.command ?? void 0,
216
+ cwd: engineConfig?.cwd ?? void 0,
217
+ timeout: engineConfig?.timeout ?? void 0
218
+ });
219
+ } catch {
220
+ }
221
+ }
222
+ if (engineId === "codex-cli") {
223
+ try {
224
+ return createCodexCliEngine({
225
+ command: engineConfig?.command ?? void 0,
226
+ cwd: engineConfig?.cwd ?? void 0,
227
+ timeout: engineConfig?.timeout ?? void 0
228
+ });
229
+ } catch {
230
+ }
231
+ }
232
+ const providerName = config.agent.provider;
233
+ const providerConfig = config.providers?.[providerName];
234
+ const apiKey = providerConfig?.apiKey;
235
+ if (providerName !== "ollama" && (!apiKey || apiKey.trim().length === 0)) {
236
+ return null;
237
+ }
238
+ try {
239
+ const provider = ProviderRegistry.createProvider({
240
+ id: providerName,
241
+ type: providerName,
242
+ ...providerConfig
243
+ });
244
+ return new NativeEngine({ provider, defaultModel: config.agent.model });
245
+ } catch {
246
+ return null;
247
+ }
248
+ }
249
+ function wireCanvasSession(sessionId, bridge, canvasSessionManager, engine, config, observer, memoryBackend, skillRegistry, defaultSessionConfig) {
250
+ const entry = canvasSessionManager.getSession(sessionId);
251
+ if (!entry) return;
252
+ const { canvasState, canvasChannel } = entry;
253
+ canvasChannel.start({ sessionId }).catch(() => {
254
+ });
255
+ canvasChannel.onMessage((msg) => {
256
+ void (async () => {
257
+ try {
258
+ if (msg.text.startsWith("[ABORT]")) {
259
+ return;
260
+ }
261
+ const session = new (await import("./dist-FVYO37XH.js")).Session({
262
+ sessionId,
263
+ ...defaultSessionConfig
264
+ });
265
+ const exclude = config.autonomy.level === "readonly" ? ["bash", "file_write", "file_edit", "delegate"] : ["delegate"];
266
+ if (!config.mesh?.enabled) {
267
+ exclude.push("mesh");
268
+ }
269
+ const tools = ToolRegistry.createDefault({ exclude });
270
+ tools.register(new CanvasTool());
271
+ if (skillRegistry && skillRegistry.size > 0) {
272
+ tools.register(new LoadSkillTool(skillRegistry));
273
+ }
274
+ const securityPolicy = new DefaultSecurityPolicy({
275
+ workspace: process.cwd(),
276
+ autonomyLevel: config.autonomy.level,
277
+ allowedCommands: config.autonomy.allowedCommands,
278
+ blockedPaths: config.security.blockedPaths
279
+ });
280
+ const toolContextExtensions = {
281
+ canvasState
282
+ };
283
+ if (config.search?.enabled && config.search.apiKey) {
284
+ toolContextExtensions.searchApiKey = config.search.apiKey;
285
+ toolContextExtensions.searchConfig = {
286
+ maxResults: config.search.maxResults,
287
+ country: config.search.country,
288
+ searchLang: config.search.searchLang
289
+ };
290
+ }
291
+ const loop = new AgentLoop(session, engine, tools.list(), observer, {
292
+ maxIterations: 30,
293
+ maxRetries: 2,
294
+ enableStateSnapshots: true,
295
+ memoryBackend,
296
+ securityPolicy,
297
+ toolContextExtensions
298
+ });
299
+ for await (const event of loop.run(msg.text)) {
300
+ bridge.handleAgentEvent(event);
301
+ }
302
+ } catch (err) {
303
+ bridge.handleAgentEvent({
304
+ type: "error",
305
+ error: err instanceof Error ? err : new Error(String(err))
306
+ });
307
+ }
308
+ })();
309
+ });
310
+ }
311
+ export {
312
+ canvas
313
+ };