@lazyingart/agintiflow 0.8.2 → 0.8.4

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 <session-id>` to continue work.
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.
58
58
 
59
59
  Launch the local web UI from an installed package:
60
60
 
@@ -80,6 +80,8 @@ aginti capabilities
80
80
  aginti doctor --capabilities
81
81
  aginti sessions list
82
82
  aginti sessions show <session-id>
83
+ aginti resume
84
+ aginti resume latest
83
85
  aginti resume <session-id> "continue with a short follow-up"
84
86
  aginti queue <session-id> "extra instruction for the running agent"
85
87
  aginti --profile code "write a small Python CLI app with tests"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.8.2",
3
+ "version": "0.8.4",
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/styles.css CHANGED
@@ -536,21 +536,35 @@ button.danger {
536
536
  overflow: auto;
537
537
  display: grid;
538
538
  gap: 12px;
539
- padding: 6px 2px;
539
+ padding: 14px;
540
+ border: 1px solid rgba(15, 118, 110, 0.16);
541
+ border-radius: 18px;
542
+ background:
543
+ radial-gradient(circle at 14% 8%, rgba(20, 184, 166, 0.18), transparent 24%),
544
+ radial-gradient(circle at 86% 0%, rgba(245, 158, 11, 0.16), transparent 22%),
545
+ linear-gradient(180deg, rgba(255, 253, 250, 0.86), rgba(241, 245, 249, 0.72));
546
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.68);
540
547
  }
541
548
 
542
549
  .chat-item {
550
+ width: fit-content;
551
+ max-width: min(780px, 88%);
543
552
  border: 1px solid var(--line);
544
- border-radius: 14px;
553
+ border-radius: 18px;
545
554
  padding: 12px 14px;
555
+ box-shadow: 0 12px 28px rgba(62, 49, 26, 0.08);
546
556
  }
547
557
 
548
558
  .chat-item.user {
559
+ justify-self: end;
549
560
  background: var(--user);
561
+ border-bottom-right-radius: 6px;
550
562
  }
551
563
 
552
564
  .chat-item.assistant {
565
+ justify-self: start;
553
566
  background: var(--assistant);
567
+ border-bottom-left-radius: 6px;
554
568
  }
555
569
 
556
570
  .chat-meta {
@@ -10,19 +10,19 @@ const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-cli-chat-")
10
10
  const binPath = path.join(repoRoot, "bin/aginti-cli.js");
11
11
 
12
12
  function runChat(inputText) {
13
+ return runCli(["chat", "--provider", "mock", "--routing", "manual", "--profile", "code"], inputText);
14
+ }
15
+
16
+ function runCli(args, inputText) {
13
17
  return new Promise((resolve, reject) => {
14
- const child = spawn(
15
- process.execPath,
16
- [binPath, "chat", "--provider", "mock", "--routing", "manual", "--profile", "code"],
17
- {
18
- cwd: tempRoot,
19
- stdio: ["pipe", "pipe", "pipe"],
20
- env: {
21
- ...process.env,
22
- AGINTIFLOW_RUNTIME_DIR: "",
23
- },
24
- }
25
- );
18
+ const child = spawn(process.execPath, [binPath, ...args], {
19
+ cwd: tempRoot,
20
+ stdio: ["pipe", "pipe", "pipe"],
21
+ env: {
22
+ ...process.env,
23
+ AGINTIFLOW_RUNTIME_DIR: "",
24
+ },
25
+ });
26
26
 
27
27
  let stdout = "";
28
28
  let stderr = "";
@@ -60,12 +60,21 @@ try {
60
60
  if (!result.stdout.includes("Interactive agent chat")) {
61
61
  throw new Error("interactive chat did not print its banner");
62
62
  }
63
+ if (!result.stdout.includes("status=running workingOn=") || !result.stdout.includes("status=idle session=")) {
64
+ throw new Error("interactive chat did not print simple run status updates");
65
+ }
66
+
67
+ const latest = await runCli(["resume"], "/exit\n");
68
+ if (!latest.stdout.includes("session=") || !latest.stdout.includes("Interactive agent chat")) {
69
+ throw new Error("bare aginti resume did not open the latest session interactively");
70
+ }
71
+
63
72
  console.log(
64
73
  JSON.stringify(
65
74
  {
66
75
  ok: true,
67
76
  projectRoot: tempRoot,
68
- checks: ["interactive-chat", "mock-file-write"],
77
+ checks: ["interactive-chat", "mock-file-write", "run-status", "resume-latest"],
69
78
  },
70
79
  null,
71
80
  2
package/src/cli.js CHANGED
@@ -219,7 +219,7 @@ export function parseArgs(argv) {
219
219
 
220
220
  function printUsage() {
221
221
  console.log(
222
- 'Usage: aginti [chat] OR aginti web [--port 3210] 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"'
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
223
  );
224
224
  }
225
225
 
@@ -356,6 +356,13 @@ async function handleSessionsCommand(argv) {
356
356
  process.exit(1);
357
357
  }
358
358
 
359
+ async function resolveResumeSessionId(sessionId) {
360
+ if (sessionId && sessionId !== "latest") return sessionId;
361
+ const sessions = await listProjectSessions(process.cwd(), 1);
362
+ if (sessions[0]?.sessionId) return sessions[0].sessionId;
363
+ throw new Error("No project-local sessions found. Run `aginti sessions list` to check this folder.");
364
+ }
365
+
359
366
  async function handleQueueCommand(argv) {
360
367
  const sessionId = argv[0] || "";
361
368
  const content = argv.slice(1).join(" ").trim();
@@ -456,12 +463,21 @@ export async function main(argv = process.argv.slice(2)) {
456
463
  }
457
464
 
458
465
  if (argv[0] === "resume") {
459
- const sessionId = argv[1] || "";
466
+ let sessionId = argv[1] || "";
460
467
  const prompt = argv.slice(2).join(" ").trim();
461
- if (!sessionId || !prompt) {
462
- console.error('Usage: aginti resume <session-id> "new prompt"');
468
+ try {
469
+ sessionId = await resolveResumeSessionId(sessionId);
470
+ } catch (error) {
471
+ console.error(error instanceof Error ? error.message : String(error));
463
472
  process.exit(1);
464
473
  }
474
+ if (!prompt) {
475
+ await startInteractiveCli(agentDefaults({ ...parseArgs([]), resume: sessionId }), {
476
+ packageDir,
477
+ packageVersion: packageJson.version,
478
+ });
479
+ return;
480
+ }
465
481
  const config = loadConfig(agentDefaults({ ...parseArgs([prompt]), resume: sessionId, goal: prompt }), { packageDir });
466
482
  await runAgent(config);
467
483
  return;
@@ -35,6 +35,8 @@ function printStatus(state) {
35
35
  console.log(`project=${process.cwd()}`);
36
36
  console.log(`cwd=${state.commandCwd || process.cwd()}`);
37
37
  console.log(`session=${state.sessionId || "new"}`);
38
+ console.log(`status=${state.status || "idle"}${state.activeGoal ? ` workingOn=${state.activeGoal}` : ""}`);
39
+ if (state.lastEvent) console.log(`last=${state.lastEvent}`);
38
40
  console.log(`provider=${state.provider || "auto"} routing=${state.routingMode} model=${state.model || "auto"}`);
39
41
  console.log(`profile=${state.taskProfile} maxSteps=${state.maxSteps}`);
40
42
  console.log(
@@ -45,6 +47,46 @@ function printStatus(state) {
45
47
  }
46
48
  }
47
49
 
50
+ function isAbortError(error) {
51
+ return error?.code === "ABORT_ERR" || error?.name === "AbortError";
52
+ }
53
+
54
+ function formatSessionLine(session) {
55
+ const goal = session.goal ? ` ${session.goal.slice(0, 72)}` : "";
56
+ return `${session.sessionId} ${session.provider || "unknown"}/${session.model || "unknown"} ${session.updatedAt || ""}${goal}`.trim();
57
+ }
58
+
59
+ async function latestSession() {
60
+ const sessions = await listProjectSessions(process.cwd(), 1);
61
+ return sessions[0] || null;
62
+ }
63
+
64
+ function printStatusEvent(state, label, details = "") {
65
+ state.lastEvent = details ? `${label}: ${details}` : label;
66
+ console.log(`status=${state.status || "running"} ${state.lastEvent}`);
67
+ }
68
+
69
+ async function printResumeHint(state) {
70
+ const sessionId = state.sessionId || "";
71
+ console.log("");
72
+ if (sessionId) {
73
+ console.log("Interrupted. Session saved.");
74
+ console.log(`Resume: aginti resume ${sessionId}`);
75
+ console.log(`One-shot: aginti resume ${sessionId} "continue"`);
76
+ } else {
77
+ console.log("Interrupted. No active session yet.");
78
+ const sessions = await listProjectSessions(process.cwd(), 5).catch(() => []);
79
+ if (sessions.length > 0) {
80
+ console.log("Recent sessions:");
81
+ for (const session of sessions) console.log(` ${formatSessionLine(session)}`);
82
+ console.log(`Resume latest: aginti resume ${sessions[0].sessionId}`);
83
+ console.log("List all: aginti sessions list");
84
+ } else {
85
+ console.log("Restart: aginti");
86
+ }
87
+ }
88
+ }
89
+
48
90
  function createState(args = {}) {
49
91
  return {
50
92
  provider: args.provider || "",
@@ -84,8 +126,15 @@ async function handleCommand(line, state, packageDir) {
84
126
  return true;
85
127
  }
86
128
  if (command === "resume") {
87
- if (!value) console.log("Usage: /resume <session-id>");
88
- else {
129
+ if (!value || value === "latest") {
130
+ const latest = await latestSession();
131
+ if (!latest) {
132
+ console.log("No project-local sessions found. Use /new or type a request to start one.");
133
+ } else {
134
+ state.sessionId = latest.sessionId;
135
+ console.log(`Resuming latest ${formatSessionLine(latest)}`);
136
+ }
137
+ } else {
89
138
  state.sessionId = value;
90
139
  console.log(`Resuming ${state.sessionId}`);
91
140
  }
@@ -199,8 +248,39 @@ async function runPrompt(prompt, state, packageDir) {
199
248
  { packageDir, baseDir: process.cwd() }
200
249
  );
201
250
 
202
- const result = await runAgent(config);
251
+ state.sessionId = config.resume || config.sessionId || state.sessionId;
252
+ state.status = "running";
253
+ state.activeGoal = prompt.replace(/\s+/g, " ").slice(0, 120);
254
+ state.lastEvent = "";
255
+ console.log(`session=${state.sessionId}`);
256
+ console.log(`status=running workingOn=${state.activeGoal}`);
257
+
258
+ const result = await runAgent({
259
+ ...config,
260
+ onEvent: (type, data = {}) => {
261
+ if (type === "plan.created") {
262
+ printStatusEvent(state, "planned");
263
+ } else if (type === "tool.started") {
264
+ printStatusEvent(state, "tool", data.toolName || "unknown");
265
+ } else if (type === "tool.completed") {
266
+ printStatusEvent(state, "tool_done", data.toolName || "unknown");
267
+ } else if (type === "tool.blocked") {
268
+ printStatusEvent(state, "tool_blocked", data.toolName || data.reason || "unknown");
269
+ } else if (type === "conversation.queued_input_applied") {
270
+ printStatusEvent(state, "queued_input_applied");
271
+ } else if (type === "session.finished") {
272
+ printStatusEvent(state, "finished");
273
+ } else if (type === "session.stopped") {
274
+ printStatusEvent(state, "stopped", data.reason || "");
275
+ } else if (type === "model.responded") {
276
+ printStatusEvent(state, "model_responded", data.content ? data.content.slice(0, 80).replace(/\s+/g, " ") : "");
277
+ }
278
+ },
279
+ });
203
280
  state.sessionId = result.sessionId || state.sessionId;
281
+ state.status = result.stopped ? "stopped" : "idle";
282
+ state.activeGoal = "";
283
+ console.log(`status=${state.status} session=${state.sessionId}`);
204
284
  }
205
285
 
206
286
  export async function startInteractiveCli(args = {}, { packageDir, packageVersion } = {}) {
@@ -219,6 +299,10 @@ export async function startInteractiveCli(args = {}, { packageDir, packageVersio
219
299
  answer = await rl.question("\naginti> ");
220
300
  } catch (error) {
221
301
  if (error?.code === "ERR_USE_AFTER_CLOSE") break;
302
+ if (isAbortError(error)) {
303
+ await printResumeHint(state);
304
+ break;
305
+ }
222
306
  throw error;
223
307
  }
224
308
  const line = answer.trim();
@@ -232,6 +316,10 @@ export async function startInteractiveCli(args = {}, { packageDir, packageVersio
232
316
  try {
233
317
  await runPrompt(line, state, packageDir);
234
318
  } catch (error) {
319
+ if (isAbortError(error)) {
320
+ await printResumeHint(state);
321
+ break;
322
+ }
235
323
  console.error(`error: ${error.message}`);
236
324
  }
237
325
  }