scalix 1.5.1 → 1.6.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.
package/dist/headless.js CHANGED
@@ -1,15 +1,16 @@
1
1
  import readline from 'node:readline';
2
2
  import fs from 'node:fs/promises';
3
3
  import { execSync } from 'node:child_process';
4
- import { createAgent } from './agent.js';
4
+ import { createAgent, REPORTED_ERROR } from './agent.js';
5
5
  import { defaultTools } from './tools.js';
6
6
  import { extraTools } from './tools-extra.js';
7
+ import { memoryTools } from './tools-memory.js';
7
8
  import { webTools } from './tools-web.js';
8
9
  import { advancedTools } from './tools-advanced.js';
9
10
  import { visionTools } from './tools-vision.js';
10
11
  import { voiceTools } from './tools-voice.js';
11
12
  import { browserTools, closeBrowser } from './tools-browser.js';
12
- import { loadMcpServices } from './mcp.js';
13
+ import { startMcp } from './mcp.js';
13
14
  import { costTracker } from './cost-tracker.js';
14
15
  import { saveSession, loadSession, listSessions, generateSessionId } from './sessions.js';
15
16
  import { logger } from './logger.js';
@@ -163,7 +164,6 @@ async function runSinglePrompt(prompt, output, format, outputSchemaPath, beforeE
163
164
  console.error('Error: No models configured.\nSet SCALIX_MODEL env var or run interactive setup: scalix');
164
165
  process.exit(1);
165
166
  }
166
- const mcpTools = loadMcpServices();
167
167
  // Only discover/load project-local plugins in a trusted workspace — plugins
168
168
  // dynamically import and execute code from the project directory.
169
169
  const plugins = isWorkspaceTrusted() ? await discoverPlugins(process.cwd()) : [];
@@ -171,7 +171,7 @@ async function runSinglePrompt(prompt, output, format, outputSchemaPath, beforeE
171
171
  process.stderr.write('Project plugins skipped (untrusted workspace).\n');
172
172
  }
173
173
  const pluginTools = getPluginTools(plugins);
174
- const allTools = [...defaultTools, ...extraTools, ...webTools, ...advancedTools, ...visionTools, ...voiceTools, ...browserTools, ...mcpTools, ...pluginTools];
174
+ const allTools = [...defaultTools, ...extraTools, ...memoryTools, ...webTools, ...advancedTools, ...visionTools, ...voiceTools, ...browserTools, ...pluginTools];
175
175
  const instructions = await buildEnhancedInstructions();
176
176
  let outputSchema;
177
177
  if (outputSchemaPath) {
@@ -214,6 +214,12 @@ async function runSinglePrompt(prompt, output, format, outputSchemaPath, beforeE
214
214
  groundingCheck: true,
215
215
  contextRetrieval: true,
216
216
  });
217
+ if (agent.getSkippedHookCount() > 0) {
218
+ process.stderr.write(`Project hooks skipped (untrusted workspace): ${agent.getSkippedHookCount()}\n`);
219
+ }
220
+ // Connect configured MCP servers and register their tools. Warnings only:
221
+ // a server that will not start must not take the whole run down with it.
222
+ const mcpHub = await startMcp(agent, (m) => process.stderr.write(`\x1b[33m⚠ ${m}\x1b[0m\n`));
217
223
  // Register the account's Scalix Cloud platform tools (one attempt; failure
218
224
  // is stated once and the run continues with local tools only).
219
225
  const cloudStatus = await agent.refreshCloudTools();
@@ -235,7 +241,9 @@ async function runSinglePrompt(prompt, output, format, outputSchemaPath, beforeE
235
241
  fullResponse = await agent.send(prompt);
236
242
  }
237
243
  catch (err) {
238
- console.error(`Error: ${err.message}`);
244
+ // The agent's 'error' event already printed this — don't print twice.
245
+ if (!err?.[REPORTED_ERROR])
246
+ console.error(`Error: ${err.message}`);
239
247
  if (beforeExit)
240
248
  await beforeExit();
241
249
  process.exit(1);
@@ -271,6 +279,7 @@ async function runSinglePrompt(prompt, output, format, outputSchemaPath, beforeE
271
279
  process.stdout.write(formatted + '\n');
272
280
  }
273
281
  await closeBrowser();
282
+ await mcpHub.close(); // stdio servers are child processes; don't orphan them
274
283
  if (beforeExit)
275
284
  await beforeExit();
276
285
  process.exit(0);
@@ -329,7 +338,6 @@ async function runInteractive() {
329
338
  if (selectedModel.length === 0) {
330
339
  throw new Error('No models configured. Set SCALIX_MODEL env var or run interactive setup.');
331
340
  }
332
- const mcpTools = loadMcpServices();
333
341
  // Only discover/load project-local plugins in a trusted workspace — plugins
334
342
  // dynamically import and execute code from the project directory.
335
343
  const plugins = isWorkspaceTrusted() ? await discoverPlugins(process.cwd()) : [];
@@ -337,7 +345,7 @@ async function runInteractive() {
337
345
  process.stderr.write('Project plugins skipped (untrusted workspace).\n');
338
346
  }
339
347
  const pluginTools = getPluginTools(plugins);
340
- const allTools = [...defaultTools, ...extraTools, ...webTools, ...advancedTools, ...visionTools, ...voiceTools, ...browserTools, ...mcpTools, ...pluginTools];
348
+ const allTools = [...defaultTools, ...extraTools, ...memoryTools, ...webTools, ...advancedTools, ...visionTools, ...voiceTools, ...browserTools, ...pluginTools];
341
349
  const instructions = await buildEnhancedInstructions();
342
350
  const rl = readline.createInterface({
343
351
  input: process.stdin,
@@ -353,7 +361,11 @@ async function runInteractive() {
353
361
  return Promise.resolve('/exit');
354
362
  return new Promise((resolve) => rl.question(question, resolve));
355
363
  }
356
- const customCmds = await loadCustomCommands(process.cwd());
364
+ // Command templates can carry !`cmd` inline-bash — trust-gate like plugins.
365
+ const customCmds = isWorkspaceTrusted() ? await loadCustomCommands(process.cwd()) : [];
366
+ if (!isWorkspaceTrusted()) {
367
+ process.stderr.write('Project custom commands skipped (untrusted workspace).\n');
368
+ }
357
369
  const agent = createAgent({
358
370
  apiKey,
359
371
  baseUrl,
@@ -383,6 +395,9 @@ async function runInteractive() {
383
395
  return answer.trim().toLowerCase() === 'y';
384
396
  },
385
397
  });
398
+ if (agent.getSkippedHookCount() > 0) {
399
+ process.stderr.write(`Project hooks skipped (untrusted workspace): ${agent.getSkippedHookCount()}\n`);
400
+ }
386
401
  let sessionId = generateSessionId();
387
402
  agent.on('thinking:start', () => process.stdout.write('\n\x1b[33mThinking...\x1b[0m'));
388
403
  agent.on('tool:call', (name, args) => {
@@ -413,6 +428,9 @@ async function runInteractive() {
413
428
  console.log(`\x1b[33m\u26A0 Context usage: ${info.percentage}% (${info.tokens}/${info.maxTokens} tokens)\x1b[0m`);
414
429
  }
415
430
  });
431
+ // Connect configured MCP servers and register their tools. Warnings only:
432
+ // a server that will not start must not take the whole run down with it.
433
+ const mcpHub = await startMcp(agent, (m) => process.stderr.write(`\x1b[33m⚠ ${m}\x1b[0m\n`));
416
434
  // Register the account's Scalix Cloud platform tools (one attempt; failure
417
435
  // is stated once and the session continues with local tools only).
418
436
  const cloudStatus = await agent.refreshCloudTools();
@@ -445,6 +463,7 @@ async function runInteractive() {
445
463
  console.log('Goodbye!');
446
464
  rl.close();
447
465
  await closeBrowser().catch(() => { });
466
+ await mcpHub.close().catch(() => { }); // stdio servers are our children
448
467
  process.exit(0);
449
468
  }
450
469
  process.on('SIGINT', () => { shutdown().catch(() => process.exit(1)); });
@@ -610,6 +629,10 @@ async function runInteractive() {
610
629
  return true;
611
630
  }
612
631
  if (cmd === '/hooks') {
632
+ const skipped = agent.getSkippedHookCount();
633
+ if (skipped > 0) {
634
+ console.log(`\n${skipped} project hook(s) skipped — untrusted workspace.`);
635
+ }
613
636
  console.log('\n' + hooksSummary(agent.getHooks()) + '\n');
614
637
  return true;
615
638
  }
@@ -626,7 +649,18 @@ async function runInteractive() {
626
649
  const cheapModel = pickCheapestModel(agent.getModels());
627
650
  console.log(`\nDelegating to sub-agent (${shortModel(cheapModel)})...`);
628
651
  try {
629
- const result = await spawnSubAgent({ apiKey: apiKey, baseUrl, models: agent.getModels() }, prompt, { model: cheapModel, tools: 'read-only', maxSteps: 10 });
652
+ const result = await spawnSubAgent({
653
+ apiKey: apiKey,
654
+ baseUrl,
655
+ // Inherit the parent's provider + cloud credentials so a BYOK
656
+ // session delegates through the same endpoint dialect it runs on;
657
+ // without `provider` the sub-agent defaults to the Scalix client
658
+ // and 404s on every request against a third-party base URL.
659
+ provider: active.provider,
660
+ cloudApiKey: active.cloudApiKey,
661
+ cloudBaseUrl: active.cloudBaseUrl,
662
+ models: agent.getModels(),
663
+ }, prompt, { model: cheapModel, tools: 'read-only', maxSteps: 10 });
630
664
  console.log(`\n${result.response}\n`);
631
665
  console.log(`\x1b[36mSub-agent: ${result.steps} steps, ~${result.tokenEstimate} tokens\x1b[0m\n`);
632
666
  }