scalix 1.0.0 → 1.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.
- package/.env.example +29 -0
- package/PARITY-PLAN.md +116 -0
- package/README.md +1 -1
- package/bin/scalix.js +1 -0
- package/dist/agent-definitions.js +100 -0
- package/dist/agent-registry.js +226 -0
- package/dist/agent.js +363 -74
- package/dist/api-client.js +77 -0
- package/dist/cli.js +724 -76
- package/dist/commands.js +53 -2
- package/dist/constants.js +43 -9
- 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 +149 -20
- package/dist/hooks.js +148 -10
- package/dist/injection-detector.js +86 -0
- package/dist/ipc-server.js +6 -2
- package/dist/keychain.js +109 -0
- package/dist/markdown.js +46 -3
- package/dist/plugins.js +132 -0
- package/dist/project-index.js +22 -1
- package/dist/sandbox.js +201 -11
- package/dist/sessions.js +96 -2
- package/dist/settings.js +194 -8
- package/dist/skills.js +269 -0
- package/dist/sub-agent.js +83 -8
- package/dist/task-board.js +105 -0
- package/dist/terminal-notify.js +73 -0
- package/dist/text-utils.js +146 -0
- package/dist/tools-advanced.js +304 -49
- package/dist/tools-browser.js +280 -19
- package/dist/tools-extra.js +9 -8
- 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 +8 -3
- package/app.py +0 -55
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
export const GROUNDING_DEFAULTS = {
|
|
2
|
+
enabled: true,
|
|
3
|
+
maxNudges: 1,
|
|
4
|
+
ungroundedThreshold: 0.5,
|
|
5
|
+
minClaimsToCheck: 2,
|
|
6
|
+
};
|
|
7
|
+
const TRIVIAL_FILES = new Set([
|
|
8
|
+
'README.md', 'LICENSE', 'package.json', 'tsconfig.json',
|
|
9
|
+
'Cargo.toml', 'go.mod', 'Makefile', 'Dockerfile',
|
|
10
|
+
'.gitignore', '.env', '.env.example',
|
|
11
|
+
]);
|
|
12
|
+
const FILE_PATH_RE = /(?:^|[\s`"'(])([.\w][\w./-]*\/[\w./-]+\.\w{1,10})(?=[\s`"'),.:;]|$)/gm;
|
|
13
|
+
const FUNC_CALL_RE = /\b([a-zA-Z_]\w{2,})\s*\(/g;
|
|
14
|
+
const FUNC_DESC_RE = /\b(?:function|method|def|fn|func|handler|callback)\s+([a-zA-Z_]\w{2,})\b/gi;
|
|
15
|
+
const CLASS_RE = /\b(?:class|interface|type|struct|enum|trait)\s+([A-Z]\w{2,})\b/g;
|
|
16
|
+
const LINE_NUM_RE = /\b(?:line\s+|:)(\d{2,5})\b/gi;
|
|
17
|
+
const IMPORT_RE = /(?:import|from|require)\s*\(?['"]([^'"]+)['"]\)?/g;
|
|
18
|
+
function surroundingContext(text, pos, radius = 60) {
|
|
19
|
+
const start = Math.max(0, pos - radius);
|
|
20
|
+
const end = Math.min(text.length, pos + radius);
|
|
21
|
+
return text.slice(start, end).replace(/\n/g, ' ').trim();
|
|
22
|
+
}
|
|
23
|
+
export function extractCodeClaims(text) {
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
const claims = [];
|
|
26
|
+
function add(type, value, position) {
|
|
27
|
+
const key = `${type}:${value}`;
|
|
28
|
+
if (seen.has(key))
|
|
29
|
+
return;
|
|
30
|
+
seen.add(key);
|
|
31
|
+
claims.push({ type, value, context: surroundingContext(text, position), position });
|
|
32
|
+
}
|
|
33
|
+
// File paths (must contain a / to avoid single-word matches)
|
|
34
|
+
for (const m of text.matchAll(FILE_PATH_RE)) {
|
|
35
|
+
const val = m[1];
|
|
36
|
+
const basename = val.split('/').pop() || val;
|
|
37
|
+
if (TRIVIAL_FILES.has(basename) || TRIVIAL_FILES.has(val))
|
|
38
|
+
continue;
|
|
39
|
+
add('file_path', val, m.index + m[0].indexOf(val));
|
|
40
|
+
}
|
|
41
|
+
// Function calls — foo()
|
|
42
|
+
for (const m of text.matchAll(FUNC_CALL_RE)) {
|
|
43
|
+
const name = m[1];
|
|
44
|
+
if (name.length > 50 || /^(if|for|while|switch|return|new|typeof|catch|throw|await|async|const|let|var|function|class|import|export|from|this|self|true|false|null|undefined|console|process|require|module)$/.test(name))
|
|
45
|
+
continue;
|
|
46
|
+
add('function_name', name, m.index);
|
|
47
|
+
}
|
|
48
|
+
// Function descriptions — "the handleSubmit function"
|
|
49
|
+
for (const m of text.matchAll(FUNC_DESC_RE)) {
|
|
50
|
+
add('function_name', m[1], m.index);
|
|
51
|
+
}
|
|
52
|
+
// Class/interface/type names
|
|
53
|
+
for (const m of text.matchAll(CLASS_RE)) {
|
|
54
|
+
if (m[1].length > 50)
|
|
55
|
+
continue;
|
|
56
|
+
add('class_name', m[1], m.index);
|
|
57
|
+
}
|
|
58
|
+
// Line numbers (only >9 to avoid trivial matches)
|
|
59
|
+
for (const m of text.matchAll(LINE_NUM_RE)) {
|
|
60
|
+
const num = parseInt(m[1], 10);
|
|
61
|
+
if (num < 10)
|
|
62
|
+
continue;
|
|
63
|
+
add('line_number', m[1], m.index);
|
|
64
|
+
}
|
|
65
|
+
// Import references
|
|
66
|
+
for (const m of text.matchAll(IMPORT_RE)) {
|
|
67
|
+
if (m[1].startsWith('.')) {
|
|
68
|
+
add('import', m[1], m.index);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return claims;
|
|
72
|
+
}
|
|
73
|
+
function buildGroundingCorpus(messages) {
|
|
74
|
+
const parts = [];
|
|
75
|
+
for (const m of messages) {
|
|
76
|
+
if (m.role === 'tool' && m.content) {
|
|
77
|
+
parts.push(typeof m.content === 'string' ? m.content : JSON.stringify(m.content));
|
|
78
|
+
}
|
|
79
|
+
if (m.role === 'assistant' && m.tool_calls) {
|
|
80
|
+
for (const tc of m.tool_calls) {
|
|
81
|
+
parts.push(tc.function.arguments);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return parts.join('\n');
|
|
86
|
+
}
|
|
87
|
+
export function checkGrounding(claims, messages, threshold = GROUNDING_DEFAULTS.ungroundedThreshold, minClaims = GROUNDING_DEFAULTS.minClaimsToCheck) {
|
|
88
|
+
if (claims.length === 0) {
|
|
89
|
+
return { claims, groundedClaims: [], ungroundedClaims: [], groundingRatio: 1, needsVerification: false };
|
|
90
|
+
}
|
|
91
|
+
const corpus = buildGroundingCorpus(messages);
|
|
92
|
+
const corpusLower = corpus.toLowerCase();
|
|
93
|
+
const grounded = [];
|
|
94
|
+
const ungrounded = [];
|
|
95
|
+
for (const claim of claims) {
|
|
96
|
+
let found = false;
|
|
97
|
+
switch (claim.type) {
|
|
98
|
+
case 'file_path': {
|
|
99
|
+
if (corpus.includes(claim.value)) {
|
|
100
|
+
found = true;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const basename = claim.value.split('/').pop();
|
|
104
|
+
if (corpus.includes(basename))
|
|
105
|
+
found = true;
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case 'function_name':
|
|
110
|
+
case 'class_name':
|
|
111
|
+
case 'variable_name': {
|
|
112
|
+
found = corpus.includes(claim.value);
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case 'line_number': {
|
|
116
|
+
const escaped = claim.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
117
|
+
const linePattern = new RegExp(`(?:^|\\n)\\s*${escaped}\\t|:${escaped}[:\\s]`, 'm');
|
|
118
|
+
found = linePattern.test(corpus);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case 'import': {
|
|
122
|
+
found = corpus.includes(claim.value);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
default:
|
|
126
|
+
found = corpusLower.includes(claim.value.toLowerCase());
|
|
127
|
+
}
|
|
128
|
+
if (found)
|
|
129
|
+
grounded.push(claim);
|
|
130
|
+
else
|
|
131
|
+
ungrounded.push(claim);
|
|
132
|
+
}
|
|
133
|
+
const ratio = grounded.length / claims.length;
|
|
134
|
+
const needsVerification = ratio < threshold && ungrounded.length >= minClaims;
|
|
135
|
+
return { claims, groundedClaims: grounded, ungroundedClaims: ungrounded, groundingRatio: ratio, needsVerification };
|
|
136
|
+
}
|
|
137
|
+
export function formatGroundingNudge(result) {
|
|
138
|
+
const items = result.ungroundedClaims.slice(0, 5).map((c) => {
|
|
139
|
+
switch (c.type) {
|
|
140
|
+
case 'file_path': return `- File "${c.value}" (not read in this conversation)`;
|
|
141
|
+
case 'function_name': return `- Function "${c.value}" (not seen in any tool output)`;
|
|
142
|
+
case 'class_name': return `- Type/class "${c.value}" (not seen in any tool output)`;
|
|
143
|
+
case 'line_number': return `- Line ${c.value} (not verified by reading the file)`;
|
|
144
|
+
case 'import': return `- Import "${c.value}" (not verified)`;
|
|
145
|
+
default: return `- "${c.value}" (not verified)`;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
return [
|
|
149
|
+
'[GROUNDING CHECK] Your response references code elements that were not verified by reading the actual source:',
|
|
150
|
+
...items,
|
|
151
|
+
'Please use read_file, search_files, or find_files to verify these references before presenting them as facts.',
|
|
152
|
+
'If you are uncertain about a path or name, say so explicitly rather than guessing.',
|
|
153
|
+
].join('\n');
|
|
154
|
+
}
|
package/dist/headless.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import readline from 'node:readline';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
3
4
|
import { execSync } from 'node:child_process';
|
|
4
5
|
import { createAgent } from './agent.js';
|
|
5
6
|
import { defaultTools } from './tools.js';
|
|
@@ -8,13 +9,14 @@ 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
|
-
import { browserTools } from './tools-browser.js';
|
|
12
|
+
import { browserTools, closeBrowser } from './tools-browser.js';
|
|
12
13
|
import { loadMcpServices } 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';
|
|
16
17
|
import { DEFAULT_MODELS, DEFAULT_INSTRUCTIONS, ENV_API_KEY, ENV_MODEL, ENV_BASE_URL, HEADLESS_DEFAULTS, fetchModelSpecs } from './constants.js';
|
|
17
18
|
import { loadConfig, DEFAULT_LIMITS } from './settings.js';
|
|
19
|
+
import { checkForUpdate, getUpdateBanner } from './update-checker.js';
|
|
18
20
|
import { getModeDescription, PERMISSION_MODES, setSandboxRoot } from './sandbox.js';
|
|
19
21
|
import { collectGitInfo, gitInfoToContext } from './git-context.js';
|
|
20
22
|
import { loadMemory, autoDetectConventions, saveMemory, memoryToContext, addNote, detectCorrection } from './project-memory.js';
|
|
@@ -26,15 +28,20 @@ import { hooksSummary } from './hooks.js';
|
|
|
26
28
|
import { spawnSubAgent, pickCheapestModel } from './sub-agent.js';
|
|
27
29
|
import { createCheckpoint, undoLastCheckpoint, listCheckpoints } from './git-undo.js';
|
|
28
30
|
import { runIpcServer } from './ipc-server.js';
|
|
29
|
-
|
|
31
|
+
import { AgentRegistry } from './agent-registry.js';
|
|
32
|
+
export function shortModel(m) {
|
|
30
33
|
return m.split('/').pop().replace(/:free$/, '').replace(/-instruct$/, '');
|
|
31
34
|
}
|
|
32
|
-
function parseArgs() {
|
|
35
|
+
export function parseArgs() {
|
|
33
36
|
const args = process.argv.slice(2);
|
|
34
37
|
let mode = 'interactive';
|
|
35
38
|
let prompt;
|
|
36
39
|
let output;
|
|
37
40
|
let format;
|
|
41
|
+
let permissionMode;
|
|
42
|
+
let outputSchema;
|
|
43
|
+
let worktree = false;
|
|
44
|
+
let worktreeName;
|
|
38
45
|
for (let i = 0; i < args.length; i++) {
|
|
39
46
|
if (args[i] === 'run' && i + 1 < args.length) {
|
|
40
47
|
mode = 'run';
|
|
@@ -49,12 +56,32 @@ function parseArgs() {
|
|
|
49
56
|
format = args[i + 1];
|
|
50
57
|
i++;
|
|
51
58
|
}
|
|
59
|
+
else if (args[i] === '--output-format' && i + 1 < args.length) {
|
|
60
|
+
format = args[i + 1];
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
else if (args[i] === '--output-schema' && i + 1 < args.length) {
|
|
64
|
+
outputSchema = args[i + 1];
|
|
65
|
+
format = 'json-schema';
|
|
66
|
+
i++;
|
|
67
|
+
}
|
|
68
|
+
else if (args[i] === '--mode' && i + 1 < args.length) {
|
|
69
|
+
permissionMode = args[i + 1];
|
|
70
|
+
i++;
|
|
71
|
+
}
|
|
52
72
|
else if (args[i] === '--pipe' || args[i] === '-p') {
|
|
53
73
|
mode = 'pipe';
|
|
54
74
|
}
|
|
55
75
|
else if (args[i] === '--ipc') {
|
|
56
76
|
mode = 'ipc';
|
|
57
77
|
}
|
|
78
|
+
else if (args[i] === '--worktree' || args[i] === '-w') {
|
|
79
|
+
worktree = true;
|
|
80
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('-') && args[i + 1] !== 'run') {
|
|
81
|
+
worktreeName = args[i + 1];
|
|
82
|
+
i++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
58
85
|
else if (args[i] === '--help' || args[i] === '-h') {
|
|
59
86
|
console.log(`
|
|
60
87
|
Scalix - Headless Mode
|
|
@@ -71,7 +98,10 @@ Options:
|
|
|
71
98
|
--pipe, -p Read prompt from stdin (for piping)
|
|
72
99
|
--ipc IPC server mode for VS Code extension integration
|
|
73
100
|
--output <file> Write result to file instead of stdout
|
|
74
|
-
--format <fmt> Output format: text (default), json, patch, markdown
|
|
101
|
+
--format <fmt> Output format: text (default), json, json-schema, patch, markdown
|
|
102
|
+
--output-format Alias for --format
|
|
103
|
+
--output-schema Path to JSON schema file (implies --format json-schema)
|
|
104
|
+
--worktree, -w Run in an isolated git worktree (optional: --worktree <name>)
|
|
75
105
|
--help, -h Show this help
|
|
76
106
|
`);
|
|
77
107
|
process.exit(0);
|
|
@@ -80,7 +110,7 @@ Options:
|
|
|
80
110
|
if (!process.stdin.isTTY && mode === 'interactive') {
|
|
81
111
|
mode = 'pipe';
|
|
82
112
|
}
|
|
83
|
-
return { mode, prompt, output, format };
|
|
113
|
+
return { mode, prompt, output, format, permissionMode, outputSchema, worktree, worktreeName };
|
|
84
114
|
}
|
|
85
115
|
async function buildEnhancedInstructions() {
|
|
86
116
|
let base = DEFAULT_INSTRUCTIONS;
|
|
@@ -101,7 +131,7 @@ async function buildEnhancedInstructions() {
|
|
|
101
131
|
const ctx = await buildProjectContext(cwd);
|
|
102
132
|
return contextToInstructions(base, ctx);
|
|
103
133
|
}
|
|
104
|
-
async function runSinglePrompt(prompt, output, format) {
|
|
134
|
+
async function runSinglePrompt(prompt, output, format, outputSchemaPath) {
|
|
105
135
|
const apiKey = process.env[ENV_API_KEY];
|
|
106
136
|
if (!apiKey) {
|
|
107
137
|
console.error('Error: SCALIX_API_KEY is not set.\nSet it via: export SCALIX_API_KEY=sk-...\nOr run the interactive setup: scalix');
|
|
@@ -112,6 +142,13 @@ async function runSinglePrompt(prompt, output, format) {
|
|
|
112
142
|
const baseUrl = process.env[ENV_BASE_URL] || config.baseUrl;
|
|
113
143
|
// Fetch model specs from API (non-blocking — uses built-in/user-defined as fallback)
|
|
114
144
|
fetchModelSpecs(baseUrl, apiKey).catch(() => { });
|
|
145
|
+
if (!process.env.NO_UPDATE_NOTIFIER && !config.disableUpdateCheck) {
|
|
146
|
+
checkForUpdate().then(() => {
|
|
147
|
+
const banner = getUpdateBanner();
|
|
148
|
+
if (banner)
|
|
149
|
+
process.stderr.write(banner + '\n');
|
|
150
|
+
}).catch(() => { });
|
|
151
|
+
}
|
|
115
152
|
const envModels = process.env[ENV_MODEL]
|
|
116
153
|
? process.env[ENV_MODEL].split(',').map((m) => m.trim()).filter(Boolean)
|
|
117
154
|
: [];
|
|
@@ -126,13 +163,30 @@ async function runSinglePrompt(prompt, output, format) {
|
|
|
126
163
|
const pluginTools = getPluginTools(plugins);
|
|
127
164
|
const allTools = [...defaultTools, ...extraTools, ...webTools, ...advancedTools, ...visionTools, ...voiceTools, ...browserTools, ...mcpTools, ...pluginTools];
|
|
128
165
|
const instructions = await buildEnhancedInstructions();
|
|
166
|
+
let outputSchema;
|
|
167
|
+
if (outputSchemaPath) {
|
|
168
|
+
try {
|
|
169
|
+
const schemaContent = await fs.readFile(outputSchemaPath, 'utf-8');
|
|
170
|
+
const parsed = JSON.parse(schemaContent);
|
|
171
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || !parsed.type) {
|
|
172
|
+
console.error(`Invalid JSON schema: must be an object with a "type" property`);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
outputSchema = parsed;
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
console.error(`Error loading output schema from ${outputSchemaPath}: ${err.message}`);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const outputFormat = (format === 'json' || format === 'json-schema') ? format : undefined;
|
|
129
183
|
const agent = createAgent({
|
|
130
184
|
apiKey,
|
|
131
185
|
baseUrl,
|
|
132
186
|
model: selectedModel,
|
|
133
187
|
instructions,
|
|
134
188
|
tools: allTools,
|
|
135
|
-
permissionMode: config.permissionMode || 'default',
|
|
189
|
+
permissionMode: (process.env.SCALIX_PERMISSION_MODE || config.permissionMode || 'default'),
|
|
136
190
|
maxSteps: limits.maxSteps,
|
|
137
191
|
maxMessages: limits.maxMessages,
|
|
138
192
|
contextManager: {
|
|
@@ -142,6 +196,10 @@ async function runSinglePrompt(prompt, output, format) {
|
|
|
142
196
|
},
|
|
143
197
|
instructionLoader: buildEnhancedInstructions,
|
|
144
198
|
autoVerify: true,
|
|
199
|
+
outputFormat,
|
|
200
|
+
outputSchema,
|
|
201
|
+
groundingCheck: true,
|
|
202
|
+
contextRetrieval: true,
|
|
145
203
|
});
|
|
146
204
|
let fullResponse = '';
|
|
147
205
|
agent.on('stream:end', (text) => { fullResponse = text; });
|
|
@@ -167,6 +225,9 @@ async function runSinglePrompt(prompt, output, format) {
|
|
|
167
225
|
cost: costTracker.totalCost,
|
|
168
226
|
}, null, 2);
|
|
169
227
|
break;
|
|
228
|
+
case 'json-schema':
|
|
229
|
+
formatted = fullResponse;
|
|
230
|
+
break;
|
|
170
231
|
case 'patch':
|
|
171
232
|
formatted = fullResponse;
|
|
172
233
|
break;
|
|
@@ -183,20 +244,27 @@ async function runSinglePrompt(prompt, output, format) {
|
|
|
183
244
|
else {
|
|
184
245
|
process.stdout.write(formatted + '\n');
|
|
185
246
|
}
|
|
247
|
+
await closeBrowser();
|
|
186
248
|
process.exit(0);
|
|
187
249
|
}
|
|
188
250
|
async function runPipeMode() {
|
|
189
251
|
const MAX_PIPE_BYTES = 50 * 1024 * 1024;
|
|
190
252
|
const chunks = [];
|
|
191
253
|
let totalBytes = 0;
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
254
|
+
try {
|
|
255
|
+
for await (const chunk of process.stdin) {
|
|
256
|
+
const str = chunk.toString();
|
|
257
|
+
totalBytes += Buffer.byteLength(str);
|
|
258
|
+
if (totalBytes > MAX_PIPE_BYTES) {
|
|
259
|
+
console.error(`Error: Pipe input exceeds ${MAX_PIPE_BYTES / 1024 / 1024}MB limit.`);
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
chunks.push(str);
|
|
198
263
|
}
|
|
199
|
-
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
console.error(`Error reading pipe input: ${err.message}`);
|
|
267
|
+
process.exit(1);
|
|
200
268
|
}
|
|
201
269
|
const prompt = chunks.join('').trim();
|
|
202
270
|
if (!prompt) {
|
|
@@ -232,7 +300,14 @@ async function runInteractive() {
|
|
|
232
300
|
input: process.stdin,
|
|
233
301
|
output: process.stdout,
|
|
234
302
|
});
|
|
303
|
+
let rlClosed = false;
|
|
304
|
+
rl.on('close', () => {
|
|
305
|
+
rlClosed = true;
|
|
306
|
+
closeBrowser().finally(() => process.exit(0));
|
|
307
|
+
});
|
|
235
308
|
function askUser(question) {
|
|
309
|
+
if (rlClosed)
|
|
310
|
+
return Promise.resolve('/exit');
|
|
236
311
|
return new Promise((resolve) => rl.question(question, resolve));
|
|
237
312
|
}
|
|
238
313
|
const customCmds = await loadCustomCommands(process.cwd());
|
|
@@ -242,7 +317,7 @@ async function runInteractive() {
|
|
|
242
317
|
model: selectedModel,
|
|
243
318
|
instructions,
|
|
244
319
|
tools: allTools,
|
|
245
|
-
permissionMode: config.permissionMode || 'default',
|
|
320
|
+
permissionMode: (process.env.SCALIX_PERMISSION_MODE || config.permissionMode || 'default'),
|
|
246
321
|
maxSteps: limits.maxSteps,
|
|
247
322
|
maxMessages: limits.maxMessages,
|
|
248
323
|
contextManager: {
|
|
@@ -252,6 +327,8 @@ async function runInteractive() {
|
|
|
252
327
|
},
|
|
253
328
|
instructionLoader: buildEnhancedInstructions,
|
|
254
329
|
autoVerify: true,
|
|
330
|
+
groundingCheck: true,
|
|
331
|
+
contextRetrieval: true,
|
|
255
332
|
confirmToolCall: async (toolName, args) => {
|
|
256
333
|
const preview = typeof args === 'string' ? args : JSON.stringify(args, null, 2);
|
|
257
334
|
console.log(`\n\u26A0 Tool "${toolName}" wants to execute:`);
|
|
@@ -307,7 +384,7 @@ async function runInteractive() {
|
|
|
307
384
|
}
|
|
308
385
|
console.log('Goodbye!');
|
|
309
386
|
rl.close();
|
|
310
|
-
process.exit(0);
|
|
387
|
+
closeBrowser().finally(() => process.exit(0));
|
|
311
388
|
}
|
|
312
389
|
process.on('SIGINT', shutdown);
|
|
313
390
|
process.on('SIGTERM', shutdown);
|
|
@@ -329,6 +406,7 @@ async function runInteractive() {
|
|
|
329
406
|
console.log(' /hooks \u2014 show lifecycle hooks');
|
|
330
407
|
console.log(' /commands \u2014 show custom commands');
|
|
331
408
|
console.log(' /delegate <t>\u2014 run task with sub-agent');
|
|
409
|
+
console.log(' /agents \u2014 show active agents & usage');
|
|
332
410
|
console.log(' /checkpoint \u2014 create git checkpoint');
|
|
333
411
|
console.log(' /undo \u2014 undo last checkpoint');
|
|
334
412
|
console.log(' /checkpoints \u2014 list recent checkpoints');
|
|
@@ -488,6 +566,22 @@ async function runInteractive() {
|
|
|
488
566
|
}
|
|
489
567
|
return true;
|
|
490
568
|
}
|
|
569
|
+
if (cmd === '/agents') {
|
|
570
|
+
const registry = AgentRegistry.getInstance();
|
|
571
|
+
const summary = registry.getUsageSummary();
|
|
572
|
+
const tree = registry.getAgentTree();
|
|
573
|
+
console.log(`\nActive: ${summary.activeCount} | Completed: ${summary.completedCount} | Failed: ${summary.failedCount}`);
|
|
574
|
+
console.log(`Total tokens: ${summary.totalTokens.toLocaleString()}`);
|
|
575
|
+
if (tree && tree !== '(no agents)') {
|
|
576
|
+
console.log('\nAgent tree:');
|
|
577
|
+
console.log(tree);
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
console.log('No agents have been spawned yet.');
|
|
581
|
+
}
|
|
582
|
+
console.log('');
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
491
585
|
if (cmd === '/checkpoint') {
|
|
492
586
|
const label = input.slice(12).trim() || undefined;
|
|
493
587
|
const hash = await createCheckpoint(process.cwd(), label);
|
|
@@ -539,7 +633,7 @@ async function runInteractive() {
|
|
|
539
633
|
return true;
|
|
540
634
|
}
|
|
541
635
|
if (cmd === '/git') {
|
|
542
|
-
const gitInfo = collectGitInfo(process.cwd());
|
|
636
|
+
const gitInfo = await collectGitInfo(process.cwd());
|
|
543
637
|
if (!gitInfo.isRepo) {
|
|
544
638
|
console.log('\nNot a git repository.\n');
|
|
545
639
|
}
|
|
@@ -630,14 +724,46 @@ async function runInteractive() {
|
|
|
630
724
|
}
|
|
631
725
|
async function main() {
|
|
632
726
|
setSandboxRoot(process.cwd());
|
|
633
|
-
const { mode, prompt, output, format } = parseArgs();
|
|
727
|
+
const { mode, prompt, output, format, permissionMode: cliPermissionMode, outputSchema, worktree, worktreeName } = parseArgs();
|
|
728
|
+
if (cliPermissionMode) {
|
|
729
|
+
process.env.SCALIX_PERMISSION_MODE = cliPermissionMode;
|
|
730
|
+
}
|
|
731
|
+
// Set up worktree isolation if requested
|
|
732
|
+
let worktreeInfo = null;
|
|
733
|
+
if (worktree) {
|
|
734
|
+
const { createWorktree, removeWorktree } = await import('./worktree.js');
|
|
735
|
+
const name = worktreeName || `session-${Date.now().toString(36)}`;
|
|
736
|
+
try {
|
|
737
|
+
worktreeInfo = await createWorktree(name, process.cwd());
|
|
738
|
+
process.chdir(worktreeInfo.path);
|
|
739
|
+
setSandboxRoot(worktreeInfo.path);
|
|
740
|
+
console.log(`Working in isolated worktree: ${worktreeInfo.branch} → ${worktreeInfo.path}`);
|
|
741
|
+
}
|
|
742
|
+
catch (err) {
|
|
743
|
+
console.error(`Failed to create worktree: ${err.message}`);
|
|
744
|
+
process.exit(1);
|
|
745
|
+
}
|
|
746
|
+
const cleanup = async () => {
|
|
747
|
+
if (worktreeInfo) {
|
|
748
|
+
try {
|
|
749
|
+
const origCwd = process.env.SCALIX_ORIGINAL_CWD || process.cwd();
|
|
750
|
+
process.chdir(path.dirname(worktreeInfo.path));
|
|
751
|
+
await removeWorktree(worktreeInfo, path.dirname(path.dirname(worktreeInfo.path)));
|
|
752
|
+
}
|
|
753
|
+
catch { }
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
process.on('exit', () => { cleanup().catch(() => { }); });
|
|
757
|
+
process.on('SIGINT', () => { cleanup().then(() => process.exit(0)).catch(() => process.exit(1)); });
|
|
758
|
+
process.env.SCALIX_ORIGINAL_CWD = process.cwd();
|
|
759
|
+
}
|
|
634
760
|
switch (mode) {
|
|
635
761
|
case 'run':
|
|
636
762
|
if (!prompt) {
|
|
637
763
|
console.error('Error: No prompt provided. Usage: scalix run "your prompt"');
|
|
638
764
|
process.exit(1);
|
|
639
765
|
}
|
|
640
|
-
await runSinglePrompt(prompt, output, format);
|
|
766
|
+
await runSinglePrompt(prompt, output, format, outputSchema);
|
|
641
767
|
break;
|
|
642
768
|
case 'pipe':
|
|
643
769
|
await runPipeMode();
|
|
@@ -651,4 +777,7 @@ async function main() {
|
|
|
651
777
|
break;
|
|
652
778
|
}
|
|
653
779
|
}
|
|
654
|
-
main().catch(
|
|
780
|
+
main().catch((err) => {
|
|
781
|
+
console.error(err);
|
|
782
|
+
process.exit(1);
|
|
783
|
+
});
|