scalix 1.0.0 → 1.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.
- package/.env.example +29 -0
- package/README.md +1 -1
- package/dist/agent-definitions.js +100 -0
- package/dist/agent-registry.js +226 -0
- package/dist/agent.js +352 -74
- package/dist/api-client.js +77 -0
- package/dist/cli.js +160 -35
- package/dist/constants.js +40 -6
- package/dist/context-builder.js +8 -1
- package/dist/context-retriever.js +257 -0
- package/dist/git-context.js +32 -29
- package/dist/grounding-checker.js +154 -0
- package/dist/headless.js +109 -20
- package/dist/hooks.js +7 -1
- package/dist/injection-detector.js +86 -0
- package/dist/ipc-server.js +6 -2
- package/dist/project-index.js +2 -0
- package/dist/sandbox.js +135 -11
- package/dist/sessions.js +37 -2
- package/dist/settings.js +111 -6
- package/dist/sub-agent.js +83 -8
- package/dist/task-board.js +105 -0
- package/dist/tools-advanced.js +304 -49
- package/dist/tools-browser.js +280 -19
- package/dist/tools-extra.js +7 -0
- package/dist/tools-vision.js +4 -3
- package/dist/tools.js +13 -3
- package/dist/update-checker.js +111 -0
- package/dist/worktree.js +172 -0
- package/package.json +6 -2
- package/app.py +0 -55
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useCallback, useRef, useMemo, memo } from 'react';
|
|
3
|
-
import { render, Box, Text,
|
|
3
|
+
import { render, Box, Text, useInput, useApp, useStdout } from 'ink';
|
|
4
4
|
import Spinner from 'ink-spinner';
|
|
5
5
|
import TextInput from 'ink-text-input';
|
|
6
6
|
import Gradient from 'ink-gradient';
|
|
@@ -17,13 +17,14 @@ import { webTools } from './tools-web.js';
|
|
|
17
17
|
import { advancedTools } from './tools-advanced.js';
|
|
18
18
|
import { visionTools } from './tools-vision.js';
|
|
19
19
|
import { voiceTools } from './tools-voice.js';
|
|
20
|
-
import { browserTools } from './tools-browser.js';
|
|
20
|
+
import { browserTools, closeBrowser } from './tools-browser.js';
|
|
21
21
|
import { loadMcpServices } from './mcp.js';
|
|
22
22
|
import { renderMarkdown } from './markdown.js';
|
|
23
23
|
import { costTracker } from './cost-tracker.js';
|
|
24
24
|
import { saveSession, listSessions, loadSession, generateSessionId } from './sessions.js';
|
|
25
25
|
import { logger } from './logger.js';
|
|
26
26
|
import { VERSION, PRODUCT_NAME, DEFAULT_MODELS, DEFAULT_BASE_URL, DEFAULT_INSTRUCTIONS, ENV_API_KEY, ENV_MODEL, TOOL_DEFAULTS, HEADLESS_DEFAULTS, fetchModelSpecs } from './constants.js';
|
|
27
|
+
import { checkForUpdate, getUpdateBanner, performUpdate } from './update-checker.js';
|
|
27
28
|
import { loadConfig, saveConfig, isConfigured, DEFAULT_LIMITS, THINKING_MODES } from './settings.js';
|
|
28
29
|
import { getModeDescription, PERMISSION_MODES, setSandboxRoot } from './sandbox.js';
|
|
29
30
|
import { buildProjectIndex, indexToContext } from './project-index.js';
|
|
@@ -36,12 +37,13 @@ import { hooksSummary } from './hooks.js';
|
|
|
36
37
|
import { spawnSubAgent, pickCheapestModel } from './sub-agent.js';
|
|
37
38
|
import { createCheckpoint, undoLastCheckpoint, listCheckpoints } from './git-undo.js';
|
|
38
39
|
import { loadScopedRules, rulesToContext } from './scoped-rules.js';
|
|
40
|
+
import { AgentRegistry } from './agent-registry.js';
|
|
39
41
|
logger.setLevel('silent');
|
|
40
42
|
setSandboxRoot(process.cwd());
|
|
41
43
|
// Suppress noisy React 19 development-mode reconciler warnings that leak to stderr
|
|
42
44
|
const _origConsoleError = console.error;
|
|
43
45
|
console.error = (...args) => {
|
|
44
|
-
if (typeof args[0] === 'string' && args[0]
|
|
46
|
+
if (typeof args[0] === 'string' && args[0].startsWith('Warning:') && args[0].includes('same key'))
|
|
45
47
|
return;
|
|
46
48
|
_origConsoleError(...args);
|
|
47
49
|
};
|
|
@@ -71,7 +73,7 @@ async function buildEnhancedInstructions() {
|
|
|
71
73
|
let instructions = baseInstructions;
|
|
72
74
|
const cwd = process.cwd();
|
|
73
75
|
try {
|
|
74
|
-
const gitInfo = collectGitInfo(cwd);
|
|
76
|
+
const gitInfo = await collectGitInfo(cwd);
|
|
75
77
|
if (gitInfo.isRepo)
|
|
76
78
|
instructions += '\n\n' + gitInfoToContext(gitInfo);
|
|
77
79
|
}
|
|
@@ -151,7 +153,7 @@ function initAgent(config) {
|
|
|
151
153
|
});
|
|
152
154
|
loadCustomCommands(process.cwd()).then((cmds) => {
|
|
153
155
|
customCommands = cmds;
|
|
154
|
-
}).catch(() => { });
|
|
156
|
+
}).catch((err) => { logger.warn('Failed to load custom commands', { error: err.message }); });
|
|
155
157
|
return agent;
|
|
156
158
|
}
|
|
157
159
|
// ─── Helpers ───
|
|
@@ -300,7 +302,7 @@ function SetupScreen({ onComplete, existingConfig }) {
|
|
|
300
302
|
if (result.length > 0) {
|
|
301
303
|
setFetchedModels(result.map((m) => ({ id: m.id, name: m.name })));
|
|
302
304
|
}
|
|
303
|
-
}).catch(() => { }).finally(() => {
|
|
305
|
+
}).catch((err) => { logger.warn('Failed to fetch model specs', { error: err.message }); }).finally(() => {
|
|
304
306
|
setFetchingModels(false);
|
|
305
307
|
setStep('models');
|
|
306
308
|
});
|
|
@@ -375,6 +377,8 @@ function SetupScreen({ onComplete, existingConfig }) {
|
|
|
375
377
|
limits,
|
|
376
378
|
permissionMode: existingConfig?.permissionMode || 'default',
|
|
377
379
|
thinkingMode: existingConfig?.thinkingMode || 'off',
|
|
380
|
+
security: existingConfig?.security || {},
|
|
381
|
+
injectionDetection: existingConfig?.injectionDetection || { enabled: true, sensitivity: 'medium' },
|
|
378
382
|
};
|
|
379
383
|
saveConfig(newConfig).then(() => {
|
|
380
384
|
onComplete(newConfig);
|
|
@@ -398,11 +402,14 @@ const StatusBar = memo(function StatusBar({ status, model, toolCount, thinkStart
|
|
|
398
402
|
const modeColor = mode === 'yolo' ? 'red' : mode === 'auto' ? 'yellow' : mode === 'plan' ? 'blue' : 'green';
|
|
399
403
|
const statusIcon = status === 'thinking' ? '\u25D4' : status === 'error' ? '\u2718' : '\u25CF';
|
|
400
404
|
const statusColor = status === 'thinking' ? 'yellow' : status === 'error' ? 'red' : 'green';
|
|
405
|
+
const activeAgents = AgentRegistry.getInstance().listActive().length;
|
|
401
406
|
const leftParts = [];
|
|
402
407
|
leftParts.push(shortModel(model));
|
|
403
408
|
if (tokens > 0)
|
|
404
409
|
leftParts.push(`${formatTokens(tokens)} tok`);
|
|
405
410
|
leftParts.push(`${toolCount} tools`);
|
|
411
|
+
if (activeAgents > 0)
|
|
412
|
+
leftParts.push(`${activeAgents} agent${activeAgents > 1 ? 's' : ''}`);
|
|
406
413
|
const leftText = leftParts.join(' \u00B7 ');
|
|
407
414
|
const rightLen = mode.length + VERSION.length + 6 + (elapsedStr ? elapsedStr.length + 3 : 0);
|
|
408
415
|
const gap = Math.max(cols - leftText.length - rightLen - 8, 2);
|
|
@@ -510,6 +517,7 @@ const StreamingArea = memo(function StreamingArea({ text, reasoning, model, maxL
|
|
|
510
517
|
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 3, paddingRight: 2, marginTop: 1, children: [displayReasoning.length > 0 && (_jsx(Box, { paddingLeft: 0, marginBottom: 0, children: _jsxs(Text, { color: "magenta", dimColor: true, italic: true, wrap: "wrap", children: ['\u2502', " ", displayReasoning] }) })), displayText.length > 0 ? (_jsxs(Text, { wrap: "wrap", children: [renderMarkdown(displayText), _jsx(Text, { color: "magentaBright", children: '\u258E' })] })) : displayReasoning.length === 0 ? (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "magentaBright", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { dimColor: true, children: ["Thinking", model ? ` (${model})` : '', "..."] })] })) : null] }));
|
|
511
518
|
});
|
|
512
519
|
function ConfirmPrompt({ toolName, args, onRespond, onScrollUp, onScrollDown }) {
|
|
520
|
+
const { cols } = useTermSize();
|
|
513
521
|
useInput((ch, key) => {
|
|
514
522
|
if (ch.toLowerCase() === 'y')
|
|
515
523
|
onRespond(true);
|
|
@@ -520,8 +528,26 @@ function ConfirmPrompt({ toolName, args, onRespond, onScrollUp, onScrollDown })
|
|
|
520
528
|
else if (key.pageDown || (key.downArrow && key.shift))
|
|
521
529
|
onScrollDown?.();
|
|
522
530
|
});
|
|
523
|
-
|
|
524
|
-
|
|
531
|
+
let parsed = {};
|
|
532
|
+
try {
|
|
533
|
+
parsed = typeof args === 'string' ? JSON.parse(args) : args ?? {};
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
parsed = {};
|
|
537
|
+
}
|
|
538
|
+
const isCommand = toolName === 'run_command';
|
|
539
|
+
const isWrite = toolName === 'write_file' || toolName === 'edit_file' || toolName === 'notebook_edit';
|
|
540
|
+
const isGit = toolName === 'git_commit';
|
|
541
|
+
const highlight = parsed.command ?? parsed.filePath ?? parsed.file_path ?? parsed.notebook_path ?? parsed.message ?? null;
|
|
542
|
+
const highlightLabel = isCommand ? 'Command' : isGit ? 'Message' : isWrite ? 'File' : 'Args';
|
|
543
|
+
const highlightStr = highlight ? String(highlight) : null;
|
|
544
|
+
const extraFields = Object.entries(parsed).filter(([k]) => !['command', 'filePath', 'file_path', 'notebook_path', 'message', 'content'].includes(k));
|
|
545
|
+
const boxWidth = Math.min(cols - 2, 80);
|
|
546
|
+
const border = '\u2500'.repeat(boxWidth - 2);
|
|
547
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { color: "yellow", children: ['\u250C', border, '\u2510'] }), _jsxs(Text, { color: "yellow", children: ['\u2502', " ", _jsxs(Text, { color: "yellow", bold: true, children: ['\u26A0', " Permission Required"] }), _jsx(Text, { children: ' '.repeat(Math.max(0, boxWidth - 24)) }), '\u2502'] }), _jsxs(Text, { color: "yellow", children: ['\u2502', " ", _jsx(Text, { dimColor: true, children: "Tool:" }), " ", _jsx(Text, { color: "cyan", bold: true, children: toolName }), _jsx(Text, { children: ' '.repeat(Math.max(0, boxWidth - 9 - toolName.length)) }), '\u2502'] }), highlightStr && (_jsxs(Text, { color: "yellow", children: ['\u2502', " ", _jsxs(Text, { dimColor: true, children: [highlightLabel, ":"] }), " ", _jsx(Text, { color: "white", bold: true, children: truncate(highlightStr, boxWidth - highlightLabel.length - 6) }), _jsx(Text, { children: ' '.repeat(Math.max(0, boxWidth - highlightLabel.length - 4 - Math.min(highlightStr.length, boxWidth - highlightLabel.length - 6))) }), '\u2502'] })), extraFields.slice(0, 3).map(([k, v]) => {
|
|
548
|
+
const val = truncate(String(v), boxWidth - k.length - 6);
|
|
549
|
+
return (_jsxs(Text, { color: "yellow", children: ['\u2502', " ", _jsxs(Text, { dimColor: true, children: [k, ":"] }), " ", _jsx(Text, { children: val }), _jsx(Text, { children: ' '.repeat(Math.max(0, boxWidth - k.length - 4 - val.length)) }), '\u2502'] }, k));
|
|
550
|
+
}), _jsxs(Text, { color: "yellow", children: ['\u251C', border, '\u2524'] }), _jsxs(Text, { color: "yellow", children: ['\u2502', " ", _jsx(Text, { color: "green", bold: true, children: "[Y]" }), _jsx(Text, { children: " Allow " }), _jsx(Text, { color: "red", bold: true, children: "[N]" }), _jsx(Text, { children: " Deny " }), _jsx(Text, { dimColor: true, children: "ESC to deny" }), _jsx(Text, { children: ' '.repeat(Math.max(0, boxWidth - 33)) }), '\u2502'] }), _jsxs(Text, { color: "yellow", children: ['\u2514', border, '\u2518'] })] }));
|
|
525
551
|
}
|
|
526
552
|
function ModelPicker({ currentModels, onSelect, onCancel }) {
|
|
527
553
|
const [customInput, setCustomInput] = useState('');
|
|
@@ -607,7 +633,7 @@ function SettingsEditor({ onSave, onClose }) {
|
|
|
607
633
|
const [selectedKey, setSelectedKey] = useState(null);
|
|
608
634
|
const [editValue, setEditValue] = useState('');
|
|
609
635
|
const [currentConfig, setCurrentConfig] = useState(null);
|
|
610
|
-
const reload = useCallback(() => { loadConfig().then(setCurrentConfig).catch(() => { }); }, []);
|
|
636
|
+
const reload = useCallback(() => { loadConfig().then(setCurrentConfig).catch((err) => { logger.warn('Failed to load config', { error: err.message }); }); }, []);
|
|
611
637
|
useEffect(() => { reload(); }, [reload]);
|
|
612
638
|
useInput((_ch, key) => {
|
|
613
639
|
if (key.escape) {
|
|
@@ -898,6 +924,7 @@ const InputSection = memo(function InputSection({ onSubmit, disabled, onAbort, o
|
|
|
898
924
|
{ cmd: '/hooks', desc: 'Show lifecycle hooks' },
|
|
899
925
|
{ cmd: '/commands', desc: 'Show custom commands' },
|
|
900
926
|
{ cmd: '/delegate', desc: 'Run task with sub-agent' },
|
|
927
|
+
{ cmd: '/agents', desc: 'Show active agents & usage' },
|
|
901
928
|
{ cmd: '/checkpoint', desc: 'Create git checkpoint' },
|
|
902
929
|
{ cmd: '/undo', desc: 'Undo last checkpoint' },
|
|
903
930
|
{ cmd: '/checkpoints', desc: 'List recent checkpoints' },
|
|
@@ -936,7 +963,7 @@ const InputSection = memo(function InputSection({ onSubmit, disabled, onAbort, o
|
|
|
936
963
|
onChange('');
|
|
937
964
|
handleSubmit(item.value);
|
|
938
965
|
}
|
|
939
|
-
}, limit: 10 }), _jsxs(Text, { dimColor: true, italic: true, children: ['↑↓', " navigate ", '•', " Enter select ", '•', " keep typing to filter"] })] })), stash.length > 0 && !disabled && (_jsxs(Box, { paddingX: 3, children: [_jsxs(Text, { color: "magenta", dimColor: true, children: ['⚑', " Stashed: ", truncate(stash, 40), " "] }), _jsx(Text, { dimColor: true, children: "(Ctrl+S to restore)" })] })), _jsx(Box, { paddingX: 2, children:
|
|
966
|
+
}, limit: 10 }), _jsxs(Text, { dimColor: true, italic: true, children: ['↑↓', " navigate ", '•', " Enter select ", '•', " keep typing to filter"] })] })), stash.length > 0 && !disabled && (_jsxs(Box, { paddingX: 3, children: [_jsxs(Text, { color: "magenta", dimColor: true, children: ['⚑', " Stashed: ", truncate(stash, 40), " "] }), _jsx(Text, { dimColor: true, children: "(Ctrl+S to restore)" })] })), _jsx(Box, { paddingX: 2, children: _jsxs(Box, { flexGrow: 1, children: [_jsx(Box, { flexShrink: 0, width: 2, children: disabled ? (_jsx(Text, { color: "magentaBright", children: _jsx(Spinner, { type: "dots" }) })) : (_jsx(Text, { color: "magentaBright", bold: true, children: '❯' })) }), _jsx(Box, { flexGrow: 1, children: _jsx(TextInput, { value: value, onChange: onChange, onSubmit: handleSubmit, placeholder: disabled ? 'Queue a message... (sent after current task)' : 'Type a message...', showCursor: true, focus: !pickerActive }) }), disabled && (_jsx(Box, { flexShrink: 0, children: _jsx(Text, { dimColor: true, children: " ^C interrupt" }) }))] }) })] }));
|
|
940
967
|
});
|
|
941
968
|
// ─── App ───
|
|
942
969
|
function estimateLines(item, cols) {
|
|
@@ -1031,6 +1058,7 @@ function App({ config }) {
|
|
|
1031
1058
|
const [currentMode, setCurrentMode] = useState(agent?.getPermissionMode() ?? 'default');
|
|
1032
1059
|
const [selectingMode, setSelectingMode] = useState(false);
|
|
1033
1060
|
const [editingSettings, setEditingSettings] = useState(false);
|
|
1061
|
+
const [agentVersion, setAgentVersion] = useState(0);
|
|
1034
1062
|
const counter = useRef(0);
|
|
1035
1063
|
const showBannerRef = useRef(true);
|
|
1036
1064
|
const modelsRef = useRef(models);
|
|
@@ -1235,7 +1263,7 @@ function App({ config }) {
|
|
|
1235
1263
|
currentAgent.off('context:compress', onContextCompress);
|
|
1236
1264
|
currentAgent.off('context:usage', onContextUsage);
|
|
1237
1265
|
};
|
|
1238
|
-
}, [add]);
|
|
1266
|
+
}, [add, agentVersion]);
|
|
1239
1267
|
// Confirmation bridge (event-driven)
|
|
1240
1268
|
useEffect(() => {
|
|
1241
1269
|
const onPending = () => {
|
|
@@ -1246,6 +1274,11 @@ function App({ config }) {
|
|
|
1246
1274
|
confirmEmitter.on('pending', onPending);
|
|
1247
1275
|
return () => { confirmEmitter.off('pending', onPending); };
|
|
1248
1276
|
}, [confirmInfo]);
|
|
1277
|
+
useEffect(() => {
|
|
1278
|
+
const banner = getUpdateBanner();
|
|
1279
|
+
if (banner)
|
|
1280
|
+
add('system', banner);
|
|
1281
|
+
}, [add]);
|
|
1249
1282
|
const handleConfirm = useCallback((ok) => {
|
|
1250
1283
|
if (pendingConfirm) {
|
|
1251
1284
|
pendingConfirm.resolve(ok);
|
|
@@ -1295,10 +1328,12 @@ function App({ config }) {
|
|
|
1295
1328
|
' /watch <cmd> \u2014 run command, auto-fix on failure',
|
|
1296
1329
|
' /create <d> \u2014 scaffold a new project',
|
|
1297
1330
|
' /delegate <t> \u2014 run task with sub-agent',
|
|
1331
|
+
' /agents \u2014 show active agents & usage',
|
|
1298
1332
|
'',
|
|
1299
1333
|
'Tools & Config:',
|
|
1300
1334
|
' /copy \u2014 copy last response to clipboard',
|
|
1301
1335
|
' /status \u2014 show version, model & connection info',
|
|
1336
|
+
' /update \u2014 check for and install updates',
|
|
1302
1337
|
' /doctor \u2014 diagnose installation',
|
|
1303
1338
|
' /settings \u2014 view & configure limits',
|
|
1304
1339
|
' /setup \u2014 reconfigure API key, URL & models',
|
|
@@ -1331,7 +1366,7 @@ function App({ config }) {
|
|
|
1331
1366
|
if (msgs.length > 0) {
|
|
1332
1367
|
saveSession(sessionId, msgs, models[0] || 'unknown', undefined, costTracker.getState()).then((meta) => {
|
|
1333
1368
|
add('system', `Auto-saved: ${meta.name}`);
|
|
1334
|
-
}).catch(() => { });
|
|
1369
|
+
}).catch((err) => { logger.warn('Failed to auto-save session', { error: err.message }); });
|
|
1335
1370
|
}
|
|
1336
1371
|
agent.clearHistory();
|
|
1337
1372
|
setItems([]);
|
|
@@ -1449,7 +1484,7 @@ function App({ config }) {
|
|
|
1449
1484
|
loadConfig().then(cfg => {
|
|
1450
1485
|
cfg.permissionMode = arg;
|
|
1451
1486
|
saveConfig(cfg);
|
|
1452
|
-
}).catch(() => { });
|
|
1487
|
+
}).catch((err) => { logger.warn('Failed to save config', { error: err.message }); });
|
|
1453
1488
|
add('system', `Mode: ${arg.toUpperCase()} \u2014 ${getModeDescription(arg)}`);
|
|
1454
1489
|
}
|
|
1455
1490
|
else {
|
|
@@ -1464,7 +1499,7 @@ function App({ config }) {
|
|
|
1464
1499
|
loadConfig().then(cfg => {
|
|
1465
1500
|
cfg.thinkingMode = arg;
|
|
1466
1501
|
saveConfig(cfg);
|
|
1467
|
-
}).catch(() => { });
|
|
1502
|
+
}).catch((err) => { logger.warn('Failed to save config', { error: err.message }); });
|
|
1468
1503
|
add('system', `Thinking: ${arg.toUpperCase()}${arg === 'off' ? '' : ' \u2014 model will reason before responding'}`);
|
|
1469
1504
|
}
|
|
1470
1505
|
else {
|
|
@@ -1510,14 +1545,15 @@ function App({ config }) {
|
|
|
1510
1545
|
return true;
|
|
1511
1546
|
}
|
|
1512
1547
|
if (cmd === '/git') {
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1548
|
+
collectGitInfo(process.cwd()).then((gitInfo) => {
|
|
1549
|
+
if (!gitInfo.isRepo) {
|
|
1550
|
+
add('system', 'Not a git repository.');
|
|
1551
|
+
}
|
|
1552
|
+
else {
|
|
1553
|
+
const lines = gitInfoToContext(gitInfo).split('\n');
|
|
1554
|
+
lines.forEach((l) => add('system', l));
|
|
1555
|
+
}
|
|
1556
|
+
}).catch(() => { add('system', 'Failed to collect git info.'); });
|
|
1521
1557
|
return true;
|
|
1522
1558
|
}
|
|
1523
1559
|
if (cmd === '/plugins') {
|
|
@@ -1597,6 +1633,23 @@ function App({ config }) {
|
|
|
1597
1633
|
});
|
|
1598
1634
|
return true;
|
|
1599
1635
|
}
|
|
1636
|
+
if (cmd === '/agents') {
|
|
1637
|
+
const registry = AgentRegistry.getInstance();
|
|
1638
|
+
const summary = registry.getUsageSummary();
|
|
1639
|
+
const tree = registry.getAgentTree();
|
|
1640
|
+
const lines = [
|
|
1641
|
+
`Active: ${summary.activeCount} | Completed: ${summary.completedCount} | Failed: ${summary.failedCount}`,
|
|
1642
|
+
`Total tokens: ${summary.totalTokens.toLocaleString()}`,
|
|
1643
|
+
];
|
|
1644
|
+
if (tree && tree !== '(no agents)') {
|
|
1645
|
+
lines.push('', 'Agent tree:', tree);
|
|
1646
|
+
}
|
|
1647
|
+
else {
|
|
1648
|
+
lines.push('', 'No agents have been spawned yet.');
|
|
1649
|
+
}
|
|
1650
|
+
lines.forEach(l => add('system', l));
|
|
1651
|
+
return true;
|
|
1652
|
+
}
|
|
1600
1653
|
if (cmd === '/checkpoint') {
|
|
1601
1654
|
const label = text.slice(12).trim() || undefined;
|
|
1602
1655
|
createCheckpoint(process.cwd(), label).then((hash) => {
|
|
@@ -1871,6 +1924,32 @@ function App({ config }) {
|
|
|
1871
1924
|
add('system', `Messages: ${msgCount}`);
|
|
1872
1925
|
add('system', `Session: ${sessionId}`);
|
|
1873
1926
|
add('system', `CWD: ${process.cwd()}`);
|
|
1927
|
+
const updateBanner = getUpdateBanner();
|
|
1928
|
+
if (updateBanner)
|
|
1929
|
+
add('system', updateBanner);
|
|
1930
|
+
return true;
|
|
1931
|
+
}
|
|
1932
|
+
if (cmd === '/update') {
|
|
1933
|
+
add('system', 'Checking for updates...');
|
|
1934
|
+
checkForUpdate().then((info) => {
|
|
1935
|
+
if (info && info.updateAvailable) {
|
|
1936
|
+
add('system', `New version available: ${info.current} → ${info.latest}`);
|
|
1937
|
+
add('system', 'Installing update...');
|
|
1938
|
+
performUpdate().then(({ success, output }) => {
|
|
1939
|
+
if (success) {
|
|
1940
|
+
add('system', `Updated successfully. Restart Scalix to use v${info.latest}.`);
|
|
1941
|
+
}
|
|
1942
|
+
else {
|
|
1943
|
+
add('system', `Update failed: ${output}\nTry manually: npm install -g scalix@latest`);
|
|
1944
|
+
}
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1947
|
+
else {
|
|
1948
|
+
add('system', `Scalix v${VERSION} is up to date.`);
|
|
1949
|
+
}
|
|
1950
|
+
}).catch(() => {
|
|
1951
|
+
add('system', 'Failed to check for updates. Try: npm install -g scalix@latest');
|
|
1952
|
+
});
|
|
1874
1953
|
return true;
|
|
1875
1954
|
}
|
|
1876
1955
|
if (cmd === '/context') {
|
|
@@ -1919,7 +1998,7 @@ function App({ config }) {
|
|
|
1919
1998
|
if (cmd === '/exit' || cmd === '/quit') {
|
|
1920
1999
|
const msgs = agent.getMessages();
|
|
1921
2000
|
if (msgs.length > 0) {
|
|
1922
|
-
saveSession(sessionId, msgs, models[0] || 'unknown', undefined, costTracker.getState()).catch(() => { }).finally(() => process.exit(0));
|
|
2001
|
+
saveSession(sessionId, msgs, models[0] || 'unknown', undefined, costTracker.getState()).catch((err) => { logger.warn('Failed to save session on exit', { error: err.message }); }).finally(() => process.exit(0));
|
|
1923
2002
|
}
|
|
1924
2003
|
else {
|
|
1925
2004
|
process.exit(0);
|
|
@@ -1932,7 +2011,7 @@ function App({ config }) {
|
|
|
1932
2011
|
loadConfig().then(cfg => {
|
|
1933
2012
|
cfg.permissionMode = 'plan';
|
|
1934
2013
|
saveConfig(cfg);
|
|
1935
|
-
}).catch(() => { });
|
|
2014
|
+
}).catch((err) => { logger.warn('Failed to save config', { error: err.message }); });
|
|
1936
2015
|
add('system', `Mode: PLAN — ${getModeDescription('plan')}`);
|
|
1937
2016
|
return true;
|
|
1938
2017
|
}
|
|
@@ -2016,10 +2095,22 @@ function App({ config }) {
|
|
|
2016
2095
|
const isLoadingRef = useRef(false);
|
|
2017
2096
|
useEffect(() => { isLoadingRef.current = isLoading; }, [isLoading]);
|
|
2018
2097
|
const multilineRef = useRef([]);
|
|
2098
|
+
const messageQueueRef = useRef([]);
|
|
2019
2099
|
const handleSubmit = useCallback(async (text) => {
|
|
2020
2100
|
const trimmed = text.trim();
|
|
2021
|
-
if (!trimmed
|
|
2101
|
+
if (!trimmed)
|
|
2102
|
+
return;
|
|
2103
|
+
// Allow /mode even while the agent is working
|
|
2104
|
+
if (isLoadingRef.current && trimmed.startsWith('/mode')) {
|
|
2105
|
+
handleCommand(trimmed);
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
// Queue messages while agent is working
|
|
2109
|
+
if (isLoadingRef.current) {
|
|
2110
|
+
messageQueueRef.current.push(trimmed);
|
|
2111
|
+
add('system', `Queued (${messageQueueRef.current.length}): ${truncate(trimmed, 60)}`);
|
|
2022
2112
|
return;
|
|
2113
|
+
}
|
|
2023
2114
|
if (trimmed.endsWith('\\')) {
|
|
2024
2115
|
if (multilineRef.current.length >= 50) {
|
|
2025
2116
|
add('system', 'Multiline limit reached (50 lines). Sending now.');
|
|
@@ -2062,7 +2153,7 @@ function App({ config }) {
|
|
|
2062
2153
|
}
|
|
2063
2154
|
const correction = detectCorrection(finalText);
|
|
2064
2155
|
if (correction) {
|
|
2065
|
-
addNote(process.cwd(), `[correction] ${correction}`).catch(() => { });
|
|
2156
|
+
addNote(process.cwd(), `[correction] ${correction}`).catch((err) => { logger.warn('Failed to save correction note', { error: err.message }); });
|
|
2066
2157
|
}
|
|
2067
2158
|
add('user', finalText);
|
|
2068
2159
|
setIsLoading(true);
|
|
@@ -2078,6 +2169,24 @@ function App({ config }) {
|
|
|
2078
2169
|
add('error', e.message || 'Unknown error');
|
|
2079
2170
|
}
|
|
2080
2171
|
finally {
|
|
2172
|
+
// Process queued messages sequentially
|
|
2173
|
+
while (messageQueueRef.current.length > 0) {
|
|
2174
|
+
const next = messageQueueRef.current.shift();
|
|
2175
|
+
const remaining = messageQueueRef.current.length;
|
|
2176
|
+
if (remaining > 0)
|
|
2177
|
+
add('system', `${remaining} more queued`);
|
|
2178
|
+
add('user', next);
|
|
2179
|
+
setStatus('thinking');
|
|
2180
|
+
setThinkStart(Date.now());
|
|
2181
|
+
try {
|
|
2182
|
+
const qResult = await agent.send(next);
|
|
2183
|
+
if (!qResult || !qResult.trim())
|
|
2184
|
+
add('system', 'Agent returned no response.');
|
|
2185
|
+
}
|
|
2186
|
+
catch (e) {
|
|
2187
|
+
add('error', e.message || 'Unknown error');
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2081
2190
|
setIsLoading(false);
|
|
2082
2191
|
setStatus('ready');
|
|
2083
2192
|
setThinkStart(null);
|
|
@@ -2087,6 +2196,7 @@ function App({ config }) {
|
|
|
2087
2196
|
setSessionList(null);
|
|
2088
2197
|
setIsLoading(true);
|
|
2089
2198
|
setStatus('thinking');
|
|
2199
|
+
setThinkStart(Date.now());
|
|
2090
2200
|
loadSession(session.id).then((data) => {
|
|
2091
2201
|
agent.loadMessages(data.messages);
|
|
2092
2202
|
sessionId = session.id;
|
|
@@ -2112,6 +2222,7 @@ function App({ config }) {
|
|
|
2112
2222
|
}).finally(() => {
|
|
2113
2223
|
setIsLoading(false);
|
|
2114
2224
|
setStatus('ready');
|
|
2225
|
+
setThinkStart(null);
|
|
2115
2226
|
});
|
|
2116
2227
|
}, [add]);
|
|
2117
2228
|
const handleAbort = useCallback(() => {
|
|
@@ -2131,7 +2242,7 @@ function App({ config }) {
|
|
|
2131
2242
|
const handleClearScreen = useCallback(() => {
|
|
2132
2243
|
const msgs = agent.getMessages();
|
|
2133
2244
|
if (msgs.length > 0) {
|
|
2134
|
-
saveSession(sessionId, msgs, models[0] || 'unknown', undefined, costTracker.getState()).catch(() => { });
|
|
2245
|
+
saveSession(sessionId, msgs, models[0] || 'unknown', undefined, costTracker.getState()).catch((err) => { logger.warn('Failed to save session on clear', { error: err.message }); });
|
|
2135
2246
|
}
|
|
2136
2247
|
agent.clearHistory();
|
|
2137
2248
|
setItems([]);
|
|
@@ -2146,6 +2257,7 @@ function App({ config }) {
|
|
|
2146
2257
|
try {
|
|
2147
2258
|
initAgent(newConfig);
|
|
2148
2259
|
setModels(agent.getModels());
|
|
2260
|
+
setAgentVersion(v => v + 1);
|
|
2149
2261
|
add('system', 'Configuration updated. Agent reinitialized.');
|
|
2150
2262
|
}
|
|
2151
2263
|
catch (err) {
|
|
@@ -2157,8 +2269,18 @@ function App({ config }) {
|
|
|
2157
2269
|
const bannerHeight = showBanner ? 5 : 0;
|
|
2158
2270
|
const statusBarHeight = 1;
|
|
2159
2271
|
const bottomHeight = useMemo(() => {
|
|
2160
|
-
if (confirmInfo)
|
|
2161
|
-
|
|
2272
|
+
if (confirmInfo) {
|
|
2273
|
+
let parsed = {};
|
|
2274
|
+
try {
|
|
2275
|
+
parsed = typeof confirmInfo.args === 'string' ? JSON.parse(confirmInfo.args) : confirmInfo.args ?? {};
|
|
2276
|
+
}
|
|
2277
|
+
catch {
|
|
2278
|
+
parsed = {};
|
|
2279
|
+
}
|
|
2280
|
+
const extraCount = Math.min(Object.keys(parsed).filter(k => !['command', 'filePath', 'file_path', 'notebook_path', 'message', 'content'].includes(k)).length, 3);
|
|
2281
|
+
const hasHighlight = !!(parsed.command ?? parsed.filePath ?? parsed.file_path ?? parsed.notebook_path ?? parsed.message);
|
|
2282
|
+
return 5 + (hasHighlight ? 1 : 0) + extraCount;
|
|
2283
|
+
}
|
|
2162
2284
|
if (selectingModel)
|
|
2163
2285
|
return 6 + Math.min(models.length + 2, 12);
|
|
2164
2286
|
if (selectingMode)
|
|
@@ -2197,7 +2319,7 @@ function App({ config }) {
|
|
|
2197
2319
|
loadConfig().then(cfg => {
|
|
2198
2320
|
cfg.models = [v];
|
|
2199
2321
|
saveConfig(cfg);
|
|
2200
|
-
}).catch(() => { });
|
|
2322
|
+
}).catch((err) => { logger.warn('Failed to save config', { error: err.message }); });
|
|
2201
2323
|
add('system', `Model switched to ${shortModel(v)}`);
|
|
2202
2324
|
}, onCancel: () => setSelectingModel(false) })) : selectingMode ? (_jsx(ModePicker, { currentMode: currentMode, onSelect: (mode) => {
|
|
2203
2325
|
agent.setPermissionMode(mode);
|
|
@@ -2206,7 +2328,7 @@ function App({ config }) {
|
|
|
2206
2328
|
loadConfig().then(cfg => {
|
|
2207
2329
|
cfg.permissionMode = mode;
|
|
2208
2330
|
saveConfig(cfg);
|
|
2209
|
-
}).catch(() => { });
|
|
2331
|
+
}).catch((err) => { logger.warn('Failed to save config', { error: err.message }); });
|
|
2210
2332
|
add('system', `Mode: ${mode.toUpperCase()} \u2014 ${getModeDescription(mode)}`);
|
|
2211
2333
|
}, onCancel: () => setSelectingMode(false) })) : editingSettings ? (_jsx(SettingsEditor, { onSave: (key, value) => {
|
|
2212
2334
|
loadConfig().then((c) => {
|
|
@@ -2247,7 +2369,10 @@ function Root() {
|
|
|
2247
2369
|
setConfig(c);
|
|
2248
2370
|
if (isConfigured(c)) {
|
|
2249
2371
|
// Fetch model specs from API in background (uses user-defined/built-in as fallback)
|
|
2250
|
-
fetchModelSpecs(c.baseUrl, c.apiKey).catch(() => { });
|
|
2372
|
+
fetchModelSpecs(c.baseUrl, c.apiKey).catch((err) => { logger.warn('Failed to fetch model specs', { error: err.message }); });
|
|
2373
|
+
if (!process.env.NO_UPDATE_NOTIFIER && !c.disableUpdateCheck) {
|
|
2374
|
+
checkForUpdate().catch(() => { });
|
|
2375
|
+
}
|
|
2251
2376
|
try {
|
|
2252
2377
|
initAgent(c);
|
|
2253
2378
|
setReady(true);
|
|
@@ -2257,7 +2382,7 @@ function Root() {
|
|
|
2257
2382
|
}
|
|
2258
2383
|
}
|
|
2259
2384
|
}).catch(() => {
|
|
2260
|
-
setConfig({ apiKey: '', baseUrl: DEFAULT_BASE_URL, models: [...DEFAULT_MODELS], customModels: {}, limits: { ...DEFAULT_LIMITS }, permissionMode: 'default', thinkingMode: 'off' });
|
|
2385
|
+
setConfig({ apiKey: '', baseUrl: DEFAULT_BASE_URL, models: [...DEFAULT_MODELS], customModels: {}, limits: { ...DEFAULT_LIMITS }, permissionMode: 'default', thinkingMode: 'off', security: {}, injectionDetection: { enabled: true, sensitivity: 'medium' } });
|
|
2261
2386
|
});
|
|
2262
2387
|
}, []);
|
|
2263
2388
|
const handleSetupComplete = useCallback((c) => {
|
|
@@ -2324,6 +2449,6 @@ function _restoreTerminal() {
|
|
|
2324
2449
|
_origWrite('\x1b[?25h'); // show cursor
|
|
2325
2450
|
}
|
|
2326
2451
|
process.on('exit', _restoreTerminal);
|
|
2327
|
-
process.on('SIGINT', () => { _restoreTerminal(); process.exit(0); });
|
|
2328
|
-
process.on('SIGTERM', () => { _restoreTerminal(); process.exit(0); });
|
|
2452
|
+
process.on('SIGINT', () => { _restoreTerminal(); closeBrowser().finally(() => process.exit(0)); });
|
|
2453
|
+
process.on('SIGTERM', () => { _restoreTerminal(); closeBrowser().finally(() => process.exit(0)); });
|
|
2329
2454
|
render(_jsx(Root, {}), { exitOnCtrlC: false, patchConsole: false });
|
package/dist/constants.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const VERSION = '1.
|
|
1
|
+
export const VERSION = '1.1.0';
|
|
2
2
|
export const PRODUCT_NAME = 'Scalix-Coder';
|
|
3
3
|
export const USER_AGENT = `${PRODUCT_NAME}/${VERSION}`;
|
|
4
4
|
export const DEFAULT_BASE_URL = process.env.SCALIX_BASE_URL || 'https://api.scalix.world/v1';
|
|
@@ -199,10 +199,16 @@ export const TOOL_DEFAULTS = {
|
|
|
199
199
|
export const SUB_AGENT_DEFAULTS = {
|
|
200
200
|
timeoutMs: 600_000,
|
|
201
201
|
agentToolTimeoutMs: 600_000,
|
|
202
|
-
maxSteps:
|
|
203
|
-
exploreMaxSteps:
|
|
204
|
-
maxContextTokens:
|
|
205
|
-
compressionThreshold: 0.
|
|
202
|
+
maxSteps: 500,
|
|
203
|
+
exploreMaxSteps: 200,
|
|
204
|
+
maxContextTokens: 1_000_000,
|
|
205
|
+
compressionThreshold: 0.80,
|
|
206
|
+
};
|
|
207
|
+
// ─── Multi-Agent Defaults ───
|
|
208
|
+
export const MULTI_AGENT_DEFAULTS = {
|
|
209
|
+
maxConcurrentAgents: 10,
|
|
210
|
+
maxDepth: 5,
|
|
211
|
+
globalTokenBudget: 5_000_000,
|
|
206
212
|
};
|
|
207
213
|
// ─── Headless Defaults ───
|
|
208
214
|
export const HEADLESS_DEFAULTS = {
|
|
@@ -263,16 +269,37 @@ export const TOKEN_ESTIMATION = {
|
|
|
263
269
|
simpleCharsPerToken: 4,
|
|
264
270
|
};
|
|
265
271
|
// ─── Network Security ───
|
|
266
|
-
export const PRIVATE_NETWORK_HOSTNAMES = new Set([
|
|
272
|
+
export const PRIVATE_NETWORK_HOSTNAMES = new Set([
|
|
273
|
+
'localhost', '127.0.0.1', '::1', '[::1]', '0.0.0.0',
|
|
274
|
+
'::ffff:127.0.0.1', '[::ffff:127.0.0.1]',
|
|
275
|
+
]);
|
|
267
276
|
export const PRIVATE_NETWORK_SUFFIXES = ['.local', '.internal'];
|
|
268
277
|
export const PRIVATE_NETWORK_CIDRS = [
|
|
269
278
|
/^10\./,
|
|
270
279
|
/^172\.(1[6-9]|2\d|3[01])\./,
|
|
271
280
|
/^192\.168\./,
|
|
272
281
|
/^169\.254\./,
|
|
282
|
+
/^\[?fe80:/i,
|
|
283
|
+
/^\[?fc00:/i,
|
|
284
|
+
/^\[?fd[0-9a-f]{2}:/i,
|
|
285
|
+
/^\[?::ffff:(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|127\.)/i,
|
|
273
286
|
];
|
|
287
|
+
let _networkOverrides = {};
|
|
288
|
+
export function setNetworkSecurityOverrides(overrides) {
|
|
289
|
+
_networkOverrides = {
|
|
290
|
+
allowAll: overrides.allowAll,
|
|
291
|
+
blockedHosts: overrides.blockedHosts ? new Set(overrides.blockedHosts.map(h => h.toLowerCase())) : undefined,
|
|
292
|
+
allowedHosts: overrides.allowedHosts ? new Set(overrides.allowedHosts.map(h => h.toLowerCase())) : undefined,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
274
295
|
export function isPrivateHost(hostname) {
|
|
275
296
|
const lower = hostname.toLowerCase();
|
|
297
|
+
if (_networkOverrides.allowAll)
|
|
298
|
+
return false;
|
|
299
|
+
if (_networkOverrides.allowedHosts?.has(lower))
|
|
300
|
+
return false;
|
|
301
|
+
if (_networkOverrides.blockedHosts?.has(lower))
|
|
302
|
+
return true;
|
|
276
303
|
if (PRIVATE_NETWORK_HOSTNAMES.has(lower))
|
|
277
304
|
return true;
|
|
278
305
|
if (PRIVATE_NETWORK_SUFFIXES.some(s => lower.endsWith(s)))
|
|
@@ -298,6 +325,13 @@ export const COMPRESSION_DEFAULTS = {
|
|
|
298
325
|
keepRatio: 0.4,
|
|
299
326
|
truncateDisplay: 1000,
|
|
300
327
|
};
|
|
328
|
+
// ─── Update Check Defaults ───
|
|
329
|
+
export const UPDATE_DEFAULTS = {
|
|
330
|
+
checkIntervalMs: 24 * 60 * 60 * 1000,
|
|
331
|
+
registryUrl: 'https://registry.npmjs.org/scalix/latest',
|
|
332
|
+
fetchTimeoutMs: 5_000,
|
|
333
|
+
npmPackageName: 'scalix',
|
|
334
|
+
};
|
|
301
335
|
// ─── Complexity Patterns (for model routing) ───
|
|
302
336
|
export const COMPLEXITY_PATTERNS = {
|
|
303
337
|
simple: [
|
package/dist/context-builder.js
CHANGED
|
@@ -4,6 +4,7 @@ import { collectGitInfo, gitInfoToContext } from './git-context.js';
|
|
|
4
4
|
import { loadMemory, memoryToContext } from './project-memory.js';
|
|
5
5
|
import { buildProjectIndex, indexToContext } from './project-index.js';
|
|
6
6
|
import { loadScopedRules, rulesToContext } from './scoped-rules.js';
|
|
7
|
+
import { retrieveContext, snippetsToContext, RETRIEVAL_DEFAULTS } from './context-retriever.js';
|
|
7
8
|
async function readJsonSafe(filePath) {
|
|
8
9
|
try {
|
|
9
10
|
return JSON.parse(await fs.readFile(filePath, 'utf-8'));
|
|
@@ -102,7 +103,7 @@ export async function buildProjectContext(cwd) {
|
|
|
102
103
|
const projectType = await detectProjectType(cwd);
|
|
103
104
|
// Gather context in parallel
|
|
104
105
|
const [gitCtx, memory, rules, projectConfig] = await Promise.all([
|
|
105
|
-
|
|
106
|
+
collectGitInfo(cwd).then(gitInfoToContext).catch(() => ''),
|
|
106
107
|
loadMemory(cwd).then(memoryToContext).catch(() => ''),
|
|
107
108
|
loadScopedRules(cwd).then(rulesToContext).catch(() => ''),
|
|
108
109
|
loadProjectConfig(cwd),
|
|
@@ -149,3 +150,9 @@ export function contextToInstructions(baseInstructions, ctx) {
|
|
|
149
150
|
}
|
|
150
151
|
return sections.filter(Boolean).join('\n\n');
|
|
151
152
|
}
|
|
153
|
+
export async function buildQueryContext(query, cwd, config) {
|
|
154
|
+
const index = await buildProjectIndex(cwd);
|
|
155
|
+
const effectiveConfig = config ? { ...RETRIEVAL_DEFAULTS, ...config } : RETRIEVAL_DEFAULTS;
|
|
156
|
+
const result = await retrieveContext(query, index, effectiveConfig);
|
|
157
|
+
return snippetsToContext(result.snippets);
|
|
158
|
+
}
|