@lazyingart/agintiflow 0.12.5 → 0.12.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 +1 -1
- package/package.json +1 -1
- package/scripts/smoke-cli-chat.js +26 -6
- package/src/interactive-cli.js +94 -8
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ aginti
|
|
|
67
67
|
aginti chat
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/instructions` to inspect `AGINTI.md`, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. `Ctrl+J` inserts a new line in the colored input panel, Enter sends, arrow keys move through wrapped multiline input, and `Ctrl+A`/`Ctrl+E` jump to the current line start/end. During an active run, Enter sends the draft as an ASAP pipe message (`→`) and Tab queues it for after the run (`↳`); ASAP messages are consumed first, Alt+Up edits the last piped message, and Shift+Left edits the last after-finish queued message. The input panel always shows the current `cwd` footer. Assistant responses start on a fresh line after the `aginti>` header and render common Markdown, including headings, inline code, bold text, lists, quotes, code fences, and
|
|
70
|
+
Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/instructions` to inspect `AGINTI.md`, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. `Ctrl+J` inserts a new line in the colored input panel, Enter sends, arrow keys move through wrapped multiline input, and `Ctrl+A`/`Ctrl+E` jump to the current line start/end. During an active run, Enter sends the draft as an ASAP pipe message (`→`) and Tab queues it for after the run (`↳`); ASAP messages are consumed first, Alt+Up edits the last piped message, and Shift+Left edits the last after-finish queued message. The input panel always shows the current `cwd` footer and a single live status row, so long goals and tool updates are compacted instead of flooding the transcript. Assistant responses start on a fresh line after the `aginti>` header with a colored response gutter and render common Markdown, including headings, inline code, bold text, lists, quotes, code fences, tables, and red/green patch diff lines. Resuming a session prints a compact recent-chat preview before the prompt. Esc or Ctrl+C stops the active run cleanly and prints the resume command.
|
|
71
71
|
|
|
72
72
|
`aginti init` creates `AGINTI.md` at the project root. This is the editable project-instruction file for both CLI and web runs, similar to `AGENTS.md` or project memory in other agents. Keep durable preferences, commands, and constraints there, but never secrets. You can edit it manually or ask in chat, for example: `update AGINTI.md to remember that this project uses pytest and npm run check`.
|
|
73
73
|
|
package/package.json
CHANGED
|
@@ -69,6 +69,22 @@ try {
|
|
|
69
69
|
if (!renderedMarkdown.includes("Check") || !renderedMarkdown.includes("Present")) {
|
|
70
70
|
throw new Error("terminal markdown renderer dropped table content");
|
|
71
71
|
}
|
|
72
|
+
if ((renderedMarkdown.match(/Present/g) || []).length !== 1) {
|
|
73
|
+
throw new Error("terminal markdown renderer duplicated table rows");
|
|
74
|
+
}
|
|
75
|
+
const renderedDiff = stripMarkdown(
|
|
76
|
+
[
|
|
77
|
+
"Diff:",
|
|
78
|
+
"--- a/example.txt",
|
|
79
|
+
"+++ b/example.txt",
|
|
80
|
+
"@@ line 1 @@",
|
|
81
|
+
"-old",
|
|
82
|
+
"+new",
|
|
83
|
+
].join("\n")
|
|
84
|
+
);
|
|
85
|
+
if (!renderedDiff.includes("-old") || !renderedDiff.includes("+new")) {
|
|
86
|
+
throw new Error("terminal markdown renderer dropped patch diff lines");
|
|
87
|
+
}
|
|
72
88
|
|
|
73
89
|
const promptLayout = buildPromptLayout(`${"x".repeat(180)}\nsecond line`, 95, 80, 24);
|
|
74
90
|
const visibleLengths = promptLayout.renderedRows.map((line) =>
|
|
@@ -86,13 +102,14 @@ try {
|
|
|
86
102
|
}
|
|
87
103
|
const queuedPromptLayout = buildPromptLayout("follow up", 9, 90, 24, {
|
|
88
104
|
commandCwd: "/tmp/aginti-project",
|
|
105
|
+
statusLine: "running · tool: apply_patch with a very long request that should be compacted in the panel",
|
|
89
106
|
pendingAsap: [{ content: "apply this to the running task" }],
|
|
90
107
|
pendingQueued: [{ content: "run this after the current task" }],
|
|
91
108
|
});
|
|
92
109
|
const queuedText = queuedPromptLayout.renderedRows
|
|
93
110
|
.map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, ""))
|
|
94
111
|
.join("\n");
|
|
95
|
-
if (!queuedText.includes("→ apply this") || !queuedText.includes("↳ run this") || !queuedText.includes("cwd /tmp/aginti-project")) {
|
|
112
|
+
if (!queuedText.includes("run running · tool: apply_patch") || !queuedText.includes("→ apply this") || !queuedText.includes("↳ run this") || !queuedText.includes("cwd /tmp/aginti-project")) {
|
|
96
113
|
throw new Error("terminal prompt layout did not render live input queue and cwd footer");
|
|
97
114
|
}
|
|
98
115
|
|
|
@@ -119,24 +136,27 @@ try {
|
|
|
119
136
|
if (!result.stdout.includes("Interactive agent chat")) {
|
|
120
137
|
throw new Error("interactive chat did not print its banner");
|
|
121
138
|
}
|
|
122
|
-
if (!result.stdout.includes("status=
|
|
123
|
-
throw new Error("interactive chat did not print
|
|
139
|
+
if (!result.stdout.includes("status=idle session=")) {
|
|
140
|
+
throw new Error("interactive chat did not print final run status");
|
|
124
141
|
}
|
|
125
|
-
if (!/aginti>\s
|
|
126
|
-
throw new Error("assistant response did not
|
|
142
|
+
if (!/aginti>\s*\r?\n\s*\|\s+Mock run complete\./.test(result.stdout)) {
|
|
143
|
+
throw new Error("assistant response did not render with a fresh-line response gutter");
|
|
127
144
|
}
|
|
128
145
|
|
|
129
146
|
const latest = await runCli(["resume"], "/exit\n");
|
|
130
147
|
if (!latest.stdout.includes("session=") || !latest.stdout.includes("Interactive agent chat")) {
|
|
131
148
|
throw new Error("bare aginti resume did not open the latest session interactively");
|
|
132
149
|
}
|
|
150
|
+
if (!latest.stdout.includes("resume history") || !latest.stdout.includes("Mock run complete")) {
|
|
151
|
+
throw new Error("bare aginti resume did not preview saved chat history");
|
|
152
|
+
}
|
|
133
153
|
|
|
134
154
|
console.log(
|
|
135
155
|
JSON.stringify(
|
|
136
156
|
{
|
|
137
157
|
ok: true,
|
|
138
158
|
projectRoot: tempRoot,
|
|
139
|
-
checks: ["markdown-render", "prompt-layout", "live-input-layout", "agent-response-
|
|
159
|
+
checks: ["markdown-render", "markdown-table-no-duplicate", "patch-diff-render", "prompt-layout", "live-input-status-layout", "agent-response-gutter", "aginti-md", "instructions-command", "instructions-chat-edit", "interactive-chat", "mock-file-write", "run-status", "resume-latest", "resume-history-preview"],
|
|
140
160
|
},
|
|
141
161
|
null,
|
|
142
162
|
2
|
package/src/interactive-cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { emitKeypressEvents } from "node:readline";
|
|
|
3
3
|
import { stdin as input, stdout as output } from "node:process";
|
|
4
4
|
import { runAgent } from "./agent-runner.js";
|
|
5
5
|
import { loadConfig } from "./config.js";
|
|
6
|
-
import { initProject, listProjectSessions, providerKeyStatus, readProjectInstructions, setProviderKey } from "./project.js";
|
|
6
|
+
import { initProject, listProjectSessions, projectPaths, providerKeyStatus, readProjectInstructions, setProviderKey } from "./project.js";
|
|
7
7
|
import { normalizePackageInstallPolicy, normalizeSandboxMode } from "./command-policy.js";
|
|
8
8
|
import { defaultMaxStepsForProfile, normalizeTaskProfile } from "./task-profiles.js";
|
|
9
9
|
import { recommendedMaxStepsForTask } from "./engineering-guidance.js";
|
|
@@ -27,6 +27,8 @@ const ansi = {
|
|
|
27
27
|
cursorShow: "\x1b[?25h",
|
|
28
28
|
userBg: "\x1b[48;5;24m\x1b[38;5;231m",
|
|
29
29
|
agentBg: "\x1b[48;5;29m\x1b[38;5;231m",
|
|
30
|
+
responseBg: "\x1b[48;5;25m\x1b[38;5;231m",
|
|
31
|
+
statusBg: "\x1b[48;5;23m\x1b[38;5;231m",
|
|
30
32
|
systemBg: "\x1b[48;5;236m\x1b[38;5;245m",
|
|
31
33
|
};
|
|
32
34
|
const brandPalette = ["\x1b[38;5;45m", "\x1b[38;5;81m", "\x1b[38;5;86m", "\x1b[38;5;118m", "\x1b[38;5;226m"];
|
|
@@ -151,6 +153,7 @@ function compactLine(value = "", limit = 96) {
|
|
|
151
153
|
export function stripMarkdown(text) {
|
|
152
154
|
const lines = String(text || "").split(/\r?\n/);
|
|
153
155
|
let inFence = false;
|
|
156
|
+
let diffContext = 0;
|
|
154
157
|
const rendered = [];
|
|
155
158
|
|
|
156
159
|
for (let index = 0; index < lines.length; index += 1) {
|
|
@@ -165,6 +168,15 @@ export function stripMarkdown(text) {
|
|
|
165
168
|
continue;
|
|
166
169
|
}
|
|
167
170
|
|
|
171
|
+
if (/^\s*Diff:\s*$/i.test(line)) diffContext = 80;
|
|
172
|
+
const patchLine = renderPatchLine(line, { active: diffContext > 0 || inFence });
|
|
173
|
+
if (patchLine) {
|
|
174
|
+
rendered.push(patchLine);
|
|
175
|
+
diffContext = 80;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (diffContext > 0) diffContext -= 1;
|
|
179
|
+
|
|
168
180
|
if (!inFence) {
|
|
169
181
|
if (/^\s*[-*_]{3,}\s*$/.test(line)) {
|
|
170
182
|
rendered.push(color("-".repeat(42), ansi.dim));
|
|
@@ -202,6 +214,17 @@ export function stripMarkdown(text) {
|
|
|
202
214
|
return rendered.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
203
215
|
}
|
|
204
216
|
|
|
217
|
+
function renderPatchLine(line = "", { active = false } = {}) {
|
|
218
|
+
const value = String(line || "");
|
|
219
|
+
if (/^diff --git\s+/.test(value)) return color(value, ansi.bold, ansi.cyan);
|
|
220
|
+
if (/^@@\s+/.test(value)) return color(value, ansi.cyan);
|
|
221
|
+
if (/^---\s+a\//.test(value)) return color(value, ansi.red);
|
|
222
|
+
if (/^\+\+\+\s+b\//.test(value)) return color(value, ansi.green);
|
|
223
|
+
if (active && /^\+(?!\+\+)/.test(value)) return color(value, ansi.green);
|
|
224
|
+
if (active && /^-(?!--)/.test(value)) return color(value, ansi.red);
|
|
225
|
+
return "";
|
|
226
|
+
}
|
|
227
|
+
|
|
205
228
|
function splitMarkdownTableRow(line = "") {
|
|
206
229
|
const trimmed = String(line || "").trim();
|
|
207
230
|
if (!trimmed.includes("|")) return null;
|
|
@@ -275,6 +298,10 @@ function rolePrefix(name, bgCode) {
|
|
|
275
298
|
return `${label(name, bgCode)} ${color("|", bgCode)} `;
|
|
276
299
|
}
|
|
277
300
|
|
|
301
|
+
function responsePrefix() {
|
|
302
|
+
return `${color(" | ", ansi.responseBg)} `;
|
|
303
|
+
}
|
|
304
|
+
|
|
278
305
|
function printWrapped(prefix, text, { stripCode = "" } = {}) {
|
|
279
306
|
const rendered = stripMarkdown(text);
|
|
280
307
|
const lines = rendered.split("\n");
|
|
@@ -287,10 +314,10 @@ function printWrapped(prefix, text, { stripCode = "" } = {}) {
|
|
|
287
314
|
}
|
|
288
315
|
|
|
289
316
|
function printAgentMessage(text) {
|
|
290
|
-
outputLine(
|
|
317
|
+
outputLine(label("aginti>", ansi.agentBg).trimEnd());
|
|
291
318
|
const rendered = stripMarkdown(text);
|
|
292
319
|
const lines = rendered.split("\n");
|
|
293
|
-
for (const line of lines) outputLine(line);
|
|
320
|
+
for (const line of lines) outputLine(`${responsePrefix()}${line}`);
|
|
294
321
|
}
|
|
295
322
|
|
|
296
323
|
function printSystemLine(text) {
|
|
@@ -514,6 +541,11 @@ export function buildPromptLayout(buffer = "", cursor = 0, width = terminalWidth
|
|
|
514
541
|
const renderedRows = [];
|
|
515
542
|
let renderedCursorRow = cursorRow - visible.start;
|
|
516
543
|
|
|
544
|
+
if (options.statusLine) {
|
|
545
|
+
renderedRows.push(panelLine(` run ${compactLine(options.statusLine, lineWidth - 8)}`, ansi.statusBg, lineWidth));
|
|
546
|
+
renderedCursorRow += 1;
|
|
547
|
+
}
|
|
548
|
+
|
|
517
549
|
const pendingAsap = Array.isArray(options.pendingAsap) ? options.pendingAsap : [];
|
|
518
550
|
const pendingQueued = Array.isArray(options.pendingQueued) ? options.pendingQueued : [];
|
|
519
551
|
for (const item of pendingAsap.slice(-4)) {
|
|
@@ -882,6 +914,7 @@ class LiveRunInput {
|
|
|
882
914
|
this.preferredColumn = null;
|
|
883
915
|
this.pendingAsap = [];
|
|
884
916
|
this.pendingQueued = [];
|
|
917
|
+
this.statusLine = "";
|
|
885
918
|
this.wasRaw = Boolean(input.isRaw);
|
|
886
919
|
this.started = false;
|
|
887
920
|
this.handler = this.handleKey.bind(this);
|
|
@@ -947,6 +980,7 @@ class LiveRunInput {
|
|
|
947
980
|
}
|
|
948
981
|
this.rendered = renderPromptBuffer(this.buffer, this.cursor, this.rendered, {
|
|
949
982
|
commandCwd: this.commandCwd,
|
|
983
|
+
statusLine: this.statusLine,
|
|
950
984
|
pendingAsap: this.pendingAsap,
|
|
951
985
|
pendingQueued: this.pendingQueued,
|
|
952
986
|
});
|
|
@@ -967,9 +1001,17 @@ class LiveRunInput {
|
|
|
967
1001
|
this.redraw();
|
|
968
1002
|
}
|
|
969
1003
|
|
|
1004
|
+
setStatus(value = "") {
|
|
1005
|
+
const nextStatus = compactLine(value, Math.max(terminalWidth() - 16, 36));
|
|
1006
|
+
if (this.statusLine === nextStatus) return;
|
|
1007
|
+
this.statusLine = nextStatus;
|
|
1008
|
+
this.redraw();
|
|
1009
|
+
}
|
|
1010
|
+
|
|
970
1011
|
moveVertical(direction) {
|
|
971
1012
|
const layout = buildPromptLayout(this.buffer, this.cursor, terminalWidth(), terminalHeight(), {
|
|
972
1013
|
commandCwd: this.commandCwd,
|
|
1014
|
+
statusLine: this.statusLine,
|
|
973
1015
|
pendingAsap: this.pendingAsap,
|
|
974
1016
|
pendingQueued: this.pendingQueued,
|
|
975
1017
|
});
|
|
@@ -1181,9 +1223,43 @@ async function latestSession() {
|
|
|
1181
1223
|
return sessions[0] || null;
|
|
1182
1224
|
}
|
|
1183
1225
|
|
|
1226
|
+
function compactHistoryText(content = "", limit = 170) {
|
|
1227
|
+
const rendered = stripAnsi(stripMarkdown(String(content || "")));
|
|
1228
|
+
return compactLine(rendered.replace(/\s+/g, " "), limit);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function printHistoryEntry(entry) {
|
|
1232
|
+
const role = entry.role === "assistant" ? "aginti" : entry.role === "user" ? "user" : String(entry.role || "note");
|
|
1233
|
+
const bg = role === "aginti" ? ansi.agentBg : role === "user" ? ansi.userBg : ansi.systemBg;
|
|
1234
|
+
const time = entry.at ? new Date(entry.at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "";
|
|
1235
|
+
const suffix = time ? ` ${color(time, ansi.dim)}` : "";
|
|
1236
|
+
outputLine(`${label(role, bg)} ${color("|", bg)} ${compactHistoryText(entry.content)}${suffix}`);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
async function printResumeHistory(state, { limit = 8 } = {}) {
|
|
1240
|
+
if (!state.sessionId) return;
|
|
1241
|
+
const store = new SessionStore(projectPaths(process.cwd()).sessionsDir, state.sessionId);
|
|
1242
|
+
const saved = await store.loadState().catch(() => null);
|
|
1243
|
+
const chat = Array.isArray(saved?.chat) ? saved.chat.filter((entry) => entry?.content) : [];
|
|
1244
|
+
if (chat.length === 0) {
|
|
1245
|
+
printSystemLine(`resume history session=${state.sessionId} messages=0`);
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
const shown = chat.slice(-limit);
|
|
1250
|
+
printSystemLine(`resume history session=${state.sessionId} showing=${shown.length}/${chat.length}`);
|
|
1251
|
+
for (const entry of shown) printHistoryEntry(entry);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1184
1254
|
function printStatusEvent(state, label, details = "") {
|
|
1185
|
-
|
|
1186
|
-
|
|
1255
|
+
const safeDetails = compactLine(details, 72);
|
|
1256
|
+
state.lastEvent = safeDetails ? `${label}: ${safeDetails}` : label;
|
|
1257
|
+
const statusText = `${state.status || "running"} · ${state.lastEvent}`;
|
|
1258
|
+
if (activeRunInput) {
|
|
1259
|
+
activeRunInput.setStatus(statusText);
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
printSystemLine(`status=${statusText}`);
|
|
1187
1263
|
}
|
|
1188
1264
|
|
|
1189
1265
|
function attachRunInterrupts(controller) {
|
|
@@ -1404,10 +1480,12 @@ async function handleCommand(line, state, packageDir) {
|
|
|
1404
1480
|
} else {
|
|
1405
1481
|
state.sessionId = latest.sessionId;
|
|
1406
1482
|
printAgentMessage(`Resuming latest ${formatSessionLine(latest)}`);
|
|
1483
|
+
await printResumeHistory(state);
|
|
1407
1484
|
}
|
|
1408
1485
|
} else {
|
|
1409
1486
|
state.sessionId = value;
|
|
1410
1487
|
printAgentMessage(`Resuming ${state.sessionId}`);
|
|
1488
|
+
await printResumeHistory(state);
|
|
1411
1489
|
}
|
|
1412
1490
|
return true;
|
|
1413
1491
|
}
|
|
@@ -1560,15 +1638,19 @@ async function runPrompt(prompt, state, packageDir) {
|
|
|
1560
1638
|
|
|
1561
1639
|
state.sessionId = config.resume || config.sessionId || state.sessionId;
|
|
1562
1640
|
state.status = "running";
|
|
1563
|
-
state.activeGoal = prompt
|
|
1641
|
+
state.activeGoal = compactLine(prompt, 84);
|
|
1564
1642
|
state.lastEvent = "";
|
|
1565
|
-
printSystemLine(`session=${state.sessionId}`);
|
|
1566
|
-
printSystemLine(`status=running workingOn=${state.activeGoal}`);
|
|
1567
1643
|
|
|
1568
1644
|
const store = new SessionStore(config.sessionsDir, state.sessionId);
|
|
1569
1645
|
const liveInput = new LiveRunInput({ state, store, controller });
|
|
1570
1646
|
const liveStarted = liveInput.start();
|
|
1571
1647
|
const detachInterrupts = liveStarted ? () => {} : attachRunInterrupts(controller);
|
|
1648
|
+
if (liveStarted) {
|
|
1649
|
+
liveInput.setStatus(`running · ${state.activeGoal}`);
|
|
1650
|
+
} else {
|
|
1651
|
+
printSystemLine(`session=${state.sessionId}`);
|
|
1652
|
+
printSystemLine(`status=running workingOn=${state.activeGoal}`);
|
|
1653
|
+
}
|
|
1572
1654
|
let result;
|
|
1573
1655
|
let queuedAfterFinish = [];
|
|
1574
1656
|
try {
|
|
@@ -1584,6 +1666,9 @@ async function runPrompt(prompt, state, packageDir) {
|
|
|
1584
1666
|
printHeading(text);
|
|
1585
1667
|
} else if (options.error) {
|
|
1586
1668
|
outputLine(`${label("error", ansi.systemBg)} ${stripMarkdown(text)}`);
|
|
1669
|
+
} else if (options.kind === "meta" && liveStarted) {
|
|
1670
|
+
const trimmed = String(text || "").trim();
|
|
1671
|
+
if (trimmed) liveInput.setStatus(trimmed);
|
|
1587
1672
|
} else {
|
|
1588
1673
|
printSystemLine(text);
|
|
1589
1674
|
}
|
|
@@ -1643,6 +1728,7 @@ export async function startInteractiveCli(args = {}, { packageDir, packageVersio
|
|
|
1643
1728
|
await maybeOnboardDeepSeekKey(state);
|
|
1644
1729
|
printAgentMessage("Interactive agent chat. Type /help for commands, /exit to quit.");
|
|
1645
1730
|
printStatus(state);
|
|
1731
|
+
await printResumeHistory(state);
|
|
1646
1732
|
|
|
1647
1733
|
try {
|
|
1648
1734
|
while (true) {
|