@lazyingart/agintiflow 0.8.4 → 0.8.6

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
@@ -54,7 +54,7 @@ aginti
54
54
  aginti chat
55
55
  ```
56
56
 
57
- Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is now 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, `/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. Ctrl+C exits cleanly, prints the active resume command when a session exists, and lists recent sessions when no new session has started yet.
57
+ 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, `/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. Esc or Ctrl+C stops the active run cleanly and prints the resume command.
58
58
 
59
59
  Launch the local web UI from an installed package:
60
60
 
@@ -340,7 +340,7 @@ Package policy values:
340
340
 
341
341
  Toolchain commands such as `python3 plot.py`, `latexmk -pdf paper.tex`, and `pdflatex -interaction=nonstopmode -halt-on-error paper.tex` are allowlisted only when the shell tool is enabled. In Docker mode the project folder is mounted as `/workspace`; any file written to `/workspace/report.pdf` appears on the host as `<your-project>/report.pdf`. CLI runs print both the host workspace and the Docker mapping before execution. File and canvas tools accept both normal relative paths and Docker virtual paths like `/workspace/report.pdf`, while other absolute host paths remain blocked.
342
342
 
343
- The web chat mirrors the CLI session store. Enter sends, Ctrl+J inserts a newline, and Tab submits/queues the message. Queued input is written to `.sessions/<session-id>/inbox.jsonl`; the running agent drains that pipe between steps and after tool calls.
343
+ The web chat mirrors the CLI session store. Enter sends, Ctrl+J inserts a newline, Tab submits/queues the message, and Esc stops the active run. Queued input is written to `.sessions/<session-id>/inbox.jsonl`; the running agent drains that pipe between steps and after tool calls. Generated local sites should use the built-in `preview_workspace` or `open_workspace_file` tools; AgInTiFlow avoids transient localhost servers inside Docker because those containers stop between commands and their ports are not host-published.
344
344
 
345
345
  Safe preflight endpoints:
346
346
 
@@ -9,6 +9,10 @@ AgInTiFlow keeps CLI and web runs equivalent by using the project folder as the
9
9
 
10
10
  When a run is active, the web chat and `aginti queue <session-id> "..."` append messages to the inbox instead of trying to mutate the running process directly. The runner drains the inbox at safe boundaries: before each model step and after tool execution. This mirrors the event-queue style used by mature agent UIs while keeping the backend decoupled from any specific frontend.
11
11
 
12
+ Runs can be stopped without corrupting session state. The CLI listens for Esc or Ctrl+C during an active run, and the web UI exposes a Stop button plus Esc. Both paths send an abort signal, persist `session.stopped`, and leave the session resumable through `aginti resume <session-id>`.
13
+
12
14
  Default execution is Docker workspace mode with package installs approved inside the sandbox. The project is mounted at `/workspace`; persistent agent toolchain folders are mounted at `/aginti-home`, `/aginti-cache`, and `/aginti-env` from `~/.agintiflow/docker/`. Python, conda, and other language-level environments should be installed under `/aginti-env` so they survive across runs. Apt/apk package changes are ephemeral unless the Docker image is rebuilt.
13
15
 
16
+ Generated local websites should use `preview_workspace` or `open_workspace_file`. The preview tool serves the host workspace on an automatically selected `127.0.0.1` port and opens it in the browser. AgInTiFlow blocks common transient Docker preview commands such as `python -m http.server` because each shell tool call runs in a short-lived container with no published host port.
17
+
14
18
  The failed `f(f(x)) = f'(x)` LaTeX task exposed three issues: host-mode command policy blocked setup/path commands, a 15-step budget was too small for iterative numerical work plus TeX output, and follow-up input could not be queued while the agent was running. The current runtime defaults and inbox pipe address those without hardcoding that specific math task.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
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",
package/public/app.js CHANGED
@@ -76,6 +76,9 @@ const translations = {
76
76
  dockerImageLabel: "Docker image",
77
77
  dockerImagePlaceholder: "agintiflow-sandbox:latest",
78
78
  startRunButton: "Start run",
79
+ stopRunButton: "Stop",
80
+ stoppingRun: "Stopping...",
81
+ stopRunFailed: "Failed to stop run.",
79
82
  runOutputTitle: "Run output",
80
83
  noRunStarted: "No run started.",
81
84
  conversationTitle: "Conversation",
@@ -643,6 +646,7 @@ const packageInstallPolicyField = document.querySelector("#packageInstallPolicy"
643
646
  const packageWarningEl = document.querySelector("#package-warning");
644
647
  const logsEl = document.querySelector("#logs");
645
648
  const runMetaEl = document.querySelector("#run-meta");
649
+ const stopRunButton = document.querySelector("#stop-run");
646
650
  const keyStatusEl = document.querySelector("#key-status");
647
651
  const allowWrapperToolsField = document.querySelector("#allowWrapperTools");
648
652
  const preferredWrapperField = document.querySelector("#preferredWrapper");
@@ -697,6 +701,7 @@ let routingPresets = {};
697
701
  let taskProfiles = [];
698
702
  let projectInfo = null;
699
703
  let currentSessionId = "";
704
+ let currentRunStatus = "";
700
705
  let pollTimer = null;
701
706
  let saveTimer = null;
702
707
  let lastChatEntries = [];
@@ -728,6 +733,12 @@ function setLogs(text, mode = "active") {
728
733
  logsEl.textContent = text;
729
734
  }
730
735
 
736
+ function updateStopRunButton() {
737
+ if (!stopRunButton) return;
738
+ stopRunButton.hidden = currentRunStatus !== "running";
739
+ stopRunButton.disabled = currentRunStatus !== "running";
740
+ }
741
+
731
742
  function renderKeyStatus(status = lastKeyStatus) {
732
743
  lastKeyStatus = status;
733
744
  if (!status) return;
@@ -949,6 +960,7 @@ function applyLanguage(language, { persist = true } = {}) {
949
960
  renderSessionManager();
950
961
  renderChat(lastChatEntries);
951
962
  renderArtifactShell();
963
+ updateStopRunButton();
952
964
  if (persist) schedulePreferenceSave();
953
965
  }
954
966
 
@@ -1429,11 +1441,13 @@ async function refreshRun() {
1429
1441
  const response = await fetch(`/api/runs/${encodeURIComponent(currentSessionId)}`);
1430
1442
  if (!response.ok) return;
1431
1443
  const run = await response.json();
1444
+ currentRunStatus = run.status || "";
1445
+ updateStopRunButton();
1432
1446
  runMetaEl.textContent = `${run.status} · ${run.sessionId}`;
1433
1447
  renderLogs(run);
1434
1448
  await refreshArtifacts({ loadSelected: artifactTunnelDialog?.open });
1435
1449
 
1436
- if (run.status === "finished" || run.status === "failed") {
1450
+ if (run.status === "finished" || run.status === "failed" || run.status === "stopped") {
1437
1451
  clearInterval(pollTimer);
1438
1452
  pollTimer = null;
1439
1453
  await refreshChat();
@@ -1443,6 +1457,22 @@ async function refreshRun() {
1443
1457
  }
1444
1458
  }
1445
1459
 
1460
+ async function stopCurrentRun() {
1461
+ if (!currentSessionId || currentRunStatus !== "running") return;
1462
+ stopRunButton.disabled = true;
1463
+ chatStatusEl.textContent = t("stoppingRun");
1464
+ const response = await fetch(`/api/runs/${encodeURIComponent(currentSessionId)}/stop`, {
1465
+ method: "POST",
1466
+ });
1467
+ if (!response.ok) {
1468
+ const data = await response.json().catch(() => ({}));
1469
+ chatStatusEl.textContent = data.error || t("stopRunFailed");
1470
+ updateStopRunButton();
1471
+ return;
1472
+ }
1473
+ await refreshRun();
1474
+ }
1475
+
1446
1476
  async function refreshChat() {
1447
1477
  if (!currentSessionId) {
1448
1478
  renderChat([]);
@@ -1771,6 +1801,24 @@ chatInputEl.addEventListener("keydown", (event) => {
1771
1801
  }
1772
1802
  });
1773
1803
 
1804
+ stopRunButton?.addEventListener("click", () => {
1805
+ stopCurrentRun().catch((error) => {
1806
+ chatStatusEl.textContent = String(error);
1807
+ updateStopRunButton();
1808
+ });
1809
+ });
1810
+
1811
+ window.addEventListener("keydown", (event) => {
1812
+ if (event.key !== "Escape" || currentRunStatus !== "running") return;
1813
+ const tag = event.target?.tagName?.toLowerCase();
1814
+ if (tag === "select" || tag === "button") return;
1815
+ event.preventDefault();
1816
+ stopCurrentRun().catch((error) => {
1817
+ chatStatusEl.textContent = String(error);
1818
+ updateStopRunButton();
1819
+ });
1820
+ });
1821
+
1774
1822
  form.addEventListener("submit", async (event) => {
1775
1823
  event.preventDefault();
1776
1824
 
@@ -1802,6 +1850,8 @@ form.addEventListener("submit", async (event) => {
1802
1850
  }
1803
1851
 
1804
1852
  currentSessionId = data.sessionId;
1853
+ currentRunStatus = "running";
1854
+ updateStopRunButton();
1805
1855
  sessionSelectEl.value = currentSessionId;
1806
1856
  runMetaEl.textContent = `${t("runningStatus").replace("...", "")} · ${currentSessionId}`;
1807
1857
  await refreshSessions();
@@ -1817,6 +1867,8 @@ form.addEventListener("submit", async (event) => {
1817
1867
  sessionSelectEl.addEventListener("change", async () => {
1818
1868
  currentSessionId = sessionSelectEl.value;
1819
1869
  if (!currentSessionId) {
1870
+ currentRunStatus = "";
1871
+ updateStopRunButton();
1820
1872
  runMetaEl.textContent = "";
1821
1873
  setLogs(t("noRunSelected"), "empty");
1822
1874
  renderChat([]);
@@ -1862,6 +1914,8 @@ chatFormEl.addEventListener("submit", async (event) => {
1862
1914
  }
1863
1915
 
1864
1916
  currentSessionId = data.sessionId;
1917
+ currentRunStatus = data.queued ? currentRunStatus : "running";
1918
+ updateStopRunButton();
1865
1919
  chatInputEl.value = "";
1866
1920
  chatStatusEl.textContent = data.queued ? t("queuedStatus") : t("runningStatus");
1867
1921
  if (data.queued) {
package/public/index.html CHANGED
@@ -307,7 +307,10 @@
307
307
  <section class="panel output-panel">
308
308
  <div class="header-row">
309
309
  <h2 data-i18n="runOutputTitle">Run output</h2>
310
- <div id="run-meta" class="subtle"></div>
310
+ <div class="run-header-actions">
311
+ <button id="stop-run" type="button" class="secondary danger" data-i18n="stopRunButton" hidden>Stop</button>
312
+ <div id="run-meta" class="subtle"></div>
313
+ </div>
311
314
  </div>
312
315
  <pre id="logs" class="logs" data-i18n="noRunStarted">No run started.</pre>
313
316
  </section>
package/public/styles.css CHANGED
@@ -346,6 +346,15 @@ button.danger {
346
346
  justify-content: space-between;
347
347
  }
348
348
 
349
+ .run-header-actions {
350
+ display: flex;
351
+ min-width: 0;
352
+ flex-wrap: wrap;
353
+ gap: 10px;
354
+ align-items: center;
355
+ justify-content: flex-end;
356
+ }
357
+
349
358
  .sandbox-card {
350
359
  display: grid;
351
360
  gap: 12px;