@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 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 tables. Esc or Ctrl+C stops the active run cleanly and prints the resume command.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.12.5",
3
+ "version": "0.12.7",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a resumable Playwright website-control agent with OpenAI-compatible tool calling.",
6
6
  "license": "Apache-2.0",
@@ -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=running workingOn=") || !result.stdout.includes("status=idle session=")) {
123
- throw new Error("interactive chat did not print simple run status updates");
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+\|\r?\nMock run complete\./.test(result.stdout)) {
126
- throw new Error("assistant response did not start on a fresh line after the aginti header");
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-newline", "aginti-md", "instructions-command", "instructions-chat-edit", "interactive-chat", "mock-file-write", "run-status", "resume-latest"],
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
@@ -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(rolePrefix("aginti>", ansi.agentBg).trimEnd());
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
- state.lastEvent = details ? `${label}: ${details}` : label;
1186
- printSystemLine(`status=${state.status || "running"} ${state.lastEvent}`);
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.replace(/\s+/g, " ").slice(0, 120);
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) {