@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.
@@ -0,0 +1,105 @@
1
+ import fs from "node:fs/promises";
2
+ import http from "node:http";
3
+ import path from "node:path";
4
+
5
+ const root = path.resolve(process.argv[2] || process.cwd());
6
+ const port = Number(process.argv[3] || 0);
7
+ const host = "127.0.0.1";
8
+
9
+ const MIME_TYPES = new Map([
10
+ [".html", "text/html; charset=utf-8"],
11
+ [".htm", "text/html; charset=utf-8"],
12
+ [".css", "text/css; charset=utf-8"],
13
+ [".js", "text/javascript; charset=utf-8"],
14
+ [".mjs", "text/javascript; charset=utf-8"],
15
+ [".json", "application/json; charset=utf-8"],
16
+ [".svg", "image/svg+xml"],
17
+ [".png", "image/png"],
18
+ [".jpg", "image/jpeg"],
19
+ [".jpeg", "image/jpeg"],
20
+ [".gif", "image/gif"],
21
+ [".webp", "image/webp"],
22
+ [".pdf", "application/pdf"],
23
+ [".txt", "text/plain; charset=utf-8"],
24
+ [".md", "text/markdown; charset=utf-8"],
25
+ ]);
26
+
27
+ function send(res, statusCode, body, headers = {}) {
28
+ res.writeHead(statusCode, {
29
+ "Cache-Control": "no-store",
30
+ "X-Content-Type-Options": "nosniff",
31
+ ...headers,
32
+ });
33
+ res.end(body);
34
+ }
35
+
36
+ function normalizeUrlPath(urlPath) {
37
+ const decoded = decodeURIComponent(urlPath.split("?")[0] || "/");
38
+ const withoutLeadingSlash = decoded.replace(/^\/+/, "") || "index.html";
39
+ return path.normalize(withoutLeadingSlash);
40
+ }
41
+
42
+ function isInsideRoot(target) {
43
+ const relative = path.relative(root, target);
44
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
45
+ }
46
+
47
+ function isBlockedPath(relativePath) {
48
+ const segments = relativePath.split(path.sep).filter(Boolean);
49
+ const lowerBase = (segments.at(-1) || "").toLowerCase();
50
+ const lowerPath = segments.join("/").toLowerCase();
51
+ if (segments.includes(".git") || segments.includes("node_modules") || segments.includes(".sessions")) return true;
52
+ if (lowerBase === ".env" || lowerBase.startsWith(".env.")) return true;
53
+ if (lowerBase === ".npmrc" || lowerBase === ".pypirc") return true;
54
+ return /(^|\/)(secrets?|tokens?|passwords?|private[-_]?keys?|credentials?)(\/|\.|$)/i.test(lowerPath);
55
+ }
56
+
57
+ const server = http.createServer(async (req, res) => {
58
+ if (!["GET", "HEAD"].includes(req.method || "")) {
59
+ send(res, 405, "Method Not Allowed");
60
+ return;
61
+ }
62
+
63
+ try {
64
+ const relative = normalizeUrlPath(req.url || "/");
65
+ const target = path.resolve(root, relative);
66
+ if (!isInsideRoot(target) || isBlockedPath(path.relative(root, target))) {
67
+ send(res, 403, "Forbidden");
68
+ return;
69
+ }
70
+
71
+ let filePath = target;
72
+ const stat = await fs.stat(filePath).catch(() => null);
73
+ if (stat?.isDirectory()) {
74
+ filePath = path.join(filePath, "index.html");
75
+ }
76
+
77
+ const finalStat = await fs.stat(filePath).catch(() => null);
78
+ if (!finalStat?.isFile()) {
79
+ send(res, 404, "Not Found");
80
+ return;
81
+ }
82
+
83
+ const contentType = MIME_TYPES.get(path.extname(filePath).toLowerCase()) || "application/octet-stream";
84
+ res.writeHead(200, {
85
+ "Content-Type": contentType,
86
+ "Content-Length": finalStat.size,
87
+ "Cache-Control": "no-store",
88
+ "X-Content-Type-Options": "nosniff",
89
+ });
90
+ if (req.method === "HEAD") {
91
+ res.end();
92
+ return;
93
+ }
94
+ const content = await fs.readFile(filePath);
95
+ res.end(content);
96
+ } catch (error) {
97
+ send(res, 500, error instanceof Error ? error.message : String(error));
98
+ }
99
+ });
100
+
101
+ server.listen(port, host, () => {
102
+ const address = server.address();
103
+ const actualPort = typeof address === "object" && address ? address.port : port;
104
+ console.log(`AgInTiFlow static preview http://${host}:${actualPort}/ root=${root}`);
105
+ });
@@ -145,7 +145,7 @@ export function summarizeWorkspaceTools(config) {
145
145
  return {
146
146
  enabled: Boolean(config.allowFileTools),
147
147
  workspace: workspaceRoot(config),
148
- tools: WORKSPACE_TOOL_NAMES,
148
+ tools: [...WORKSPACE_TOOL_NAMES, "open_workspace_file", "preview_workspace"],
149
149
  writeTools: WORKSPACE_WRITE_TOOL_NAMES,
150
150
  limits: {
151
151
  maxReadBytes: MAX_READ_BYTES,
package/web.js CHANGED
@@ -455,6 +455,8 @@ function createRunRecord(config, goal, existingLogs = []) {
455
455
  }
456
456
 
457
457
  function wireRun(record, config) {
458
+ const abortController = new AbortController();
459
+ record.abortController = abortController;
458
460
  const push = (kind, message, data = {}) => {
459
461
  record.logs.push({
460
462
  at: new Date().toISOString(),
@@ -470,6 +472,7 @@ function wireRun(record, config) {
470
472
 
471
473
  void runAgent({
472
474
  ...config,
475
+ abortSignal: abortController.signal,
473
476
  onLog: (message, data = {}) => push("log", message, data),
474
477
  onEvent: (type, data = {}) => push("event", type, data),
475
478
  })
@@ -846,6 +849,30 @@ app.get("/api/runs/:sessionId", async (req, res) => {
846
849
  res.json(stored);
847
850
  });
848
851
 
852
+ app.post("/api/runs/:sessionId/stop", async (req, res) => {
853
+ const run = runs.get(req.params.sessionId);
854
+ if (!run) {
855
+ res.status(404).json({ error: "Run not found." });
856
+ return;
857
+ }
858
+ if (run.status !== "running") {
859
+ res.status(409).json({ error: "Run is not active.", status: run.status });
860
+ return;
861
+ }
862
+
863
+ run.abortController?.abort(new Error("Stopped from web UI."));
864
+ run.logs.push({
865
+ at: new Date().toISOString(),
866
+ kind: "event",
867
+ message: "session.stop_requested",
868
+ data: { source: "web" },
869
+ });
870
+ run.updatedAt = new Date().toISOString();
871
+ db.upsertSession(run);
872
+ await sessionStore(req.params.sessionId).appendEvent("session.stop_requested", { source: "web" }).catch(() => {});
873
+ res.json({ ok: true, sessionId: req.params.sessionId });
874
+ });
875
+
849
876
  app.get("/health", (_req, res) => {
850
877
  res.json({ ok: true, port });
851
878
  });