@lazyingart/agintiflow 0.1.0 → 0.2.0
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/AGENTS.md +6 -3
- package/README.md +68 -15
- package/docker/sandbox.Dockerfile +8 -0
- package/package.json +11 -3
- package/public/app.js +644 -11
- package/public/index.html +165 -20
- package/public/styles.css +586 -13
- package/scripts/setup-agent-toolchain-docker.sh +51 -0
- package/scripts/smoke-coding-tools.js +138 -0
- package/scripts/smoke-toolchain-docker.js +185 -0
- package/scripts/smoke-web-api.js +217 -0
- package/src/agent-runner.js +286 -21
- package/src/artifact-tunnel.js +401 -0
- package/src/cli.js +47 -3
- package/src/command-policy.js +21 -0
- package/src/config.js +9 -2
- package/src/docker-sandbox.js +38 -8
- package/src/guardrails.js +31 -0
- package/src/model-client.js +300 -6
- package/src/model-routing.js +40 -0
- package/src/session-store.js +4 -0
- package/src/tool-wrappers.js +6 -0
- package/src/web-db.js +48 -5
- package/src/workspace-tools.js +438 -0
- package/web.js +265 -14
package/web.js
CHANGED
|
@@ -7,17 +7,27 @@ import { resolveRuntimeConfig } from "./src/config.js";
|
|
|
7
7
|
import { WebDatabase } from "./src/web-db.js";
|
|
8
8
|
import { SessionStore } from "./src/session-store.js";
|
|
9
9
|
import { getModelPresets, getProviderDefaults, normalizeRoutingMode } from "./src/model-routing.js";
|
|
10
|
-
import { listAgentWrappers } from "./src/tool-wrappers.js";
|
|
10
|
+
import { listAgentWrappers, normalizeWrapperName } from "./src/tool-wrappers.js";
|
|
11
11
|
import { getDockerSandboxStatus, getSandboxLogs, runDockerPreflight } from "./src/docker-sandbox.js";
|
|
12
12
|
import { normalizePackageInstallPolicy, normalizeSandboxMode } from "./src/command-policy.js";
|
|
13
|
+
import { summarizeWorkspaceTools, WORKSPACE_TOOL_NAMES } from "./src/workspace-tools.js";
|
|
14
|
+
import {
|
|
15
|
+
buildArtifacts,
|
|
16
|
+
countUnreadArtifacts,
|
|
17
|
+
findArtifact,
|
|
18
|
+
readArtifactContent,
|
|
19
|
+
serializeArtifacts,
|
|
20
|
+
} from "./src/artifact-tunnel.js";
|
|
13
21
|
|
|
14
22
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
23
|
const __dirname = path.dirname(__filename);
|
|
16
|
-
const
|
|
24
|
+
const packageDir = __dirname;
|
|
25
|
+
const baseDir = path.resolve(process.env.AGINTIFLOW_RUNTIME_DIR || process.cwd());
|
|
17
26
|
const sessionsDir = path.join(baseDir, ".sessions");
|
|
18
27
|
|
|
19
28
|
const app = express();
|
|
20
29
|
const port = Number(process.env.PORT || 3210);
|
|
30
|
+
const host = process.env.HOST || "127.0.0.1";
|
|
21
31
|
const runs = new Map();
|
|
22
32
|
const db = new WebDatabase(baseDir);
|
|
23
33
|
const supportedLanguages = new Set([
|
|
@@ -43,6 +53,10 @@ function sessionStore(sessionId) {
|
|
|
43
53
|
return new SessionStore(sessionsDir, sessionId);
|
|
44
54
|
}
|
|
45
55
|
|
|
56
|
+
function isSafeSessionId(sessionId) {
|
|
57
|
+
return /^[A-Za-z0-9._:-]+$/.test(String(sessionId || "")) && !String(sessionId || "").includes("..");
|
|
58
|
+
}
|
|
59
|
+
|
|
46
60
|
function mapEventLogs(events) {
|
|
47
61
|
return events.map((event) => ({
|
|
48
62
|
at: event.timestamp,
|
|
@@ -65,6 +79,7 @@ async function loadStoredRun(sessionId) {
|
|
|
65
79
|
provider: meta.provider,
|
|
66
80
|
model: meta.model,
|
|
67
81
|
goal: meta.goal,
|
|
82
|
+
title: meta.title || "",
|
|
68
83
|
startedAt: meta.startedAt,
|
|
69
84
|
endedAt: meta.endedAt || null,
|
|
70
85
|
result: meta.result || "",
|
|
@@ -80,6 +95,7 @@ function serializeRun(run) {
|
|
|
80
95
|
provider: run.provider,
|
|
81
96
|
model: run.model,
|
|
82
97
|
goal: run.goal,
|
|
98
|
+
title: run.title || "",
|
|
83
99
|
startedAt: run.startedAt,
|
|
84
100
|
endedAt: run.endedAt || null,
|
|
85
101
|
result: run.result || "",
|
|
@@ -90,13 +106,14 @@ function serializeRun(run) {
|
|
|
90
106
|
|
|
91
107
|
function normalizePreferencePayload(body = {}, current = db.getPreferences()) {
|
|
92
108
|
const modelPresets = getModelPresets();
|
|
93
|
-
const
|
|
94
|
-
const provider =
|
|
95
|
-
|
|
109
|
+
const providerCandidate = body.provider || current.provider || "deepseek";
|
|
110
|
+
const provider = ["openai", "deepseek", "mock"].includes(providerCandidate) ? providerCandidate : "deepseek";
|
|
111
|
+
const routingMode =
|
|
112
|
+
provider === "mock" ? "manual" : normalizeRoutingMode(body.routingMode || current.routingMode || "smart");
|
|
96
113
|
const providerDefaults = getProviderDefaults(provider);
|
|
97
114
|
const parsedMaxSteps = Number(body.maxSteps);
|
|
98
115
|
const parsedWrapperTimeoutMs = Number(body.wrapperTimeoutMs);
|
|
99
|
-
const sandboxMode = normalizeSandboxMode(body.sandboxMode || current.sandboxMode || "docker-
|
|
116
|
+
const sandboxMode = normalizeSandboxMode(body.sandboxMode || current.sandboxMode || "docker-workspace");
|
|
100
117
|
|
|
101
118
|
return {
|
|
102
119
|
routingMode,
|
|
@@ -117,8 +134,10 @@ function normalizePreferencePayload(body = {}, current = db.getPreferences()) {
|
|
|
117
134
|
? body.commandCwd.trim()
|
|
118
135
|
: current.commandCwd || path.resolve(baseDir, ".."),
|
|
119
136
|
allowShellTool: typeof body.allowShellTool === "boolean" ? body.allowShellTool : Boolean(current.allowShellTool),
|
|
137
|
+
allowFileTools: typeof body.allowFileTools === "boolean" ? body.allowFileTools : current.allowFileTools !== false,
|
|
120
138
|
allowWrapperTools:
|
|
121
139
|
typeof body.allowWrapperTools === "boolean" ? body.allowWrapperTools : Boolean(current.allowWrapperTools),
|
|
140
|
+
preferredWrapper: normalizeWrapperName(body.preferredWrapper || current.preferredWrapper || "codex"),
|
|
122
141
|
wrapperTimeoutMs:
|
|
123
142
|
Number.isFinite(parsedWrapperTimeoutMs) && parsedWrapperTimeoutMs >= 10000
|
|
124
143
|
? parsedWrapperTimeoutMs
|
|
@@ -180,7 +199,9 @@ function buildRunConfig(body, overrides = {}) {
|
|
|
180
199
|
allowPasswords: merged.allowPasswords,
|
|
181
200
|
allowDestructive: merged.allowDestructive,
|
|
182
201
|
allowShellTool: merged.allowShellTool,
|
|
202
|
+
allowFileTools: merged.allowFileTools,
|
|
183
203
|
allowWrapperTools: merged.allowWrapperTools,
|
|
204
|
+
preferredWrapper: merged.preferredWrapper,
|
|
184
205
|
wrapperTimeoutMs: merged.wrapperTimeoutMs,
|
|
185
206
|
sandboxMode: merged.sandboxMode,
|
|
186
207
|
packageInstallPolicy: merged.packageInstallPolicy,
|
|
@@ -188,6 +209,7 @@ function buildRunConfig(body, overrides = {}) {
|
|
|
188
209
|
dockerSandboxImage: merged.dockerSandboxImage,
|
|
189
210
|
commandCwd: merged.commandCwd,
|
|
190
211
|
baseDir,
|
|
212
|
+
packageDir,
|
|
191
213
|
sessionId: overrides.sessionId,
|
|
192
214
|
}
|
|
193
215
|
);
|
|
@@ -214,6 +236,7 @@ function deriveSessionRecordFromState(state, existing = null) {
|
|
|
214
236
|
provider: state.provider || existing?.provider || "deepseek",
|
|
215
237
|
model: state.model || existing?.model || getProviderDefaults(state.provider || existing?.provider || "deepseek").model,
|
|
216
238
|
goal: state.goal || existing?.goal || "",
|
|
239
|
+
title: existing?.title || "",
|
|
217
240
|
status,
|
|
218
241
|
startedAt: state.createdAt || existing?.startedAt || new Date().toISOString(),
|
|
219
242
|
updatedAt,
|
|
@@ -295,12 +318,35 @@ async function loadChat(sessionId) {
|
|
|
295
318
|
return {
|
|
296
319
|
sessionId,
|
|
297
320
|
goal: state?.goal || meta?.goal || "",
|
|
321
|
+
title: meta?.title || "",
|
|
298
322
|
provider: state?.provider || meta?.provider || "",
|
|
299
323
|
model: state?.model || meta?.model || "",
|
|
300
324
|
chat: deriveChatFromState(state, meta),
|
|
301
325
|
};
|
|
302
326
|
}
|
|
303
327
|
|
|
328
|
+
function normalizeSessionTitle(title) {
|
|
329
|
+
return String(title || "")
|
|
330
|
+
.replace(/[\u0000-\u001f\u007f]/g, " ")
|
|
331
|
+
.replace(/\s+/g, " ")
|
|
332
|
+
.trim()
|
|
333
|
+
.slice(0, 90);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function autoSessionTitleFromChat(data) {
|
|
337
|
+
const candidate =
|
|
338
|
+
data?.chat?.find((entry) => entry.role === "user" && String(entry.content || "").trim())?.content ||
|
|
339
|
+
data?.goal ||
|
|
340
|
+
"Untitled conversation";
|
|
341
|
+
const cleaned = normalizeSessionTitle(candidate)
|
|
342
|
+
.replace(/^please\s+/i, "")
|
|
343
|
+
.replace(/^could you\s+/i, "")
|
|
344
|
+
.replace(/^can you\s+/i, "")
|
|
345
|
+
.replace(/^plz\s+/i, "")
|
|
346
|
+
.replace(/^i want to\s+/i, "");
|
|
347
|
+
return normalizeSessionTitle(cleaned.length > 72 ? `${cleaned.slice(0, 69).trim()}...` : cleaned) || "Untitled conversation";
|
|
348
|
+
}
|
|
349
|
+
|
|
304
350
|
async function ensureNotRunning(sessionId) {
|
|
305
351
|
const inMemory = runs.get(sessionId);
|
|
306
352
|
if (inMemory?.status === "running") {
|
|
@@ -313,6 +359,67 @@ async function ensureNotRunning(sessionId) {
|
|
|
313
359
|
}
|
|
314
360
|
}
|
|
315
361
|
|
|
362
|
+
async function collectWorkspaceActivity(limit = 24) {
|
|
363
|
+
const activity = [];
|
|
364
|
+
const sessions = db.listSessions(30);
|
|
365
|
+
|
|
366
|
+
for (const session of sessions) {
|
|
367
|
+
const store = sessionStore(session.sessionId);
|
|
368
|
+
const events = await store.loadEvents();
|
|
369
|
+
for (const event of events) {
|
|
370
|
+
if (event.type === "file.changed") {
|
|
371
|
+
activity.push({
|
|
372
|
+
at: event.timestamp,
|
|
373
|
+
kind: "changed",
|
|
374
|
+
sessionId: session.sessionId,
|
|
375
|
+
goal: session.goal,
|
|
376
|
+
...event.data,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (
|
|
381
|
+
event.type === "tool.blocked" &&
|
|
382
|
+
(WORKSPACE_TOOL_NAMES.includes(event.data?.toolName) || WORKSPACE_TOOL_NAMES.includes(event.data?.args?.toolName))
|
|
383
|
+
) {
|
|
384
|
+
activity.push({
|
|
385
|
+
at: event.timestamp,
|
|
386
|
+
kind: "blocked",
|
|
387
|
+
sessionId: session.sessionId,
|
|
388
|
+
goal: session.goal,
|
|
389
|
+
toolName: event.data?.toolName || "",
|
|
390
|
+
path: event.data?.args?.path || "",
|
|
391
|
+
reason: event.data?.reason || "",
|
|
392
|
+
category: event.data?.category || "",
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return activity.sort((a, b) => String(b.at).localeCompare(String(a.at))).slice(0, limit);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async function loadArtifactBundle(sessionId) {
|
|
402
|
+
if (!isSafeSessionId(sessionId)) {
|
|
403
|
+
return { error: "Invalid session id.", status: 400 };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const meta = db.getSession(sessionId);
|
|
407
|
+
const store = sessionStore(sessionId);
|
|
408
|
+
const events = await store.loadEvents();
|
|
409
|
+
if (!meta && events.length === 0) {
|
|
410
|
+
return { error: "Session not found.", status: 404 };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const preferences = normalizePreferencePayload({}, db.getPreferences());
|
|
414
|
+
const config = buildRunConfig({ ...preferences, goal: "" });
|
|
415
|
+
const bundle = buildArtifacts({ sessionId, events, store });
|
|
416
|
+
return {
|
|
417
|
+
...bundle,
|
|
418
|
+
store,
|
|
419
|
+
config,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
316
423
|
function createRunRecord(config, goal, existingLogs = []) {
|
|
317
424
|
return {
|
|
318
425
|
sessionId: config.sessionId,
|
|
@@ -349,11 +456,15 @@ function wireRun(record, config) {
|
|
|
349
456
|
onEvent: (type, data = {}) => push("event", type, data),
|
|
350
457
|
})
|
|
351
458
|
.then((result) => {
|
|
352
|
-
record.status = "finished";
|
|
459
|
+
record.status = result?.stopped ? "stopped" : "finished";
|
|
353
460
|
record.result = result?.result || "";
|
|
461
|
+
record.error = result?.stopped ? result.reason || "Run stopped before finish." : "";
|
|
354
462
|
record.updatedAt = new Date().toISOString();
|
|
355
463
|
record.endedAt = new Date().toISOString();
|
|
356
|
-
push("session", "Run finished", {
|
|
464
|
+
push("session", result?.stopped ? "Run stopped" : "Run finished", {
|
|
465
|
+
result: record.result,
|
|
466
|
+
reason: result?.reason || "",
|
|
467
|
+
});
|
|
357
468
|
db.upsertSession(record);
|
|
358
469
|
})
|
|
359
470
|
.catch((error) => {
|
|
@@ -370,12 +481,15 @@ app.use(express.json({ limit: "1mb" }));
|
|
|
370
481
|
app.use(express.static(path.join(__dirname, "public")));
|
|
371
482
|
app.use("/logos", express.static(path.join(__dirname, "logos")));
|
|
372
483
|
|
|
373
|
-
app.get("/api/config", (_req, res) => {
|
|
484
|
+
app.get("/api/config", async (_req, res) => {
|
|
485
|
+
await syncStoredSessions();
|
|
374
486
|
const preferences = normalizePreferencePayload({}, db.getPreferences());
|
|
487
|
+
const config = buildRunConfig({ ...preferences, goal: "" });
|
|
375
488
|
res.json({
|
|
376
489
|
defaults: {
|
|
377
490
|
openai: publicProviderDefault("openai"),
|
|
378
491
|
deepseek: publicProviderDefault("deepseek"),
|
|
492
|
+
mock: publicProviderDefault("mock"),
|
|
379
493
|
headless: true,
|
|
380
494
|
maxSteps: 15,
|
|
381
495
|
},
|
|
@@ -387,12 +501,14 @@ app.get("/api/config", (_req, res) => {
|
|
|
387
501
|
sandbox: {
|
|
388
502
|
logs: getSandboxLogs(),
|
|
389
503
|
},
|
|
504
|
+
workspace: summarizeWorkspaceTools(config),
|
|
390
505
|
preferences,
|
|
391
506
|
keyStatus: {
|
|
392
507
|
openai: Boolean(process.env.OPENAI_API_KEY),
|
|
393
508
|
deepseek: Boolean(process.env.DEEPSEEK_API_KEY),
|
|
509
|
+
mock: true,
|
|
394
510
|
},
|
|
395
|
-
sessions: db.listSessions(
|
|
511
|
+
sessions: db.listSessions(100),
|
|
396
512
|
});
|
|
397
513
|
});
|
|
398
514
|
|
|
@@ -419,8 +535,73 @@ app.post("/api/preferences", (req, res) => {
|
|
|
419
535
|
res.json({ ok: true, preferences });
|
|
420
536
|
});
|
|
421
537
|
|
|
422
|
-
app.get("/api/sessions", (_req, res) => {
|
|
423
|
-
|
|
538
|
+
app.get("/api/sessions", async (_req, res) => {
|
|
539
|
+
await syncStoredSessions();
|
|
540
|
+
res.json({ sessions: db.listSessions(100) });
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
app.get("/api/workspace/changes", async (_req, res) => {
|
|
544
|
+
const preferences = normalizePreferencePayload({}, db.getPreferences());
|
|
545
|
+
const config = buildRunConfig({ ...preferences, goal: "" });
|
|
546
|
+
res.json({
|
|
547
|
+
ok: true,
|
|
548
|
+
workspace: summarizeWorkspaceTools(config),
|
|
549
|
+
activity: await collectWorkspaceActivity(),
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
app.get("/api/sessions/:sessionId/artifacts", async (req, res) => {
|
|
554
|
+
const bundle = await loadArtifactBundle(req.params.sessionId);
|
|
555
|
+
if (bundle.error) {
|
|
556
|
+
res.status(bundle.status || 500).json({ error: bundle.error });
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
res.json({
|
|
561
|
+
ok: true,
|
|
562
|
+
sessionId: req.params.sessionId,
|
|
563
|
+
items: serializeArtifacts(bundle.items),
|
|
564
|
+
selectedItemId: bundle.selectedItemId,
|
|
565
|
+
unreadCount: countUnreadArtifacts(bundle.items, req.query.seenAfter),
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
app.get("/api/sessions/:sessionId/artifacts/:artifactId", async (req, res) => {
|
|
570
|
+
const bundle = await loadArtifactBundle(req.params.sessionId);
|
|
571
|
+
if (bundle.error) {
|
|
572
|
+
res.status(bundle.status || 500).json({ error: bundle.error });
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const artifact = findArtifact(bundle.items, req.params.artifactId);
|
|
577
|
+
const content = await readArtifactContent(artifact, bundle);
|
|
578
|
+
if (!content.ok) {
|
|
579
|
+
res.status(artifact ? 400 : 404).json({ error: content.error || "Artifact not found." });
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
res.json(content);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
app.post("/api/sessions/:sessionId/artifacts/select", async (req, res) => {
|
|
586
|
+
const bundle = await loadArtifactBundle(req.params.sessionId);
|
|
587
|
+
if (bundle.error) {
|
|
588
|
+
res.status(bundle.status || 500).json({ error: bundle.error });
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const artifactId = String(req.body?.artifactId || "");
|
|
593
|
+
const artifact = findArtifact(bundle.items, artifactId);
|
|
594
|
+
if (!artifact) {
|
|
595
|
+
res.status(404).json({ error: "Artifact not found." });
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
await sessionStore(req.params.sessionId).appendEvent("canvas.selected", {
|
|
600
|
+
artifactId,
|
|
601
|
+
title: artifact.title,
|
|
602
|
+
source: "user",
|
|
603
|
+
});
|
|
604
|
+
res.json({ ok: true, artifact: serializeArtifacts([artifact])[0] });
|
|
424
605
|
});
|
|
425
606
|
|
|
426
607
|
app.get("/api/sessions/:sessionId/chat", async (req, res) => {
|
|
@@ -432,6 +613,75 @@ app.get("/api/sessions/:sessionId/chat", async (req, res) => {
|
|
|
432
613
|
res.json(data);
|
|
433
614
|
});
|
|
434
615
|
|
|
616
|
+
app.patch("/api/sessions/:sessionId", async (req, res) => {
|
|
617
|
+
const sessionId = req.params.sessionId;
|
|
618
|
+
if (!isSafeSessionId(sessionId)) {
|
|
619
|
+
res.status(400).json({ error: "Invalid session id." });
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const meta = db.getSession(sessionId);
|
|
624
|
+
if (!meta) {
|
|
625
|
+
res.status(404).json({ error: "Session not found." });
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const title = normalizeSessionTitle(req.body?.title);
|
|
630
|
+
if (!title) {
|
|
631
|
+
res.status(400).json({ error: "Title is required." });
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
db.renameSession(sessionId, title);
|
|
636
|
+
await sessionStore(sessionId).appendEvent("session.renamed", { title }).catch(() => {});
|
|
637
|
+
res.json({ ok: true, session: db.getSession(sessionId) });
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
app.post("/api/sessions/:sessionId/auto-title", async (req, res) => {
|
|
641
|
+
const sessionId = req.params.sessionId;
|
|
642
|
+
if (!isSafeSessionId(sessionId)) {
|
|
643
|
+
res.status(400).json({ error: "Invalid session id." });
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const data = await loadChat(sessionId);
|
|
648
|
+
if (!data) {
|
|
649
|
+
res.status(404).json({ error: "Session not found." });
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const title = autoSessionTitleFromChat(data);
|
|
654
|
+
db.renameSession(sessionId, title);
|
|
655
|
+
await sessionStore(sessionId).appendEvent("session.auto_renamed", { title }).catch(() => {});
|
|
656
|
+
res.json({ ok: true, session: db.getSession(sessionId) });
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
app.delete("/api/sessions/:sessionId", async (req, res) => {
|
|
660
|
+
const sessionId = req.params.sessionId;
|
|
661
|
+
if (!isSafeSessionId(sessionId)) {
|
|
662
|
+
res.status(400).json({ error: "Invalid session id." });
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
try {
|
|
667
|
+
await ensureNotRunning(sessionId);
|
|
668
|
+
} catch (error) {
|
|
669
|
+
res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const meta = db.getSession(sessionId);
|
|
674
|
+
if (!meta) {
|
|
675
|
+
res.status(404).json({ error: "Session not found." });
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
runs.delete(sessionId);
|
|
680
|
+
db.deleteSession(sessionId);
|
|
681
|
+
await sessionStore(sessionId).remove();
|
|
682
|
+
res.json({ ok: true, sessionId });
|
|
683
|
+
});
|
|
684
|
+
|
|
435
685
|
app.post("/api/runs", async (req, res) => {
|
|
436
686
|
const body = req.body || {};
|
|
437
687
|
const goal = String(body.goal || "").trim();
|
|
@@ -523,8 +773,9 @@ app.get("/health", (_req, res) => {
|
|
|
523
773
|
res.json({ ok: true, port });
|
|
524
774
|
});
|
|
525
775
|
|
|
776
|
+
await fs.mkdir(sessionsDir, { recursive: true });
|
|
526
777
|
await syncStoredSessions();
|
|
527
778
|
|
|
528
|
-
app.listen(port, () => {
|
|
529
|
-
console.log(`Website control agent UI running on http
|
|
779
|
+
app.listen(port, host, () => {
|
|
780
|
+
console.log(`Website control agent UI running on http://${host}:${port}`);
|
|
530
781
|
});
|