notsleep 1.0.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 +117 -0
- package/LICENSE +21 -0
- package/README.md +341 -0
- package/dashboard/index.html +893 -0
- package/dist/agent.d.ts +162 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +1064 -0
- package/dist/agent.js.map +1 -0
- package/dist/api.d.ts +10 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +39 -0
- package/dist/api.js.map +1 -0
- package/dist/checkpoint.d.ts +52 -0
- package/dist/checkpoint.d.ts.map +1 -0
- package/dist/checkpoint.js +234 -0
- package/dist/checkpoint.js.map +1 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +265 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +15 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +237 -0
- package/dist/context.js.map +1 -0
- package/dist/cost.d.ts +26 -0
- package/dist/cost.d.ts.map +1 -0
- package/dist/cost.js +161 -0
- package/dist/cost.js.map +1 -0
- package/dist/dashboard.d.ts +96 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +292 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/fileconfig.d.ts +129 -0
- package/dist/fileconfig.d.ts.map +1 -0
- package/dist/fileconfig.js +391 -0
- package/dist/fileconfig.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +882 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +9 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +221 -0
- package/dist/init.js.map +1 -0
- package/dist/logger.d.ts +33 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +150 -0
- package/dist/logger.js.map +1 -0
- package/dist/modelsdev.d.ts +63 -0
- package/dist/modelsdev.d.ts.map +1 -0
- package/dist/modelsdev.js +265 -0
- package/dist/modelsdev.js.map +1 -0
- package/dist/parallel.d.ts +54 -0
- package/dist/parallel.d.ts.map +1 -0
- package/dist/parallel.js +139 -0
- package/dist/parallel.js.map +1 -0
- package/dist/pool.d.ts +17 -0
- package/dist/pool.d.ts.map +1 -0
- package/dist/pool.js +53 -0
- package/dist/pool.js.map +1 -0
- package/dist/provider.d.ts +60 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +396 -0
- package/dist/provider.js.map +1 -0
- package/dist/registry.d.ts +60 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +153 -0
- package/dist/registry.js.map +1 -0
- package/dist/safety.d.ts +21 -0
- package/dist/safety.d.ts.map +1 -0
- package/dist/safety.js +118 -0
- package/dist/safety.js.map +1 -0
- package/dist/schema.d.ts +9 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +166 -0
- package/dist/schema.js.map +1 -0
- package/dist/test-dashboard.d.ts +5 -0
- package/dist/test-dashboard.d.ts.map +1 -0
- package/dist/test-dashboard.js +202 -0
- package/dist/test-dashboard.js.map +1 -0
- package/dist/tools.d.ts +27 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +1212 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +180 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/dist/worktree.d.ts +50 -0
- package/dist/worktree.d.ts.map +1 -0
- package/dist/worktree.js +186 -0
- package/dist/worktree.js.map +1 -0
- package/package.json +67 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,882 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
// ============================================================
|
|
4
|
+
// NotSleep - CLI Entry Point
|
|
5
|
+
// ============================================================
|
|
6
|
+
//
|
|
7
|
+
// Two modes:
|
|
8
|
+
// notsleep → Interactive REPL (like OpenCode)
|
|
9
|
+
// notsleep "Build an API" → One-shot autonomous agent
|
|
10
|
+
//
|
|
11
|
+
// ============================================================
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
46
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
47
|
+
};
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
const commander_1 = require("commander");
|
|
50
|
+
const fs = __importStar(require("fs"));
|
|
51
|
+
const readline = __importStar(require("readline"));
|
|
52
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
53
|
+
const config_1 = require("./config");
|
|
54
|
+
const agent_1 = require("./agent");
|
|
55
|
+
const logger_1 = require("./logger");
|
|
56
|
+
const dashboard_1 = require("./dashboard");
|
|
57
|
+
const context_1 = require("./context");
|
|
58
|
+
const checkpoint_1 = require("./checkpoint");
|
|
59
|
+
const provider_1 = require("./provider");
|
|
60
|
+
const types_1 = require("./types");
|
|
61
|
+
const init_1 = require("./init");
|
|
62
|
+
const fileconfig_1 = require("./fileconfig");
|
|
63
|
+
const parallel_1 = require("./parallel");
|
|
64
|
+
const registry_1 = require("./registry");
|
|
65
|
+
const modelsdev_1 = require("./modelsdev");
|
|
66
|
+
const schema_1 = require("./schema");
|
|
67
|
+
const path = __importStar(require("path"));
|
|
68
|
+
const VERSION = '1.0.0';
|
|
69
|
+
const BANNER = `
|
|
70
|
+
${chalk_1.default.bold.cyan('╔══════════════════════════════════════════════════════════╗')}
|
|
71
|
+
${chalk_1.default.bold.cyan('║')} ${chalk_1.default.bold.white('NotSleep')} - Long Horizon Autonomous AI Agent ${chalk_1.default.bold.cyan('║')}
|
|
72
|
+
${chalk_1.default.bold.cyan('║')} ${chalk_1.default.dim('"Do not stop until I woke up"')} ${chalk_1.default.bold.cyan('║')}
|
|
73
|
+
${chalk_1.default.bold.cyan('║')} ${chalk_1.default.dim(`v${VERSION} | OpenAI + Anthropic Compatible`)} ${chalk_1.default.bold.cyan('║')}
|
|
74
|
+
${chalk_1.default.bold.cyan('╚══════════════════════════════════════════════════════════╝')}
|
|
75
|
+
`;
|
|
76
|
+
const HELP_TEXT = `
|
|
77
|
+
${chalk_1.default.bold('Commands:')}
|
|
78
|
+
${chalk_1.default.cyan('/help')} Show this help
|
|
79
|
+
${chalk_1.default.cyan('/run <task>')} Run agent on a task (autonomous, long-horizon)
|
|
80
|
+
${chalk_1.default.cyan('/status')} Show agent status & stats
|
|
81
|
+
${chalk_1.default.cyan('/context')} Show detected project context
|
|
82
|
+
${chalk_1.default.cyan('/config')} Show resolved configuration & sources
|
|
83
|
+
${chalk_1.default.cyan('/checkpoints')} List saved checkpoints you can resume from
|
|
84
|
+
${chalk_1.default.cyan('/resume [task]')} Resume the latest checkpoint (autonomous)
|
|
85
|
+
${chalk_1.default.cyan('/clear')} Clear conversation history
|
|
86
|
+
${chalk_1.default.cyan('/model')} Show current model info
|
|
87
|
+
${chalk_1.default.cyan('/exit')} Exit NotSleep
|
|
88
|
+
|
|
89
|
+
${chalk_1.default.dim('Or just type normally to chat with the AI.')}
|
|
90
|
+
${chalk_1.default.dim('The AI can see your project files and run commands.')}
|
|
91
|
+
`;
|
|
92
|
+
async function main() {
|
|
93
|
+
const program = new commander_1.Command();
|
|
94
|
+
// ── Subcommand: init ──
|
|
95
|
+
program
|
|
96
|
+
.command('init')
|
|
97
|
+
.description('Create a notsleep.json config file (interactive wizard)')
|
|
98
|
+
.option('-g, --global', 'Write to the global config (~/.notsleep/config.json)', false)
|
|
99
|
+
.option('-y, --yes', 'Skip prompts and write defaults', false)
|
|
100
|
+
.action(async (opts) => {
|
|
101
|
+
console.log(BANNER);
|
|
102
|
+
await (0, init_1.runInit)({ global: opts.global, yes: opts.yes });
|
|
103
|
+
process.exit(0);
|
|
104
|
+
});
|
|
105
|
+
// ── Subcommand: config ──
|
|
106
|
+
program
|
|
107
|
+
.command('config')
|
|
108
|
+
.description('Show the resolved configuration and where it came from')
|
|
109
|
+
.action(async () => {
|
|
110
|
+
console.log(BANNER);
|
|
111
|
+
// Refresh live model metadata so the shown context limit is accurate.
|
|
112
|
+
await (0, modelsdev_1.ensureModelCatalog)({ log: (m) => console.log(chalk_1.default.dim(` ${m}`)) });
|
|
113
|
+
await showConfig();
|
|
114
|
+
process.exit(0);
|
|
115
|
+
});
|
|
116
|
+
program
|
|
117
|
+
.command('schema')
|
|
118
|
+
.description('Write the notsleep.json JSON Schema to disk (for IDE autocomplete)')
|
|
119
|
+
.option('-o, --out <path>', 'Output path', schema_1.DEFAULT_SCHEMA_FILENAME)
|
|
120
|
+
.action(async (options) => {
|
|
121
|
+
const written = (0, schema_1.writeSchemaFile)(options.out);
|
|
122
|
+
console.log(chalk_1.default.green(` JSON Schema written to ${written}`));
|
|
123
|
+
console.log(chalk_1.default.dim(` Reference it in notsleep.json: "$schema": "./${schema_1.DEFAULT_SCHEMA_FILENAME}"`));
|
|
124
|
+
process.exit(0);
|
|
125
|
+
});
|
|
126
|
+
program
|
|
127
|
+
.name('notsleep')
|
|
128
|
+
.version(VERSION)
|
|
129
|
+
.description('Long-horizon autonomous AI agent. Interactive REPL or one-shot mode.')
|
|
130
|
+
.argument('[task...]', 'Task description. If omitted, starts interactive mode.')
|
|
131
|
+
.option('-p, --provider <provider>', 'LLM provider: openai or anthropic (default: openai, or from config)')
|
|
132
|
+
.option('-m, --model <model>', 'Model to use (overrides .env). Accepts provider/model, e.g. groq/llama-3.3-70b')
|
|
133
|
+
.option('--small-model <model>', 'Cheaper model for internal calls (summarize, task-gen). provider/model or a bare id')
|
|
134
|
+
.option('--resume', 'Resume from the latest checkpoint', false)
|
|
135
|
+
.option('--task-file <path>', 'Read the task description from a file')
|
|
136
|
+
.option('--max-iterations <n>', 'Maximum iterations (default: 1000; use 0 for unlimited)')
|
|
137
|
+
.option('--unlimited', 'Run with no iteration cap (same as --max-iterations 0)', false)
|
|
138
|
+
.option('--max-iterations-per-task <n>', 'Auto-advance to the next queued task after N iterations on one task (0 = no cap)')
|
|
139
|
+
.option('--context-limit <n>', 'Context window token limit (default: 120000, or from config)')
|
|
140
|
+
.option('--verbose', 'Enable verbose/debug logging', false)
|
|
141
|
+
.option('--openai-key <key>', 'OpenAI API key (overrides .env)')
|
|
142
|
+
.option('--anthropic-key <key>', 'Anthropic API key (overrides .env)')
|
|
143
|
+
.option('--base-url <url>', 'Custom API base URL')
|
|
144
|
+
.option('--system-prompt <prompt>', 'Custom system prompt')
|
|
145
|
+
.option('--system-prompt-file <path>', 'Read system prompt from a file')
|
|
146
|
+
.option('-c, --continuous', 'Continuous mode: keep picking up new tasks', false)
|
|
147
|
+
.option('--stop-file <path>', 'File path that signals the agent to stop')
|
|
148
|
+
.option('--max-cost <usd>', 'Stop when estimated cost exceeds this USD budget (e.g. 5.00)')
|
|
149
|
+
.option('--auto-commit', 'Create a local git commit after each completed task (never pushes)', false)
|
|
150
|
+
.option('--parallel <n>', 'Run tasks concurrently (n at a time), each in an isolated git worktree/branch')
|
|
151
|
+
.option('--tasks-file <path>', 'File with one task per line (used with --parallel)')
|
|
152
|
+
.option('--merge', 'After --parallel, merge each successful task branch back into the current branch', false)
|
|
153
|
+
.option('--no-cleanup', 'Keep task worktree directories after a --parallel run (for debugging)')
|
|
154
|
+
.option('--ui', 'Enable web dashboard UI', false)
|
|
155
|
+
.option('--port <port>', 'Dashboard port')
|
|
156
|
+
.option('--host <host>', 'Dashboard bind address (default 127.0.0.1; use 0.0.0.0 to expose to your network)')
|
|
157
|
+
.action(async (taskArgs, options) => {
|
|
158
|
+
console.log(BANNER);
|
|
159
|
+
// Sync live model metadata (context window + pricing) from models.dev
|
|
160
|
+
// BEFORE building config, so the context-window limit is auto-detected
|
|
161
|
+
// from the real model window rather than the static fallback table.
|
|
162
|
+
await (0, modelsdev_1.ensureModelCatalog)({
|
|
163
|
+
log: options.verbose ? (m) => console.log(chalk_1.default.dim(` ${m}`)) : undefined,
|
|
164
|
+
});
|
|
165
|
+
// Build config
|
|
166
|
+
const config = buildConfig(options);
|
|
167
|
+
// Layer 2: if the catalog didn't know this model (static fallback), ask
|
|
168
|
+
// the provider's own /models endpoint for the real context window.
|
|
169
|
+
await refineContextLimitFromEndpoint(config, options.verbose);
|
|
170
|
+
// Determine mode
|
|
171
|
+
let taskDescription = '';
|
|
172
|
+
if (options.taskFile) {
|
|
173
|
+
try {
|
|
174
|
+
taskDescription = fs.readFileSync(options.taskFile, 'utf-8').trim();
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
console.error(chalk_1.default.red(`Error reading task file: ${err.message}`));
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else if (taskArgs.length > 0) {
|
|
182
|
+
taskDescription = taskArgs.join(' ');
|
|
183
|
+
}
|
|
184
|
+
if (options.parallel) {
|
|
185
|
+
// ─── Parallel Mode ──────────────────────────────
|
|
186
|
+
await runParallel(config, options);
|
|
187
|
+
}
|
|
188
|
+
else if (taskDescription || options.resume) {
|
|
189
|
+
// ─── One-Shot Mode ──────────────────────────────
|
|
190
|
+
await runOneShot(config, taskDescription, options);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
// ─── Interactive REPL Mode ──────────────────────
|
|
194
|
+
await runInteractive(config, options);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
await program.parseAsync(process.argv);
|
|
198
|
+
}
|
|
199
|
+
// ─────────────────────────────────────────────────────────────
|
|
200
|
+
// Config Builder
|
|
201
|
+
// ─────────────────────────────────────────────────────────────
|
|
202
|
+
function buildConfig(options) {
|
|
203
|
+
const overrides = {};
|
|
204
|
+
if (options.provider)
|
|
205
|
+
overrides.provider = options.provider;
|
|
206
|
+
if (options.openaiKey)
|
|
207
|
+
overrides.openai = { apiKey: options.openaiKey };
|
|
208
|
+
if (options.anthropicKey)
|
|
209
|
+
overrides.anthropic = { apiKey: options.anthropicKey };
|
|
210
|
+
// Resolve the EFFECTIVE provider so --model / --base-url land on the right
|
|
211
|
+
// block. Priority mirrors config.ts: CLI flag > file config > env > default.
|
|
212
|
+
// We must NOT assume 'openai' here — that would misroute overrides when the
|
|
213
|
+
// active provider comes from notsleep.json or the environment.
|
|
214
|
+
const { config: fileCfg } = (0, fileconfig_1.loadFileConfig)();
|
|
215
|
+
const effectiveProvider = (options.provider ||
|
|
216
|
+
fileCfg.provider ||
|
|
217
|
+
process.env.NOTSLEEP_PROVIDER ||
|
|
218
|
+
'openai');
|
|
219
|
+
if (options.model) {
|
|
220
|
+
if ((0, registry_1.isModelRef)(options.model)) {
|
|
221
|
+
// Unified "provider/model" registry selector (e.g. groq/llama-3.3-70b).
|
|
222
|
+
// Resolved against the provider registry in loadConfig().
|
|
223
|
+
overrides.model = options.model;
|
|
224
|
+
}
|
|
225
|
+
else if (effectiveProvider === 'anthropic') {
|
|
226
|
+
overrides.anthropic = { ...overrides.anthropic, model: options.model };
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
overrides.openai = { ...overrides.openai, model: options.model };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Small model ref (provider/model or bare id) — resolved in loadConfig().
|
|
233
|
+
if (options.smallModel)
|
|
234
|
+
overrides.smallModelRef = options.smallModel;
|
|
235
|
+
if (options.baseUrl) {
|
|
236
|
+
if (effectiveProvider === 'anthropic') {
|
|
237
|
+
overrides.anthropic = { ...overrides.anthropic, baseURL: options.baseUrl };
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
overrides.openai = { ...overrides.openai, baseURL: options.baseUrl };
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (options.unlimited) {
|
|
244
|
+
overrides.maxIterations = 0; // 0 = unlimited
|
|
245
|
+
}
|
|
246
|
+
else if (options.maxIterations !== undefined) {
|
|
247
|
+
overrides.maxIterations = parseInt(options.maxIterations, 10);
|
|
248
|
+
}
|
|
249
|
+
if (options.maxIterationsPerTask !== undefined) {
|
|
250
|
+
overrides.maxIterationsPerTask = parseInt(options.maxIterationsPerTask, 10);
|
|
251
|
+
}
|
|
252
|
+
if (options.contextLimit)
|
|
253
|
+
overrides.contextWindowLimit = parseInt(options.contextLimit, 10);
|
|
254
|
+
if (options.systemPromptFile) {
|
|
255
|
+
try {
|
|
256
|
+
overrides.systemPrompt = fs.readFileSync(options.systemPromptFile, 'utf-8').trim();
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
console.error(chalk_1.default.red(`Error reading system prompt file: ${err.message}`));
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else if (options.systemPrompt) {
|
|
264
|
+
overrides.systemPrompt = options.systemPrompt;
|
|
265
|
+
}
|
|
266
|
+
if (options.continuous)
|
|
267
|
+
overrides.continuous = true;
|
|
268
|
+
if (options.stopFile)
|
|
269
|
+
overrides.stopFile = options.stopFile;
|
|
270
|
+
if (options.autoCommit)
|
|
271
|
+
overrides.autoCommit = true;
|
|
272
|
+
if (options.maxCost) {
|
|
273
|
+
const c = parseFloat(options.maxCost);
|
|
274
|
+
if (!isNaN(c) && c > 0)
|
|
275
|
+
overrides.maxCostUsd = c;
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
return (0, config_1.loadConfig)(overrides);
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
console.error(chalk_1.default.red(`Configuration error: ${err.message}`));
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// ─────────────────────────────────────────────────────────────
|
|
286
|
+
// One-Shot Mode (autonomous agent, like before)
|
|
287
|
+
// ─────────────────────────────────────────────────────────────
|
|
288
|
+
async function runOneShot(config, taskDescription, options) {
|
|
289
|
+
const logLevel = options.verbose ? logger_1.LogLevel.DEBUG : logger_1.LogLevel.INFO;
|
|
290
|
+
const logger = new logger_1.Logger(config.logDir, logLevel);
|
|
291
|
+
printConfigWarnings();
|
|
292
|
+
// Detect project context
|
|
293
|
+
const ctx = (0, context_1.detectProjectContext)();
|
|
294
|
+
logger.info(`Project: ${chalk_1.default.bold(ctx.language)} / ${chalk_1.default.bold(ctx.framework)}`);
|
|
295
|
+
logger.info(`Provider: ${chalk_1.default.bold(getProviderLabel(config))} | Model: ${chalk_1.default.bold(getModelName(config))}`);
|
|
296
|
+
logger.info(`Mode: ${chalk_1.default.bold(config.continuous ? 'CONTINUOUS' : 'SINGLE TASK')}`);
|
|
297
|
+
if (ctx.gitBranch) {
|
|
298
|
+
logger.info(`Git: ${chalk_1.default.bold(ctx.gitBranch)}${ctx.gitDirty ? chalk_1.default.yellow(' (dirty)') : chalk_1.default.green(' (clean)')}`);
|
|
299
|
+
}
|
|
300
|
+
// Inject project context into system prompt
|
|
301
|
+
config.systemPrompt += `\n\nPROJECT CONTEXT:\n${ctx.summary}`;
|
|
302
|
+
// Create agent
|
|
303
|
+
const agent = new agent_1.Agent(config, logger);
|
|
304
|
+
// Start dashboard if --ui flag OR enabled in config
|
|
305
|
+
let dashboard = null;
|
|
306
|
+
if (options.ui || config.dashboard.enabled) {
|
|
307
|
+
const port = options.port ? parseInt(options.port, 10) : config.dashboard.port;
|
|
308
|
+
dashboard = new dashboard_1.DashboardServer(port, logger, options.host || '127.0.0.1');
|
|
309
|
+
agent.setDashboard(dashboard);
|
|
310
|
+
try {
|
|
311
|
+
await dashboard.start();
|
|
312
|
+
logger.separator();
|
|
313
|
+
console.log(chalk_1.default.bold.green(` Dashboard: http://localhost:${port}`));
|
|
314
|
+
logger.separator();
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
logger.error(`Failed to start dashboard: ${err.message}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// Run
|
|
321
|
+
const result = await agent.run(taskDescription, options.resume);
|
|
322
|
+
if (dashboard)
|
|
323
|
+
await dashboard.stop();
|
|
324
|
+
if (result.success) {
|
|
325
|
+
console.log(chalk_1.default.green.bold(`\n Task completed! (${result.stats.tasksCompleted} task(s))`));
|
|
326
|
+
console.log(chalk_1.default.white(result.summary));
|
|
327
|
+
process.exit(0);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
console.log(chalk_1.default.yellow.bold('\n Task did not complete.'));
|
|
331
|
+
console.log(chalk_1.default.white(result.summary));
|
|
332
|
+
console.log(chalk_1.default.dim(' Use --resume to continue from the last checkpoint.'));
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// ─────────────────────────────────────────────────────────────
|
|
337
|
+
// Parallel Mode (multiple tasks concurrently, isolated worktrees)
|
|
338
|
+
// ─────────────────────────────────────────────────────────────
|
|
339
|
+
async function runParallel(config, options) {
|
|
340
|
+
const logLevel = options.verbose ? logger_1.LogLevel.DEBUG : logger_1.LogLevel.INFO;
|
|
341
|
+
const logger = new logger_1.Logger(config.logDir, logLevel);
|
|
342
|
+
printConfigWarnings();
|
|
343
|
+
const concurrency = parseInt(options.parallel, 10);
|
|
344
|
+
if (!Number.isFinite(concurrency) || concurrency < 1) {
|
|
345
|
+
console.error(chalk_1.default.red('--parallel needs a positive integer (e.g. --parallel 3).'));
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
// Gather tasks: one per line from --tasks-file.
|
|
349
|
+
let tasks = [];
|
|
350
|
+
if (options.tasksFile) {
|
|
351
|
+
let raw = '';
|
|
352
|
+
try {
|
|
353
|
+
raw = fs.readFileSync(options.tasksFile, 'utf-8');
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
console.error(chalk_1.default.red(`Error reading tasks file: ${err.message}`));
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
tasks = raw
|
|
360
|
+
.split('\n')
|
|
361
|
+
.map((l) => l.trim())
|
|
362
|
+
.filter((l) => l.length > 0 && !l.startsWith('#'))
|
|
363
|
+
.map((description, i) => ({ id: `task-${i + 1}`, description }));
|
|
364
|
+
}
|
|
365
|
+
if (tasks.length === 0) {
|
|
366
|
+
console.error(chalk_1.default.red('No tasks found. Provide --tasks-file <path> with one task per line.'));
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
369
|
+
logger.info(`Loaded ${tasks.length} task(s) for parallel execution.`);
|
|
370
|
+
const runner = new parallel_1.ParallelRunner(process.cwd(), logger);
|
|
371
|
+
// Each task runs the built one-shot CLI as a child process, in its worktree.
|
|
372
|
+
// Forward the safety/limit flags so child agents behave like this invocation.
|
|
373
|
+
const cliEntry = path.resolve(__dirname, 'index.js');
|
|
374
|
+
const forwarded = [];
|
|
375
|
+
if (options.maxIterations !== undefined)
|
|
376
|
+
forwarded.push('--max-iterations', String(options.maxIterations));
|
|
377
|
+
if (options.unlimited)
|
|
378
|
+
forwarded.push('--unlimited');
|
|
379
|
+
if (options.maxCost !== undefined)
|
|
380
|
+
forwarded.push('--max-cost', String(options.maxCost));
|
|
381
|
+
if (options.autoCommit)
|
|
382
|
+
forwarded.push('--auto-commit');
|
|
383
|
+
if (options.provider)
|
|
384
|
+
forwarded.push('--provider', String(options.provider));
|
|
385
|
+
if (options.model)
|
|
386
|
+
forwarded.push('--model', String(options.model));
|
|
387
|
+
if (options.smallModel)
|
|
388
|
+
forwarded.push('--small-model', String(options.smallModel));
|
|
389
|
+
const taskRunner = (0, parallel_1.spawnAgentInWorktree)(process.execPath, cliEntry, forwarded, logger);
|
|
390
|
+
let outcomes;
|
|
391
|
+
try {
|
|
392
|
+
outcomes = await runner.run(tasks, taskRunner, {
|
|
393
|
+
concurrency,
|
|
394
|
+
merge: options.merge === true,
|
|
395
|
+
cleanup: options.cleanup !== false, // commander sets cleanup=false for --no-cleanup
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
catch (err) {
|
|
399
|
+
console.error(chalk_1.default.red(`Parallel run could not start: ${err.message}`));
|
|
400
|
+
process.exit(1);
|
|
401
|
+
}
|
|
402
|
+
// Summary
|
|
403
|
+
console.log('');
|
|
404
|
+
logger.separator();
|
|
405
|
+
console.log(chalk_1.default.bold(' Parallel run summary:'));
|
|
406
|
+
for (const o of outcomes) {
|
|
407
|
+
const status = o.ok ? chalk_1.default.green('ok') : chalk_1.default.red('failed');
|
|
408
|
+
const mergeNote = o.merged ? chalk_1.default.green(' merged') : o.conflict ? chalk_1.default.yellow(' conflict (kept branch)') : '';
|
|
409
|
+
console.log(` [${status}] ${o.task.id} -> ${o.branch}${mergeNote}`);
|
|
410
|
+
if (o.error)
|
|
411
|
+
console.log(chalk_1.default.dim(` ${o.error}`));
|
|
412
|
+
}
|
|
413
|
+
logger.separator();
|
|
414
|
+
const failed = outcomes.filter((o) => !o.ok).length;
|
|
415
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
416
|
+
}
|
|
417
|
+
// ─────────────────────────────────────────────────────────────
|
|
418
|
+
// Interactive REPL Mode (like OpenCode)
|
|
419
|
+
// ─────────────────────────────────────────────────────────────
|
|
420
|
+
async function runInteractive(config, options) {
|
|
421
|
+
const logLevel = options.verbose ? logger_1.LogLevel.DEBUG : logger_1.LogLevel.INFO;
|
|
422
|
+
const logger = new logger_1.Logger(config.logDir, logLevel);
|
|
423
|
+
// Detect project context
|
|
424
|
+
const ctx = (0, context_1.detectProjectContext)();
|
|
425
|
+
printConfigWarnings();
|
|
426
|
+
// Show project info
|
|
427
|
+
console.log(chalk_1.default.dim(' Scanning project...'));
|
|
428
|
+
console.log(` ${chalk_1.default.bold('Project:')} ${ctx.language} / ${ctx.framework}`);
|
|
429
|
+
console.log(` ${chalk_1.default.bold('Provider:')} ${getProviderLabel(config)} / ${getModelName(config)}`);
|
|
430
|
+
if (ctx.gitBranch) {
|
|
431
|
+
console.log(` ${chalk_1.default.bold('Git:')} ${ctx.gitBranch}${ctx.gitDirty ? chalk_1.default.yellow(' (dirty)') : chalk_1.default.green(' (clean)')}`);
|
|
432
|
+
}
|
|
433
|
+
console.log(` ${chalk_1.default.bold('CWD:')} ${ctx.cwd}`);
|
|
434
|
+
console.log('');
|
|
435
|
+
console.log(chalk_1.default.dim(' Type a message to chat, /run <task> for autonomous mode, /help for commands.'));
|
|
436
|
+
console.log('');
|
|
437
|
+
// Build provider for direct chat
|
|
438
|
+
const provider = new provider_1.LLMProvider(config, logger);
|
|
439
|
+
// Inject project context into system prompt
|
|
440
|
+
const systemPrompt = config.systemPrompt +
|
|
441
|
+
`\n\nPROJECT CONTEXT:\n${ctx.summary}` +
|
|
442
|
+
`\n\nYou are in INTERACTIVE mode. The user is chatting with you directly. ` +
|
|
443
|
+
`You can use tools to help them. Be concise but helpful. ` +
|
|
444
|
+
`If they ask you to do something complex, use tools step by step.`;
|
|
445
|
+
// Conversation history
|
|
446
|
+
const messages = [
|
|
447
|
+
{ role: 'system', content: systemPrompt },
|
|
448
|
+
];
|
|
449
|
+
// Tools (from the agent's built-in tools + any user-defined custom tools)
|
|
450
|
+
const { createBuiltinTools, createCustomTools } = require('./tools');
|
|
451
|
+
const taskQueue = [];
|
|
452
|
+
const tools = createBuiltinTools(logger, config.toolTimeoutMs, taskQueue, process.cwd());
|
|
453
|
+
const customTools = createCustomTools(config.customTools, logger, config.toolTimeoutMs);
|
|
454
|
+
if (customTools.length > 0)
|
|
455
|
+
tools.push(...customTools);
|
|
456
|
+
// Remove task_complete from interactive mode - not needed
|
|
457
|
+
const interactiveTools = tools.filter((t) => t.name !== 'task_complete');
|
|
458
|
+
// Real prompt-token count from the API's last reply (0 until the first call),
|
|
459
|
+
// so /status reports the true context size instead of a chars/4 guess.
|
|
460
|
+
let lastPromptTokens = 0;
|
|
461
|
+
const chatTracked = async (msgs, toolsArg) => {
|
|
462
|
+
const r = await provider.chat(msgs, toolsArg);
|
|
463
|
+
if (r.usage && r.usage.prompt_tokens > 0)
|
|
464
|
+
lastPromptTokens = r.usage.prompt_tokens;
|
|
465
|
+
return r;
|
|
466
|
+
};
|
|
467
|
+
// Readline interface
|
|
468
|
+
const rl = readline.createInterface({
|
|
469
|
+
input: process.stdin,
|
|
470
|
+
output: process.stdout,
|
|
471
|
+
prompt: chalk_1.default.cyan('you > '),
|
|
472
|
+
});
|
|
473
|
+
rl.prompt();
|
|
474
|
+
rl.on('line', async (line) => {
|
|
475
|
+
const input = line.trim();
|
|
476
|
+
if (!input) {
|
|
477
|
+
rl.prompt();
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
// ── Slash Commands ──
|
|
481
|
+
if (input.startsWith('/')) {
|
|
482
|
+
const [cmd, ...args] = input.split(' ');
|
|
483
|
+
switch (cmd) {
|
|
484
|
+
case '/help':
|
|
485
|
+
console.log(HELP_TEXT);
|
|
486
|
+
break;
|
|
487
|
+
case '/exit':
|
|
488
|
+
case '/quit':
|
|
489
|
+
console.log(chalk_1.default.dim(' Goodbye!'));
|
|
490
|
+
process.exit(0);
|
|
491
|
+
break; // unreachable (process.exit), but makes the fallthrough explicit
|
|
492
|
+
case '/clear':
|
|
493
|
+
messages.length = 0;
|
|
494
|
+
messages.push({ role: 'system', content: systemPrompt });
|
|
495
|
+
console.log(chalk_1.default.dim(' Conversation cleared.'));
|
|
496
|
+
break;
|
|
497
|
+
case '/context':
|
|
498
|
+
console.log(chalk_1.default.dim('─'.repeat(60)));
|
|
499
|
+
console.log(ctx.summary);
|
|
500
|
+
console.log(chalk_1.default.dim('─'.repeat(60)));
|
|
501
|
+
break;
|
|
502
|
+
case '/model':
|
|
503
|
+
console.log(` Provider: ${chalk_1.default.bold(config.provider)}`);
|
|
504
|
+
console.log(` Model: ${chalk_1.default.bold(getModelName(config))}`);
|
|
505
|
+
console.log(` Context: ${chalk_1.default.bold(config.contextWindowLimit.toLocaleString())} tokens`);
|
|
506
|
+
break;
|
|
507
|
+
case '/config':
|
|
508
|
+
await showConfig();
|
|
509
|
+
break;
|
|
510
|
+
case '/status':
|
|
511
|
+
console.log(` Messages in context: ${chalk_1.default.bold(String(messages.length))}`);
|
|
512
|
+
if (lastPromptTokens > 0) {
|
|
513
|
+
console.log(` Context tokens: ${chalk_1.default.bold(lastPromptTokens.toLocaleString())} ${chalk_1.default.dim('(from API)')}`);
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
const estimatedTokens = Math.round(messages.reduce((sum, m) => sum + (m.content?.length || 0), 0) / 4);
|
|
517
|
+
console.log(` Context tokens: ${chalk_1.default.bold('~' + estimatedTokens.toLocaleString())} ${chalk_1.default.dim('(estimated, no API call yet)')}`);
|
|
518
|
+
}
|
|
519
|
+
console.log(` Tasks in queue: ${chalk_1.default.bold(String(taskQueue.length))}`);
|
|
520
|
+
break;
|
|
521
|
+
case '/run': {
|
|
522
|
+
const task = args.join(' ');
|
|
523
|
+
if (!task) {
|
|
524
|
+
console.log(chalk_1.default.red(' Usage: /run <task description>'));
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
console.log(chalk_1.default.dim(` Starting autonomous agent for: "${task}"...`));
|
|
528
|
+
console.log(chalk_1.default.dim(' Press Ctrl+C to interrupt.\n'));
|
|
529
|
+
// Run as autonomous agent
|
|
530
|
+
const agentConfig = { ...config };
|
|
531
|
+
agentConfig.systemPrompt += `\n\nPROJECT CONTEXT:\n${ctx.summary}`;
|
|
532
|
+
const agentLogger = new logger_1.Logger(config.logDir, logLevel);
|
|
533
|
+
const agent = new agent_1.Agent(agentConfig, agentLogger);
|
|
534
|
+
// Start dashboard if --ui flag OR enabled in config
|
|
535
|
+
let dashboard = null;
|
|
536
|
+
if (options.ui || config.dashboard.enabled) {
|
|
537
|
+
const port = options.port ? parseInt(options.port, 10) : config.dashboard.port;
|
|
538
|
+
dashboard = new dashboard_1.DashboardServer(port, agentLogger, options.host || '127.0.0.1');
|
|
539
|
+
agent.setDashboard(dashboard);
|
|
540
|
+
try {
|
|
541
|
+
await dashboard.start();
|
|
542
|
+
console.log(chalk_1.default.bold.green(` Dashboard: http://localhost:${port}\n`));
|
|
543
|
+
}
|
|
544
|
+
catch { /* ignore */ }
|
|
545
|
+
}
|
|
546
|
+
const result = await agent.run(task, false);
|
|
547
|
+
if (dashboard)
|
|
548
|
+
await dashboard.stop();
|
|
549
|
+
if (result.success) {
|
|
550
|
+
console.log(chalk_1.default.green.bold(`\n Task completed! (${result.stats.tasksCompleted} task(s))`));
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
console.log(chalk_1.default.yellow.bold('\n Task did not complete.'));
|
|
554
|
+
}
|
|
555
|
+
console.log(chalk_1.default.white(' ' + result.summary.substring(0, 300)));
|
|
556
|
+
console.log('');
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
case '/checkpoints': {
|
|
560
|
+
const mgr = new checkpoint_1.CheckpointManager(config.checkpointDir, new logger_1.Logger(config.logDir, logger_1.LogLevel.ERROR));
|
|
561
|
+
const list = mgr.list();
|
|
562
|
+
if (list.length === 0) {
|
|
563
|
+
console.log(chalk_1.default.dim(' No checkpoints found. They are created automatically while an agent runs.'));
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
console.log(chalk_1.default.bold(` ${list.length} checkpoint(s) (newest first):`));
|
|
567
|
+
for (const cp of list) {
|
|
568
|
+
console.log(` ${chalk_1.default.cyan(cp.timestamp)} iteration ${chalk_1.default.bold(String(cp.iteration))}`);
|
|
569
|
+
}
|
|
570
|
+
console.log(chalk_1.default.dim(' Use /resume to continue from the latest checkpoint.'));
|
|
571
|
+
}
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
case '/resume': {
|
|
575
|
+
const mgr = new checkpoint_1.CheckpointManager(config.checkpointDir, new logger_1.Logger(config.logDir, logger_1.LogLevel.ERROR));
|
|
576
|
+
const latest = mgr.loadLatest();
|
|
577
|
+
if (!latest) {
|
|
578
|
+
console.log(chalk_1.default.yellow(' No checkpoint to resume from.'));
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
const task = args.join(' ') || latest.taskDescription;
|
|
582
|
+
console.log(chalk_1.default.dim(` Resuming from checkpoint at iteration ${latest.iteration}...`));
|
|
583
|
+
console.log(chalk_1.default.dim(` Task: ${task.substring(0, 100)}`));
|
|
584
|
+
console.log(chalk_1.default.dim(' Press Ctrl+C to interrupt.\n'));
|
|
585
|
+
const agentConfig = { ...config };
|
|
586
|
+
agentConfig.systemPrompt += `\n\nPROJECT CONTEXT:\n${ctx.summary}`;
|
|
587
|
+
const agentLogger = new logger_1.Logger(config.logDir, logLevel);
|
|
588
|
+
const agent = new agent_1.Agent(agentConfig, agentLogger);
|
|
589
|
+
let dashboard = null;
|
|
590
|
+
if (options.ui || config.dashboard.enabled) {
|
|
591
|
+
const port = options.port ? parseInt(options.port, 10) : config.dashboard.port;
|
|
592
|
+
dashboard = new dashboard_1.DashboardServer(port, agentLogger, options.host || '127.0.0.1');
|
|
593
|
+
agent.setDashboard(dashboard);
|
|
594
|
+
try {
|
|
595
|
+
await dashboard.start();
|
|
596
|
+
console.log(chalk_1.default.bold.green(` Dashboard: http://localhost:${port}\n`));
|
|
597
|
+
}
|
|
598
|
+
catch { /* ignore */ }
|
|
599
|
+
}
|
|
600
|
+
const result = await agent.run(task, true); // resume=true
|
|
601
|
+
if (dashboard)
|
|
602
|
+
await dashboard.stop();
|
|
603
|
+
if (result.success) {
|
|
604
|
+
console.log(chalk_1.default.green.bold(`\n Task completed! (${result.stats.tasksCompleted} task(s))`));
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
console.log(chalk_1.default.yellow.bold('\n Task did not complete.'));
|
|
608
|
+
}
|
|
609
|
+
console.log(chalk_1.default.white(' ' + result.summary.substring(0, 300)));
|
|
610
|
+
console.log('');
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
default:
|
|
614
|
+
console.log(chalk_1.default.red(` Unknown command: ${cmd}. Type /help for available commands.`));
|
|
615
|
+
}
|
|
616
|
+
rl.prompt();
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
// ── Normal Chat ──
|
|
620
|
+
messages.push({ role: 'user', content: input });
|
|
621
|
+
// Show thinking indicator
|
|
622
|
+
process.stdout.write(chalk_1.default.dim(' thinking...'));
|
|
623
|
+
try {
|
|
624
|
+
// Call LLM
|
|
625
|
+
const response = await chatTracked(messages, interactiveTools);
|
|
626
|
+
// Clear thinking indicator
|
|
627
|
+
process.stdout.write('\r' + ' '.repeat(30) + '\r');
|
|
628
|
+
// Handle tool calls
|
|
629
|
+
if (response.tool_calls.length > 0) {
|
|
630
|
+
// Add assistant message with tool calls
|
|
631
|
+
messages.push({
|
|
632
|
+
role: 'assistant',
|
|
633
|
+
content: response.content || '',
|
|
634
|
+
tool_calls: response.tool_calls,
|
|
635
|
+
});
|
|
636
|
+
if (response.content) {
|
|
637
|
+
console.log(chalk_1.default.green(' ai > ') + response.content);
|
|
638
|
+
}
|
|
639
|
+
// Execute tools
|
|
640
|
+
for (const tc of response.tool_calls) {
|
|
641
|
+
const tool = interactiveTools.find((t) => t.name === tc.function.name);
|
|
642
|
+
if (!tool) {
|
|
643
|
+
console.log(chalk_1.default.red(` Unknown tool: ${tc.function.name}`));
|
|
644
|
+
messages.push({ role: 'tool', tool_call_id: tc.id, content: 'Unknown tool' });
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
let args;
|
|
648
|
+
try {
|
|
649
|
+
args = JSON.parse(tc.function.arguments);
|
|
650
|
+
}
|
|
651
|
+
catch {
|
|
652
|
+
messages.push({ role: 'tool', tool_call_id: tc.id, content: 'Invalid JSON arguments' });
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
console.log(chalk_1.default.magenta(` [${tc.function.name}]`) + chalk_1.default.dim(` ${JSON.stringify(args).substring(0, 100)}`));
|
|
656
|
+
try {
|
|
657
|
+
const result = await tool.execute(args);
|
|
658
|
+
messages.push({ role: 'tool', tool_call_id: tc.id, content: result });
|
|
659
|
+
// Show truncated result
|
|
660
|
+
const displayResult = result.length > 500 ? result.substring(0, 500) + '...' : result;
|
|
661
|
+
console.log(chalk_1.default.dim(' ' + displayResult.split('\n').join('\n ')));
|
|
662
|
+
}
|
|
663
|
+
catch (err) {
|
|
664
|
+
messages.push({ role: 'tool', tool_call_id: tc.id, content: `Error: ${err.message}` });
|
|
665
|
+
console.log(chalk_1.default.red(` Error: ${err.message}`));
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
// Get follow-up response after tool results
|
|
669
|
+
const followUp = await chatTracked(messages, interactiveTools);
|
|
670
|
+
// Always show the follow-up text, but only push a content-only
|
|
671
|
+
// assistant message when there are NO tool calls. If tool_calls are
|
|
672
|
+
// present, the loop below pushes the assistant message together with
|
|
673
|
+
// its tool_calls — pushing here too would duplicate the turn and
|
|
674
|
+
// orphan the text from its tool calls, corrupting the history.
|
|
675
|
+
if (followUp.content) {
|
|
676
|
+
console.log('');
|
|
677
|
+
console.log(chalk_1.default.green(' ai > ') + followUp.content);
|
|
678
|
+
if (followUp.tool_calls.length === 0) {
|
|
679
|
+
messages.push({ role: 'assistant', content: followUp.content });
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
// Handle nested tool calls (up to 5 rounds)
|
|
683
|
+
let depth = 0;
|
|
684
|
+
let currentResponse = followUp;
|
|
685
|
+
while (currentResponse.tool_calls.length > 0 && depth < 5) {
|
|
686
|
+
depth++;
|
|
687
|
+
messages.push({
|
|
688
|
+
role: 'assistant',
|
|
689
|
+
content: currentResponse.content || '',
|
|
690
|
+
tool_calls: currentResponse.tool_calls,
|
|
691
|
+
});
|
|
692
|
+
for (const tc of currentResponse.tool_calls) {
|
|
693
|
+
const tool = interactiveTools.find((t) => t.name === tc.function.name);
|
|
694
|
+
if (!tool) {
|
|
695
|
+
messages.push({ role: 'tool', tool_call_id: tc.id, content: 'Unknown tool' });
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
let args;
|
|
699
|
+
try {
|
|
700
|
+
args = JSON.parse(tc.function.arguments);
|
|
701
|
+
}
|
|
702
|
+
catch {
|
|
703
|
+
args = {};
|
|
704
|
+
}
|
|
705
|
+
console.log(chalk_1.default.magenta(` [${tc.function.name}]`) + chalk_1.default.dim(` ${JSON.stringify(args).substring(0, 100)}`));
|
|
706
|
+
try {
|
|
707
|
+
const result = await tool.execute(args);
|
|
708
|
+
messages.push({ role: 'tool', tool_call_id: tc.id, content: result });
|
|
709
|
+
const displayResult = result.length > 500 ? result.substring(0, 500) + '...' : result;
|
|
710
|
+
console.log(chalk_1.default.dim(' ' + displayResult.split('\n').join('\n ')));
|
|
711
|
+
}
|
|
712
|
+
catch (err) {
|
|
713
|
+
messages.push({ role: 'tool', tool_call_id: tc.id, content: `Error: ${err.message}` });
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
currentResponse = await chatTracked(messages, interactiveTools);
|
|
717
|
+
// Same rule as above: only push content-only here. If this response
|
|
718
|
+
// has tool_calls, the next loop iteration pushes it with them.
|
|
719
|
+
if (currentResponse.content) {
|
|
720
|
+
console.log('');
|
|
721
|
+
console.log(chalk_1.default.green(' ai > ') + currentResponse.content);
|
|
722
|
+
if (currentResponse.tool_calls.length === 0) {
|
|
723
|
+
messages.push({ role: 'assistant', content: currentResponse.content });
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
// Simple text response
|
|
730
|
+
messages.push({ role: 'assistant', content: response.content || '' });
|
|
731
|
+
console.log(chalk_1.default.green(' ai > ') + (response.content || ''));
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
catch (err) {
|
|
735
|
+
process.stdout.write('\r' + ' '.repeat(30) + '\r');
|
|
736
|
+
console.log(chalk_1.default.red(` Error: ${err.message}`));
|
|
737
|
+
}
|
|
738
|
+
console.log('');
|
|
739
|
+
rl.prompt();
|
|
740
|
+
});
|
|
741
|
+
rl.on('close', () => {
|
|
742
|
+
console.log(chalk_1.default.dim('\n Goodbye!'));
|
|
743
|
+
process.exit(0);
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
// ─────────────────────────────────────────────────────────────
|
|
747
|
+
// Helpers
|
|
748
|
+
// ─────────────────────────────────────────────────────────────
|
|
749
|
+
function getModelName(config) {
|
|
750
|
+
// When a unified "provider/model" selector is in use, show it verbatim
|
|
751
|
+
// (e.g. "groq/llama-3.3-70b-versatile") — that's what the user typed.
|
|
752
|
+
if ((0, registry_1.isModelRef)(config.model))
|
|
753
|
+
return config.model;
|
|
754
|
+
if (config.provider === 'openai')
|
|
755
|
+
return config.openai?.model || 'gpt-4o';
|
|
756
|
+
return config.anthropic?.model || 'claude-sonnet-4-20250514';
|
|
757
|
+
}
|
|
758
|
+
/** Display label for the active provider: registry name if set, else protocol. */
|
|
759
|
+
function getProviderLabel(config) {
|
|
760
|
+
if ((0, registry_1.isModelRef)(config.model))
|
|
761
|
+
return config.model.slice(0, config.model.indexOf('/'));
|
|
762
|
+
return config.provider;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Layer 2 of the context-window sync: when the model wasn't found in the
|
|
766
|
+
* models.dev catalog (contextLimitSource === 'static') and the user didn't pin
|
|
767
|
+
* a limit, ask the provider's own OpenAI-style /models endpoint for the real
|
|
768
|
+
* context window. Useful for custom proxies (e.g. LimitRouter) and models the
|
|
769
|
+
* catalog doesn't list. Best-effort and quiet: any failure leaves the static
|
|
770
|
+
* fallback untouched. Skips native Anthropic (no baseURL / no /models).
|
|
771
|
+
*/
|
|
772
|
+
async function refineContextLimitFromEndpoint(config, verbose) {
|
|
773
|
+
if ((0, config_1.getConfigSources)().contextLimitSource !== 'static')
|
|
774
|
+
return;
|
|
775
|
+
const baseURL = config.provider === 'openai' ? config.openai?.baseURL : config.anthropic?.baseURL;
|
|
776
|
+
const apiKey = config.provider === 'openai' ? config.openai?.apiKey : config.anthropic?.apiKey;
|
|
777
|
+
const modelId = config.provider === 'openai' ? config.openai?.model : config.anthropic?.model;
|
|
778
|
+
if (!baseURL || !modelId)
|
|
779
|
+
return;
|
|
780
|
+
const live = await (0, modelsdev_1.fetchEndpointContextLimit)(baseURL, apiKey, modelId);
|
|
781
|
+
if (live && live > 0) {
|
|
782
|
+
config.contextWindowLimit = Math.floor(live * 0.9);
|
|
783
|
+
(0, config_1.markContextLimitSource)('endpoint');
|
|
784
|
+
if (verbose) {
|
|
785
|
+
console.log(chalk_1.default.dim(` Context window ${live.toLocaleString()} tokens from ${baseURL}/models`));
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
/** Print any config warnings collected during loadConfig(). */
|
|
790
|
+
function printConfigWarnings() {
|
|
791
|
+
const { warnings } = (0, config_1.getConfigSources)();
|
|
792
|
+
if (warnings.length > 0) {
|
|
793
|
+
console.log(chalk_1.default.yellow.bold(' Config warnings:'));
|
|
794
|
+
for (const w of warnings) {
|
|
795
|
+
console.log(chalk_1.default.yellow(` ! ${w}`));
|
|
796
|
+
}
|
|
797
|
+
console.log('');
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
/** Mask a secret, showing only the last 4 chars. */
|
|
801
|
+
function maskSecret(secret) {
|
|
802
|
+
if (!secret)
|
|
803
|
+
return chalk_1.default.red('(not set)');
|
|
804
|
+
if (secret.length <= 4)
|
|
805
|
+
return '****';
|
|
806
|
+
return '****' + secret.slice(-4);
|
|
807
|
+
}
|
|
808
|
+
/** Print the resolved config and its sources (for `notsleep config`). */
|
|
809
|
+
async function showConfig() {
|
|
810
|
+
// Show config file locations first (before validation can throw).
|
|
811
|
+
const globalPath = (0, fileconfig_1.getGlobalConfigPath)();
|
|
812
|
+
const projectPath = (0, fileconfig_1.findProjectConfig)();
|
|
813
|
+
console.log(chalk_1.default.bold(' Config files:'));
|
|
814
|
+
console.log(` Global: ${fs.existsSync(globalPath) ? globalPath : chalk_1.default.dim(globalPath + ' (none)')}`);
|
|
815
|
+
console.log(` Project: ${projectPath ? projectPath : chalk_1.default.dim('(none found)')}`);
|
|
816
|
+
console.log('');
|
|
817
|
+
let config;
|
|
818
|
+
try {
|
|
819
|
+
config = (0, config_1.loadConfig)();
|
|
820
|
+
}
|
|
821
|
+
catch (err) {
|
|
822
|
+
// loadConfig() ran far enough to populate (filtered) warnings — show them.
|
|
823
|
+
printConfigWarnings();
|
|
824
|
+
// Still show what we can even if validation fails.
|
|
825
|
+
console.log(chalk_1.default.yellow(` Warning: ${err.message}`));
|
|
826
|
+
const { config: file } = (0, fileconfig_1.loadFileConfig)();
|
|
827
|
+
console.log('');
|
|
828
|
+
console.log(chalk_1.default.bold(' Partial config (from files/env):'));
|
|
829
|
+
console.log(' ' + JSON.stringify(file, null, 2).split('\n').join('\n '));
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
// Layer 2 endpoint fallback, same as a real run, so the shown limit matches.
|
|
833
|
+
await refineContextLimitFromEndpoint(config, false);
|
|
834
|
+
// Surface filtered config warnings (malformed env refs, missing active-provider vars).
|
|
835
|
+
printConfigWarnings();
|
|
836
|
+
const sources = (0, config_1.getConfigSources)();
|
|
837
|
+
const limitSourceLabel = sources.contextLimitSource === 'catalog' ? chalk_1.default.dim(' (models.dev)')
|
|
838
|
+
: sources.contextLimitSource === 'endpoint' ? chalk_1.default.dim(' (/models endpoint)')
|
|
839
|
+
: sources.contextLimitSource === 'explicit' ? chalk_1.default.dim(' (set)')
|
|
840
|
+
: chalk_1.default.dim(' (static table)');
|
|
841
|
+
const key = config.provider === 'openai' ? config.openai?.apiKey : config.anthropic?.apiKey;
|
|
842
|
+
console.log(chalk_1.default.bold(' Resolved configuration:'));
|
|
843
|
+
console.log(` Provider: ${chalk_1.default.cyan(getProviderLabel(config))}`);
|
|
844
|
+
console.log(` Model: ${chalk_1.default.cyan(getModelName(config))}`);
|
|
845
|
+
if (config.smallModel) {
|
|
846
|
+
console.log(` Small model: ${chalk_1.default.cyan(config.smallModel.model)} ${chalk_1.default.dim('(summarize / task-gen)')}`);
|
|
847
|
+
}
|
|
848
|
+
console.log(` API key: ${maskSecret(key || '')}`);
|
|
849
|
+
const baseURL = config.provider === 'openai' ? config.openai?.baseURL : config.anthropic?.baseURL;
|
|
850
|
+
if (baseURL)
|
|
851
|
+
console.log(` Base URL: ${baseURL}`);
|
|
852
|
+
console.log(` Max iterations: ${(0, types_1.isUnlimitedIterations)(config.maxIterations) ? 'unlimited' : config.maxIterations}`);
|
|
853
|
+
console.log(` Max iter/task: ${config.maxIterationsPerTask > 0 ? config.maxIterationsPerTask : 'no cap'}`);
|
|
854
|
+
console.log(` Context limit: ${config.contextWindowLimit.toLocaleString()} tokens${limitSourceLabel}`);
|
|
855
|
+
console.log(` Summarize at: ${Math.round(config.summarizeThreshold * 100)}%`);
|
|
856
|
+
console.log(` Max retries: ${config.maxRetries}`);
|
|
857
|
+
console.log(` Tool timeout: ${config.toolTimeoutMs}ms`);
|
|
858
|
+
console.log(` Continuous mode: ${config.continuous ? chalk_1.default.green('on') : chalk_1.default.dim('off')}`);
|
|
859
|
+
console.log(` Max cost (USD): ${config.maxCostUsd ? chalk_1.default.cyan('$' + config.maxCostUsd) : chalk_1.default.dim('no limit')}`);
|
|
860
|
+
console.log(` Auto-commit: ${config.autoCommit ? chalk_1.default.green('on (local commits per task)') : chalk_1.default.dim('off')}`);
|
|
861
|
+
console.log(` Dashboard: ${config.dashboard.enabled ? chalk_1.default.green(`on (port ${config.dashboard.port})`) : chalk_1.default.dim('off')}`);
|
|
862
|
+
console.log(` Checkpoint dir: ${config.checkpointDir}`);
|
|
863
|
+
console.log(` Log dir: ${config.logDir}`);
|
|
864
|
+
console.log(` Stop file: ${config.stopFile}`);
|
|
865
|
+
if (sources.instructionFiles.length > 0) {
|
|
866
|
+
console.log(` Instructions: ${chalk_1.default.cyan(sources.instructionFiles.join(', '))}`);
|
|
867
|
+
}
|
|
868
|
+
console.log('');
|
|
869
|
+
console.log(chalk_1.default.dim(` .env loaded: ${sources.envLoaded ? 'yes' : 'no'}`));
|
|
870
|
+
if (sources.fileConfigSources.length > 0) {
|
|
871
|
+
console.log(chalk_1.default.dim(` Config sources: ${sources.fileConfigSources.join(', ')}`));
|
|
872
|
+
}
|
|
873
|
+
console.log('');
|
|
874
|
+
console.log(chalk_1.default.dim(' Run "notsleep init" to create or update your config file.'));
|
|
875
|
+
}
|
|
876
|
+
// Run
|
|
877
|
+
main().catch((err) => {
|
|
878
|
+
console.error(chalk_1.default.red(`Fatal error: ${err.message}`));
|
|
879
|
+
console.error(err.stack);
|
|
880
|
+
process.exit(1);
|
|
881
|
+
});
|
|
882
|
+
//# sourceMappingURL=index.js.map
|