@lazyingart/agintiflow 0.8.5 → 0.8.7
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/README.md +8 -0
- package/package.json +1 -1
- package/src/agent-runner.js +29 -17
- package/src/auth-onboarding.js +62 -0
- package/src/cli.js +32 -10
- package/src/interactive-cli.js +185 -53
- package/src/project.js +5 -0
package/README.md
CHANGED
|
@@ -46,6 +46,14 @@ aginti --list-profiles
|
|
|
46
46
|
aginti --sandbox-status
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
On first interactive use, if no DeepSeek key is detected, `aginti` asks you to paste it and saves it to the project-local ignored file `.aginti/.env` with `0600` permissions. You can also set it explicitly:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
aginti login deepseek
|
|
53
|
+
# or non-interactively:
|
|
54
|
+
printf '%s' "$DEEPSEEK_API_KEY" | aginti keys set deepseek --stdin
|
|
55
|
+
```
|
|
56
|
+
|
|
49
57
|
Start an interactive Codex-style CLI chat from any project folder:
|
|
50
58
|
|
|
51
59
|
```bash
|
package/package.json
CHANGED
package/src/agent-runner.js
CHANGED
|
@@ -329,6 +329,16 @@ function createObservers(config) {
|
|
|
329
329
|
};
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
function emitConsole(config, value = "", options = {}) {
|
|
333
|
+
if (typeof config.onConsole === "function") {
|
|
334
|
+
config.onConsole(String(value), options);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (options.error) console.error(value);
|
|
339
|
+
else console.log(value);
|
|
340
|
+
}
|
|
341
|
+
|
|
332
342
|
function createBrowserState() {
|
|
333
343
|
return {
|
|
334
344
|
browser: null,
|
|
@@ -1033,25 +1043,27 @@ export async function runAgent(config) {
|
|
|
1033
1043
|
startUrl: config.startUrl,
|
|
1034
1044
|
});
|
|
1035
1045
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1046
|
+
emitConsole(config, `Session: ${sessionId}`, { kind: "meta" });
|
|
1047
|
+
emitConsole(config, `Provider: ${config.provider}`, { kind: "meta" });
|
|
1048
|
+
emitConsole(config, `Model: ${config.model}`, { kind: "meta" });
|
|
1049
|
+
emitConsole(config, `Routing: ${config.routingMode} (${config.routeReason})`, { kind: "meta" });
|
|
1050
|
+
emitConsole(config, `Workspace: ${config.commandCwd}`, { kind: "meta" });
|
|
1051
|
+
emitConsole(config, `Sessions: ${config.sessionsDir}`, { kind: "meta" });
|
|
1042
1052
|
if (config.useDockerSandbox) {
|
|
1043
|
-
|
|
1044
|
-
|
|
1053
|
+
emitConsole(
|
|
1054
|
+
config,
|
|
1055
|
+
`Docker: image=${config.dockerSandboxImage} mode=${config.sandboxMode} packagePolicy=${config.packageInstallPolicy}`,
|
|
1056
|
+
{ kind: "meta" }
|
|
1045
1057
|
);
|
|
1046
|
-
|
|
1047
|
-
|
|
1058
|
+
emitConsole(config, `Docker workspace: /workspace -> ${config.commandCwd}`, { kind: "meta" });
|
|
1059
|
+
emitConsole(config, "Docker env: /aginti-env persistent toolchain; /aginti-cache persistent caches", { kind: "meta" });
|
|
1048
1060
|
} else if (config.allowShellTool) {
|
|
1049
|
-
|
|
1061
|
+
emitConsole(config, `Shell: host policy=${config.packageInstallPolicy}`, { kind: "meta" });
|
|
1050
1062
|
}
|
|
1051
1063
|
if (state.plan) {
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1064
|
+
emitConsole(config, "\nPlan:", { kind: "heading" });
|
|
1065
|
+
emitConsole(config, state.plan, { kind: "plan", markdown: true });
|
|
1066
|
+
emitConsole(config, "", { kind: "meta" });
|
|
1055
1067
|
}
|
|
1056
1068
|
|
|
1057
1069
|
for (let step = state.stepsCompleted + 1; step <= config.maxSteps; step += 1) {
|
|
@@ -1139,7 +1151,7 @@ export async function runAgent(config) {
|
|
|
1139
1151
|
result: fallback,
|
|
1140
1152
|
sessionId,
|
|
1141
1153
|
});
|
|
1142
|
-
|
|
1154
|
+
emitConsole(config, fallback, { kind: "assistant", markdown: true });
|
|
1143
1155
|
state.stepsCompleted = step;
|
|
1144
1156
|
state.updatedAt = new Date().toISOString();
|
|
1145
1157
|
await store.saveState(state);
|
|
@@ -1209,7 +1221,7 @@ export async function runAgent(config) {
|
|
|
1209
1221
|
result: toolResult.result,
|
|
1210
1222
|
sessionId,
|
|
1211
1223
|
});
|
|
1212
|
-
|
|
1224
|
+
emitConsole(config, toolResult.result, { kind: "assistant", markdown: true });
|
|
1213
1225
|
return {
|
|
1214
1226
|
sessionId,
|
|
1215
1227
|
result: toolResult.result,
|
|
@@ -1232,7 +1244,7 @@ export async function runAgent(config) {
|
|
|
1232
1244
|
reason: "max_steps_reached",
|
|
1233
1245
|
sessionId,
|
|
1234
1246
|
});
|
|
1235
|
-
|
|
1247
|
+
emitConsole(config, `Stopped after ${config.maxSteps} steps without finish().`, { kind: "error", error: true });
|
|
1236
1248
|
return {
|
|
1237
1249
|
sessionId,
|
|
1238
1250
|
result: "",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import readline from "node:readline/promises";
|
|
2
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
import { Writable } from "node:stream";
|
|
4
|
+
import { providerKeyStatus, setProviderKey } from "./project.js";
|
|
5
|
+
|
|
6
|
+
class MutedWritable extends Writable {
|
|
7
|
+
constructor(target) {
|
|
8
|
+
super();
|
|
9
|
+
this.target = target;
|
|
10
|
+
this.muted = false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
_write(chunk, encoding, callback) {
|
|
14
|
+
if (!this.muted) this.target.write(chunk, encoding);
|
|
15
|
+
callback();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function promptHidden(promptText) {
|
|
20
|
+
if (!input.isTTY || !output.isTTY) return "";
|
|
21
|
+
|
|
22
|
+
const mutedOutput = new MutedWritable(output);
|
|
23
|
+
const rl = readline.createInterface({
|
|
24
|
+
input,
|
|
25
|
+
output: mutedOutput,
|
|
26
|
+
terminal: true,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
output.write(promptText);
|
|
31
|
+
mutedOutput.muted = true;
|
|
32
|
+
const value = await rl.question("");
|
|
33
|
+
output.write("\n");
|
|
34
|
+
return String(value || "").trim();
|
|
35
|
+
} finally {
|
|
36
|
+
mutedOutput.muted = false;
|
|
37
|
+
rl.close();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function shouldPromptForDeepSeek(args = {}, projectRoot = process.cwd()) {
|
|
42
|
+
const provider = String(args.provider || "").toLowerCase();
|
|
43
|
+
if (provider === "mock" || provider === "openai") return false;
|
|
44
|
+
if (process.env.AGINTIFLOW_NO_AUTH_PROMPT === "1") return false;
|
|
45
|
+
if (!input.isTTY || !output.isTTY) return false;
|
|
46
|
+
return !providerKeyStatus(projectRoot).deepseek;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function promptAndSaveDeepSeekKey(projectRoot = process.cwd(), options = {}) {
|
|
50
|
+
const key = await promptHidden(
|
|
51
|
+
options.promptText || "DeepSeek API key not found. Paste DEEPSEEK_API_KEY to save locally, or press Enter to skip: "
|
|
52
|
+
);
|
|
53
|
+
if (!key) return { saved: false, skipped: true };
|
|
54
|
+
|
|
55
|
+
const result = await setProviderKey(projectRoot, "deepseek", key);
|
|
56
|
+
return {
|
|
57
|
+
saved: true,
|
|
58
|
+
provider: result.provider,
|
|
59
|
+
keyName: result.keyName,
|
|
60
|
+
path: result.path,
|
|
61
|
+
};
|
|
62
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
showProjectSession,
|
|
16
16
|
} from "./project.js";
|
|
17
17
|
import { listTaskProfiles } from "./task-profiles.js";
|
|
18
|
+
import { promptAndSaveDeepSeekKey, promptHidden, shouldPromptForDeepSeek } from "./auth-onboarding.js";
|
|
18
19
|
import fs from "node:fs/promises";
|
|
19
20
|
import path from "node:path";
|
|
20
21
|
import { fileURLToPath } from "node:url";
|
|
@@ -219,7 +220,7 @@ export function parseArgs(argv) {
|
|
|
219
220
|
|
|
220
221
|
function printUsage() {
|
|
221
222
|
console.log(
|
|
222
|
-
'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|mock] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-destructive] [--allow-file-tools|--no-file-tools] [--allow-wrappers --wrapper codex] [--sandbox-status|--sandbox-preflight] "your task"'
|
|
223
|
+
'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti login deepseek OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|mock] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-destructive] [--allow-file-tools|--no-file-tools] [--allow-wrappers --wrapper codex] [--sandbox-status|--sandbox-preflight] "your task"'
|
|
223
224
|
);
|
|
224
225
|
}
|
|
225
226
|
|
|
@@ -298,6 +299,21 @@ async function readStdin() {
|
|
|
298
299
|
return input.trim();
|
|
299
300
|
}
|
|
300
301
|
|
|
302
|
+
async function ensureDeepSeekKeyForOneShot(args) {
|
|
303
|
+
if (!shouldPromptForDeepSeek(args, process.cwd())) return true;
|
|
304
|
+
console.log("DeepSeek API key is not configured for this project.");
|
|
305
|
+
console.log("Paste it once to save it in `.aginti/.env` with 0600 permissions, or press Enter to cancel.");
|
|
306
|
+
const result = await promptAndSaveDeepSeekKey(process.cwd(), {
|
|
307
|
+
promptText: "DeepSeek API key: ",
|
|
308
|
+
});
|
|
309
|
+
if (result.saved) {
|
|
310
|
+
console.log(`saved ${result.keyName} to project-local ignored env`);
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
console.error("No DeepSeek key saved. Run `aginti login deepseek` later, or use `--provider mock` for local tests.");
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
|
|
301
317
|
async function handleKeyCommand(argv) {
|
|
302
318
|
const [verb = "status", provider = ""] = argv;
|
|
303
319
|
if (verb === "status") {
|
|
@@ -313,17 +329,17 @@ async function handleKeyCommand(argv) {
|
|
|
313
329
|
|
|
314
330
|
if (verb === "set") {
|
|
315
331
|
const target = provider || "deepseek";
|
|
316
|
-
|
|
317
|
-
|
|
332
|
+
const key = argv.includes("--stdin") ? await readStdin() : await promptHidden(`${target === "openai" ? "OpenAI" : "DeepSeek"} API key: `);
|
|
333
|
+
if (!key) {
|
|
334
|
+
console.error("No key saved.");
|
|
318
335
|
process.exit(1);
|
|
319
336
|
}
|
|
320
|
-
const key = await readStdin();
|
|
321
337
|
const result = await setProviderKey(process.cwd(), target, key);
|
|
322
338
|
console.log(`saved ${result.provider} key to project-local ignored env (${result.keyName})`);
|
|
323
339
|
return;
|
|
324
340
|
}
|
|
325
341
|
|
|
326
|
-
console.error("Usage: aginti keys status OR aginti keys set deepseek --stdin");
|
|
342
|
+
console.error("Usage: aginti keys status OR aginti keys set deepseek [--stdin]");
|
|
327
343
|
process.exit(1);
|
|
328
344
|
}
|
|
329
345
|
|
|
@@ -442,11 +458,13 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
442
458
|
|
|
443
459
|
if (argv[0] === "login") {
|
|
444
460
|
const provider = argv[1] || "deepseek";
|
|
445
|
-
|
|
446
|
-
|
|
461
|
+
const key = argv.includes("--stdin") || !process.stdin.isTTY
|
|
462
|
+
? await readStdin()
|
|
463
|
+
: await promptHidden(`${provider === "openai" ? "OpenAI" : "DeepSeek"} API key: `);
|
|
464
|
+
if (!key) {
|
|
465
|
+
console.error("No key saved.");
|
|
447
466
|
process.exit(1);
|
|
448
467
|
}
|
|
449
|
-
const key = await readStdin();
|
|
450
468
|
const result = await setProviderKey(process.cwd(), provider, key);
|
|
451
469
|
console.log(`saved ${result.provider} key to project-local ignored env (${result.keyName})`);
|
|
452
470
|
return;
|
|
@@ -478,7 +496,9 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
478
496
|
});
|
|
479
497
|
return;
|
|
480
498
|
}
|
|
481
|
-
const
|
|
499
|
+
const resumeArgs = agentDefaults({ ...parseArgs([prompt]), resume: sessionId, goal: prompt });
|
|
500
|
+
if (!(await ensureDeepSeekKeyForOneShot(resumeArgs))) process.exit(1);
|
|
501
|
+
const config = loadConfig(resumeArgs, { packageDir });
|
|
482
502
|
await runAgent(config);
|
|
483
503
|
return;
|
|
484
504
|
}
|
|
@@ -527,6 +547,8 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
527
547
|
process.exit(1);
|
|
528
548
|
}
|
|
529
549
|
|
|
530
|
-
const
|
|
550
|
+
const finalArgs = agentDefaults(args);
|
|
551
|
+
if (!(await ensureDeepSeekKeyForOneShot(finalArgs))) process.exit(1);
|
|
552
|
+
const config = loadConfig(finalArgs, { packageDir });
|
|
531
553
|
await runAgent(config);
|
|
532
554
|
}
|
package/src/interactive-cli.js
CHANGED
|
@@ -6,9 +6,98 @@ import { loadConfig } from "./config.js";
|
|
|
6
6
|
import { initProject, listProjectSessions } from "./project.js";
|
|
7
7
|
import { normalizePackageInstallPolicy, normalizeSandboxMode } from "./command-policy.js";
|
|
8
8
|
import { normalizeTaskProfile } from "./task-profiles.js";
|
|
9
|
+
import { promptAndSaveDeepSeekKey, shouldPromptForDeepSeek } from "./auth-onboarding.js";
|
|
10
|
+
|
|
11
|
+
const useColor = Boolean(input.isTTY && output.isTTY && !process.env.NO_COLOR);
|
|
12
|
+
const ansi = {
|
|
13
|
+
reset: "\x1b[0m",
|
|
14
|
+
bold: "\x1b[1m",
|
|
15
|
+
dim: "\x1b[2m",
|
|
16
|
+
cyan: "\x1b[36m",
|
|
17
|
+
green: "\x1b[32m",
|
|
18
|
+
yellow: "\x1b[33m",
|
|
19
|
+
red: "\x1b[31m",
|
|
20
|
+
userBg: "\x1b[48;5;24m\x1b[38;5;231m",
|
|
21
|
+
agentBg: "\x1b[48;5;29m\x1b[38;5;231m",
|
|
22
|
+
systemBg: "\x1b[48;5;236m\x1b[38;5;245m",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function color(value, ...codes) {
|
|
26
|
+
if (!useColor || codes.length === 0) return String(value);
|
|
27
|
+
return `${codes.join("")}${value}${ansi.reset}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function label(name, bgCode) {
|
|
31
|
+
return color(` ${name} `, bgCode, ansi.bold);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function userPrompt() {
|
|
35
|
+
return `\n${label("user>", ansi.userBg)} `;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function stripMarkdown(text) {
|
|
39
|
+
const lines = String(text || "").split(/\r?\n/);
|
|
40
|
+
let inFence = false;
|
|
41
|
+
const rendered = [];
|
|
42
|
+
|
|
43
|
+
for (const rawLine of lines) {
|
|
44
|
+
let line = rawLine;
|
|
45
|
+
if (/^\s*```/.test(line)) {
|
|
46
|
+
inFence = !inFence;
|
|
47
|
+
if (inFence) rendered.push(color("code", ansi.dim));
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!inFence) {
|
|
52
|
+
if (/^\s*[-*_]{3,}\s*$/.test(line)) {
|
|
53
|
+
rendered.push("");
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
|
|
57
|
+
line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
|
|
58
|
+
line = line.replace(/\*\*([^*]+)\*\*/g, (_, value) => color(value, ansi.bold));
|
|
59
|
+
line = line.replace(/__([^_]+)__/g, (_, value) => color(value, ansi.bold));
|
|
60
|
+
line = line.replace(/(^|[^\w])\*([^*\n]+)\*/g, "$1$2");
|
|
61
|
+
line = line.replace(/(^|[^\w])_([^_\n]+)_/g, "$1$2");
|
|
62
|
+
line = line.replace(/`([^`]+)`/g, (_, value) => color(value, ansi.yellow));
|
|
63
|
+
line = line.replace(/^(\s*)[-*+]\s+/, "$1- ");
|
|
64
|
+
line = line.replace(/^\s*>\s?/, " ");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
rendered.push(line);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return rendered.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function printWrapped(prefix, text) {
|
|
74
|
+
const rendered = stripMarkdown(text);
|
|
75
|
+
const lines = rendered.split("\n");
|
|
76
|
+
const gutter = " ".repeat(useColor ? 9 : prefix.length);
|
|
77
|
+
console.log(`${prefix}${lines[0] || ""}`);
|
|
78
|
+
for (const line of lines.slice(1)) {
|
|
79
|
+
console.log(`${gutter}${line}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function printAgentMessage(text) {
|
|
84
|
+
printWrapped(`${label("aginti>", ansi.agentBg)} `, text);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function printSystemLine(text) {
|
|
88
|
+
if (!String(text || "").trim()) {
|
|
89
|
+
console.log("");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
console.log(`${label("state", ansi.systemBg)} ${color(text, ansi.dim)}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function printHeading(text) {
|
|
96
|
+
console.log(color(stripMarkdown(text), ansi.bold, ansi.cyan));
|
|
97
|
+
}
|
|
9
98
|
|
|
10
99
|
function printHelp() {
|
|
11
|
-
|
|
100
|
+
printAgentMessage(
|
|
12
101
|
[
|
|
13
102
|
"Commands:",
|
|
14
103
|
" /help Show this help.",
|
|
@@ -34,18 +123,18 @@ function printHelp() {
|
|
|
34
123
|
}
|
|
35
124
|
|
|
36
125
|
function printStatus(state) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (state.lastEvent)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
126
|
+
printSystemLine(`project=${process.cwd()}`);
|
|
127
|
+
printSystemLine(`cwd=${state.commandCwd || process.cwd()}`);
|
|
128
|
+
printSystemLine(`session=${state.sessionId || "new"}`);
|
|
129
|
+
printSystemLine(`status=${state.status || "idle"}${state.activeGoal ? ` workingOn=${state.activeGoal}` : ""}`);
|
|
130
|
+
if (state.lastEvent) printSystemLine(`last=${state.lastEvent}`);
|
|
131
|
+
printSystemLine(`provider=${state.provider || "auto"} routing=${state.routingMode} model=${state.model || "auto"}`);
|
|
132
|
+
printSystemLine(`profile=${state.taskProfile} maxSteps=${state.maxSteps}`);
|
|
133
|
+
printSystemLine(
|
|
45
134
|
`shell=${state.allowShellTool} files=${state.allowFileTools} sandbox=${state.sandboxMode} installs=${state.packageInstallPolicy}`
|
|
46
135
|
);
|
|
47
136
|
if (state.sandboxMode !== "host") {
|
|
48
|
-
|
|
137
|
+
printSystemLine(`dockerWorkspace=/workspace -> ${state.commandCwd || process.cwd()}`);
|
|
49
138
|
}
|
|
50
139
|
}
|
|
51
140
|
|
|
@@ -65,7 +154,7 @@ async function latestSession() {
|
|
|
65
154
|
|
|
66
155
|
function printStatusEvent(state, label, details = "") {
|
|
67
156
|
state.lastEvent = details ? `${label}: ${details}` : label;
|
|
68
|
-
|
|
157
|
+
printSystemLine(`status=${state.status || "running"} ${state.lastEvent}`);
|
|
69
158
|
}
|
|
70
159
|
|
|
71
160
|
function attachRunInterrupts(controller) {
|
|
@@ -80,7 +169,7 @@ function attachRunInterrupts(controller) {
|
|
|
80
169
|
if (!isEscape && !isCtrlC) return;
|
|
81
170
|
if (controller.signal.aborted) return;
|
|
82
171
|
const reason = isEscape ? "escape" : "ctrl-c";
|
|
83
|
-
|
|
172
|
+
printSystemLine(`status=stopping reason=${reason}`);
|
|
84
173
|
controller.abort(new Error(`Interrupted by ${reason}.`));
|
|
85
174
|
};
|
|
86
175
|
input.on("keypress", handler);
|
|
@@ -96,19 +185,21 @@ async function printResumeHint(state) {
|
|
|
96
185
|
const sessionId = state.sessionId || "";
|
|
97
186
|
console.log("");
|
|
98
187
|
if (sessionId) {
|
|
99
|
-
|
|
100
|
-
console.log(`Resume: aginti resume ${sessionId}`);
|
|
101
|
-
console.log(`One-shot: aginti resume ${sessionId} "continue"`);
|
|
188
|
+
printAgentMessage(["Interrupted. Session saved.", `Resume: aginti resume ${sessionId}`, `One-shot: aginti resume ${sessionId} "continue"`].join("\n"));
|
|
102
189
|
} else {
|
|
103
|
-
|
|
190
|
+
printAgentMessage("Interrupted. No active session yet.");
|
|
104
191
|
const sessions = await listProjectSessions(process.cwd(), 5).catch(() => []);
|
|
105
192
|
if (sessions.length > 0) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
193
|
+
printAgentMessage(
|
|
194
|
+
[
|
|
195
|
+
"Recent sessions:",
|
|
196
|
+
...sessions.map((session) => ` ${formatSessionLine(session)}`),
|
|
197
|
+
`Resume latest: aginti resume ${sessions[0].sessionId}`,
|
|
198
|
+
"List all: aginti sessions list",
|
|
199
|
+
].join("\n")
|
|
200
|
+
);
|
|
110
201
|
} else {
|
|
111
|
-
|
|
202
|
+
printAgentMessage("Restart: aginti");
|
|
112
203
|
}
|
|
113
204
|
}
|
|
114
205
|
}
|
|
@@ -133,6 +224,29 @@ function createState(args = {}) {
|
|
|
133
224
|
};
|
|
134
225
|
}
|
|
135
226
|
|
|
227
|
+
async function maybeOnboardDeepSeekKey(state) {
|
|
228
|
+
if (!shouldPromptForDeepSeek(state, process.cwd())) return;
|
|
229
|
+
|
|
230
|
+
printAgentMessage(
|
|
231
|
+
[
|
|
232
|
+
"DeepSeek API key is not configured for this project.",
|
|
233
|
+
"Paste it once to save it in `.aginti/.env` with 0600 permissions, or press Enter to continue in mock mode.",
|
|
234
|
+
].join("\n")
|
|
235
|
+
);
|
|
236
|
+
const result = await promptAndSaveDeepSeekKey(process.cwd(), {
|
|
237
|
+
promptText: "DeepSeek API key: ",
|
|
238
|
+
});
|
|
239
|
+
if (result.saved) {
|
|
240
|
+
printAgentMessage(`Saved ${result.keyName} to project-local ignored env.`);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
state.provider = "mock";
|
|
245
|
+
state.routingMode = "manual";
|
|
246
|
+
state.model = "mock-agent";
|
|
247
|
+
printAgentMessage("No key saved. Continuing in local mock mode. Use `/provider deepseek` after running `aginti login deepseek`.");
|
|
248
|
+
}
|
|
249
|
+
|
|
136
250
|
async function handleCommand(line, state, packageDir) {
|
|
137
251
|
const [command, ...rest] = line.slice(1).trim().split(/\s+/);
|
|
138
252
|
const value = rest.join(" ").trim();
|
|
@@ -148,71 +262,75 @@ async function handleCommand(line, state, packageDir) {
|
|
|
148
262
|
}
|
|
149
263
|
if (command === "new") {
|
|
150
264
|
state.sessionId = "";
|
|
151
|
-
|
|
265
|
+
printAgentMessage("Next message will start a new session.");
|
|
152
266
|
return true;
|
|
153
267
|
}
|
|
154
268
|
if (command === "resume") {
|
|
155
269
|
if (!value || value === "latest") {
|
|
156
270
|
const latest = await latestSession();
|
|
157
271
|
if (!latest) {
|
|
158
|
-
|
|
272
|
+
printAgentMessage("No project-local sessions found. Use /new or type a request to start one.");
|
|
159
273
|
} else {
|
|
160
274
|
state.sessionId = latest.sessionId;
|
|
161
|
-
|
|
275
|
+
printAgentMessage(`Resuming latest ${formatSessionLine(latest)}`);
|
|
162
276
|
}
|
|
163
277
|
} else {
|
|
164
278
|
state.sessionId = value;
|
|
165
|
-
|
|
279
|
+
printAgentMessage(`Resuming ${state.sessionId}`);
|
|
166
280
|
}
|
|
167
281
|
return true;
|
|
168
282
|
}
|
|
169
283
|
if (command === "sessions") {
|
|
170
284
|
const sessions = await listProjectSessions(process.cwd(), 20);
|
|
171
|
-
if (sessions.length === 0)
|
|
285
|
+
if (sessions.length === 0) printAgentMessage("No project-local sessions found.");
|
|
172
286
|
else {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
287
|
+
printAgentMessage(
|
|
288
|
+
sessions
|
|
289
|
+
.map((session) => {
|
|
290
|
+
const goal = session.goal ? ` ${session.goal.slice(0, 80)}` : "";
|
|
291
|
+
return `${session.sessionId} ${session.provider}/${session.model} ${session.updatedAt}${goal}`;
|
|
292
|
+
})
|
|
293
|
+
.join("\n")
|
|
294
|
+
);
|
|
177
295
|
}
|
|
178
296
|
return true;
|
|
179
297
|
}
|
|
180
298
|
if (command === "profile") {
|
|
181
299
|
state.taskProfile = normalizeTaskProfile(value || "auto");
|
|
182
|
-
|
|
300
|
+
printSystemLine(`profile=${state.taskProfile}`);
|
|
183
301
|
return true;
|
|
184
302
|
}
|
|
185
303
|
if (command === "routing") {
|
|
186
304
|
state.routingMode = value || "smart";
|
|
187
|
-
|
|
305
|
+
printSystemLine(`routing=${state.routingMode}`);
|
|
188
306
|
return true;
|
|
189
307
|
}
|
|
190
308
|
if (command === "provider") {
|
|
191
309
|
state.provider = value === "auto" ? "" : value;
|
|
192
|
-
|
|
310
|
+
printSystemLine(`provider=${state.provider || "auto"}`);
|
|
193
311
|
return true;
|
|
194
312
|
}
|
|
195
313
|
if (command === "model") {
|
|
196
314
|
state.model = value === "auto" ? "" : value;
|
|
197
|
-
|
|
315
|
+
printSystemLine(`model=${state.model || "auto"}`);
|
|
198
316
|
return true;
|
|
199
317
|
}
|
|
200
318
|
if (command === "installs") {
|
|
201
319
|
state.packageInstallPolicy = normalizePackageInstallPolicy(value || "prompt");
|
|
202
|
-
|
|
320
|
+
printSystemLine(`installs=${state.packageInstallPolicy}`);
|
|
203
321
|
return true;
|
|
204
322
|
}
|
|
205
323
|
if (command === "docker") {
|
|
206
324
|
if (value === "on") {
|
|
207
325
|
state.sandboxMode = "docker-workspace";
|
|
208
326
|
state.packageInstallPolicy = "allow";
|
|
209
|
-
|
|
327
|
+
printSystemLine(`docker=on /workspace -> ${state.commandCwd || process.cwd()} installs=allow`);
|
|
210
328
|
} else if (value === "off") {
|
|
211
329
|
state.sandboxMode = "host";
|
|
212
330
|
state.packageInstallPolicy = "prompt";
|
|
213
|
-
|
|
331
|
+
printSystemLine("docker=off sandbox=host installs=prompt");
|
|
214
332
|
} else {
|
|
215
|
-
|
|
333
|
+
printAgentMessage("Usage: /docker on OR /docker off");
|
|
216
334
|
}
|
|
217
335
|
return true;
|
|
218
336
|
}
|
|
@@ -222,32 +340,32 @@ async function handleCommand(line, state, packageDir) {
|
|
|
222
340
|
state.sandboxMode = "docker-workspace";
|
|
223
341
|
state.packageInstallPolicy = "allow";
|
|
224
342
|
state.maxSteps = Math.max(state.maxSteps, 30);
|
|
225
|
-
|
|
343
|
+
printSystemLine("latex=on profile=latex sandbox=docker-workspace installs=allow maxSteps=30");
|
|
226
344
|
} else if (value === "off") {
|
|
227
345
|
state.taskProfile = "auto";
|
|
228
|
-
|
|
346
|
+
printSystemLine("latex=off profile=auto");
|
|
229
347
|
} else {
|
|
230
|
-
|
|
348
|
+
printAgentMessage("Usage: /latex on OR /latex off");
|
|
231
349
|
}
|
|
232
350
|
return true;
|
|
233
351
|
}
|
|
234
352
|
if (command === "cwd") {
|
|
235
353
|
state.commandCwd = value || process.cwd();
|
|
236
|
-
|
|
354
|
+
printSystemLine(`cwd=${state.commandCwd}`);
|
|
237
355
|
return true;
|
|
238
356
|
}
|
|
239
357
|
if (command === "init") {
|
|
240
358
|
const result = await initProject(process.cwd());
|
|
241
|
-
|
|
359
|
+
printAgentMessage(`initialized project=${result.projectRoot}`);
|
|
242
360
|
return true;
|
|
243
361
|
}
|
|
244
362
|
if (command === "web") {
|
|
245
363
|
const port = value || "3220";
|
|
246
|
-
|
|
364
|
+
printAgentMessage(`Run in another terminal: aginti web --port ${port}`);
|
|
247
365
|
return true;
|
|
248
366
|
}
|
|
249
367
|
|
|
250
|
-
|
|
368
|
+
printAgentMessage(`Unknown command: /${command}. Use /help.`);
|
|
251
369
|
return true;
|
|
252
370
|
}
|
|
253
371
|
|
|
@@ -279,8 +397,8 @@ async function runPrompt(prompt, state, packageDir) {
|
|
|
279
397
|
state.status = "running";
|
|
280
398
|
state.activeGoal = prompt.replace(/\s+/g, " ").slice(0, 120);
|
|
281
399
|
state.lastEvent = "";
|
|
282
|
-
|
|
283
|
-
|
|
400
|
+
printSystemLine(`session=${state.sessionId}`);
|
|
401
|
+
printSystemLine(`status=running workingOn=${state.activeGoal}`);
|
|
284
402
|
|
|
285
403
|
const detachInterrupts = attachRunInterrupts(controller);
|
|
286
404
|
let result;
|
|
@@ -288,6 +406,19 @@ async function runPrompt(prompt, state, packageDir) {
|
|
|
288
406
|
result = await runAgent({
|
|
289
407
|
...config,
|
|
290
408
|
abortSignal: controller.signal,
|
|
409
|
+
onConsole: (text, options = {}) => {
|
|
410
|
+
if (options.kind === "assistant") {
|
|
411
|
+
printAgentMessage(text);
|
|
412
|
+
} else if (options.kind === "plan") {
|
|
413
|
+
printWrapped(`${label("plan", ansi.systemBg)} `, text);
|
|
414
|
+
} else if (options.kind === "heading") {
|
|
415
|
+
printHeading(text);
|
|
416
|
+
} else if (options.error) {
|
|
417
|
+
console.error(`${label("error", ansi.systemBg)} ${stripMarkdown(text)}`);
|
|
418
|
+
} else {
|
|
419
|
+
printSystemLine(text);
|
|
420
|
+
}
|
|
421
|
+
},
|
|
291
422
|
onEvent: (type, data = {}) => {
|
|
292
423
|
if (type === "plan.created") {
|
|
293
424
|
printStatusEvent(state, "planned");
|
|
@@ -316,7 +447,7 @@ async function runPrompt(prompt, state, packageDir) {
|
|
|
316
447
|
state.sessionId = result.sessionId || state.sessionId;
|
|
317
448
|
state.status = result.stopped ? "stopped" : "idle";
|
|
318
449
|
state.activeGoal = "";
|
|
319
|
-
|
|
450
|
+
printSystemLine(`status=${state.status} session=${state.sessionId}`);
|
|
320
451
|
if (result.stopped && result.reason === "user_interrupt") {
|
|
321
452
|
await printResumeHint(state);
|
|
322
453
|
}
|
|
@@ -324,18 +455,19 @@ async function runPrompt(prompt, state, packageDir) {
|
|
|
324
455
|
|
|
325
456
|
export async function startInteractiveCli(args = {}, { packageDir, packageVersion } = {}) {
|
|
326
457
|
const state = createState(args);
|
|
458
|
+
await maybeOnboardDeepSeekKey(state);
|
|
327
459
|
const rl = readline.createInterface({ input, output, terminal: Boolean(input.isTTY && output.isTTY) });
|
|
328
460
|
|
|
329
|
-
console.log(`AgInTiFlow ${packageVersion || ""}
|
|
330
|
-
|
|
331
|
-
|
|
461
|
+
console.log(color(` AgInTiFlow ${packageVersion || ""} `, ansi.agentBg, ansi.bold).trimEnd());
|
|
462
|
+
printSystemLine(`Project: ${process.cwd()}`);
|
|
463
|
+
printAgentMessage("Interactive agent chat. Type /help for commands, /exit to quit.");
|
|
332
464
|
printStatus(state);
|
|
333
465
|
|
|
334
466
|
try {
|
|
335
467
|
while (true) {
|
|
336
468
|
let answer = "";
|
|
337
469
|
try {
|
|
338
|
-
answer = await rl.question(
|
|
470
|
+
answer = await rl.question(userPrompt());
|
|
339
471
|
} catch (error) {
|
|
340
472
|
if (error?.code === "ERR_USE_AFTER_CLOSE") break;
|
|
341
473
|
if (isAbortError(error)) {
|
|
@@ -359,7 +491,7 @@ export async function startInteractiveCli(args = {}, { packageDir, packageVersio
|
|
|
359
491
|
await printResumeHint(state);
|
|
360
492
|
break;
|
|
361
493
|
}
|
|
362
|
-
console.error(
|
|
494
|
+
console.error(`${label("error", ansi.systemBg)} ${error.message}`);
|
|
363
495
|
}
|
|
364
496
|
}
|
|
365
497
|
} finally {
|
package/src/project.js
CHANGED
|
@@ -199,6 +199,11 @@ export async function setProviderKey(projectRoot, provider, value) {
|
|
|
199
199
|
|
|
200
200
|
const paths = projectPaths(projectRoot);
|
|
201
201
|
await fsp.mkdir(paths.controlDir, { recursive: true });
|
|
202
|
+
await ensureLine(paths.gitignorePath, [
|
|
203
|
+
".aginti/.env",
|
|
204
|
+
".aginti/.env.*",
|
|
205
|
+
"!.aginti/.env.example",
|
|
206
|
+
]);
|
|
202
207
|
let parsed = {};
|
|
203
208
|
try {
|
|
204
209
|
parsed = parseEnvText(await fsp.readFile(paths.envPath, "utf8"));
|