agenttwin 0.3.4 → 0.3.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 +8 -0
- package/dist/api.js +96 -56
- package/dist/cli.js +210 -180
- package/dist/dashboard/assets/dashboard/agent-home.css +33 -10
- package/dist/dashboard/assets/dashboard/bay-morning.png +0 -0
- package/dist/dashboard/assets/dashboard/golden-gate-afternoon.png +0 -0
- package/dist/dashboard/assets/dashboard/inventory.json +1 -1
- package/dist/dashboard/assets/dashboard/runs.json +1 -1
- package/dist/dashboard/assets/next/static/HZwGKKvvx_oO6caYggjFh/_buildManifest.js +1 -0
- package/dist/dashboard/assets/next/static/HZwGKKvvx_oO6caYggjFh/_ssgManifest.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/255-09fb0d129bb12f51.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/772-85561381eedc95da.js +67 -0
- package/dist/dashboard/assets/next/static/chunks/app/_not-found/page-5dc6a60918e8652d.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/app/dashboard/layout-74603c2c8eaf0760.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/app/dashboard/page-b79ed135f6bac5f7.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/app/demo/page-760a47a5e4dee43f.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/app/docs/[[...slug]]/page-80a0c2c82d5b4efd.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/app/layout-1a40c59e535513d4.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/app/page-de4735052adab906.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/app/pricing/page-7703fc31d6dff0f7.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/app/sitemap.xml/route-ef5a216a578eb7a8.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/framework-292291387d6b2e39.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/main-aea6e71289384293.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/main-app-99295a8cc37e8a47.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/webpack-f3b6dd27998e84ce.js +1 -0
- package/dist/dashboard/assets/next/static/css/1323d7da89b3fa5e.css +1 -0
- package/dist/dashboard/assets/next/static/css/45513ebc8c6ae800.css +1 -0
- package/dist/dashboard/assets/next/static/css/61ec74d503f353d4.css +1 -0
- package/dist/dashboard/assets/next/static/css/9282330a2fd1ab73.css +1 -0
- package/dist/dashboard/assets/next/static/css/96e4f356a1159071.css +1 -0
- package/dist/dashboard/assets/next/static/css/c5ac36ef5726e746.css +1 -0
- package/dist/dashboard/assets/next/static/media/bay-morning.1a9f358c.png +0 -0
- package/dist/dashboard/assets/next/static/media/golden-gate-afternoon.3e7cea58.png +0 -0
- package/dist/dashboard/assets/next/static/media/inter-var.e4af272c.woff2 +0 -0
- package/dist/dashboard/assets/next/static/media/moonlit-coast.bbe901a1.png +0 -0
- package/dist/dashboard/assets/next/static/media/plexmono-400.d3ebbfd6.woff2 +0 -0
- package/dist/dashboard/index.html +1 -140
- package/dist/gateway.js +96 -56
- package/package.json +1 -1
- package/dist/dashboard/assets/dashboard/agent-home.js +0 -298
- package/dist/dashboard/assets/dashboard/dashboard.js +0 -462
- package/dist/dashboard/assets/dashboard/hosted.js +0 -89
- package/dist/dashboard/assets/dashboard/workflows.js +0 -39
- package/dist/dashboard/assets/vendor/dompurify.LICENSE +0 -202
- package/dist/dashboard/assets/vendor/marked.LICENSE +0 -44
- package/dist/dashboard/assets/vendor/marked.js +0 -82
- package/dist/dashboard/assets/vendor/purify.min.js +0 -7
package/README.md
CHANGED
|
@@ -15,6 +15,14 @@ npx agenttwin example --out ./agenttwin-example
|
|
|
15
15
|
|
|
16
16
|
`init` writes the inventory, workflow candidates, `agenttwin.config.json`, `ENVIRONMENT.md`, `REPLICATION.md`, and an offline browser report. Discovery is evidence, not an automatically executable environment. It never uploads source. `init --json --dry-run` writes nothing.
|
|
17
17
|
|
|
18
|
+
Installed packages and source references are clues, not proof that an integration is active. Credentials, environment keys and deployment configuration are kept separately. Unmatched `.env` entries do not require setup and are never turned into services by an interactive prompt.
|
|
19
|
+
|
|
20
|
+
Exclude unused integrations by name; your choices are remembered for this repository:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npx agenttwin init --exclude "Ollama,ClickHouse,AWS,Google Cloud"
|
|
24
|
+
```
|
|
25
|
+
|
|
18
26
|
## Test through the CLI
|
|
19
27
|
|
|
20
28
|
```sh
|
package/dist/api.js
CHANGED
|
@@ -782,11 +782,50 @@ var init_catalog = __esm({
|
|
|
782
782
|
}
|
|
783
783
|
});
|
|
784
784
|
|
|
785
|
+
// src/inventory/store.ts
|
|
786
|
+
import { mkdir, readFile as readFile5, writeFile } from "fs/promises";
|
|
787
|
+
import { join as join5 } from "path";
|
|
788
|
+
function storeDir(cwd) {
|
|
789
|
+
return join5(cwd, STORE_DIR);
|
|
790
|
+
}
|
|
791
|
+
function storePath(cwd, name) {
|
|
792
|
+
return join5(storeDir(cwd), name);
|
|
793
|
+
}
|
|
794
|
+
async function readJson(path) {
|
|
795
|
+
try {
|
|
796
|
+
return JSON.parse(await readFile5(path, "utf8"));
|
|
797
|
+
} catch {
|
|
798
|
+
return void 0;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
var STORE_DIR, STORE_GITIGNORE, readDecisions;
|
|
802
|
+
var init_store = __esm({
|
|
803
|
+
"src/inventory/store.ts"() {
|
|
804
|
+
"use strict";
|
|
805
|
+
STORE_DIR = ".agenttwin";
|
|
806
|
+
STORE_GITIGNORE = [
|
|
807
|
+
"inventory.json",
|
|
808
|
+
"plan.json",
|
|
809
|
+
"permissions.local.json",
|
|
810
|
+
"report.html",
|
|
811
|
+
"demo/",
|
|
812
|
+
"trial/",
|
|
813
|
+
"feedback.json",
|
|
814
|
+
"cache/",
|
|
815
|
+
"logs/",
|
|
816
|
+
"connections/",
|
|
817
|
+
""
|
|
818
|
+
].join("\n");
|
|
819
|
+
readDecisions = (cwd) => readJson(storePath(cwd, "decisions.json"));
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
|
|
785
823
|
// src/inventory/project-products.ts
|
|
786
824
|
async function projectProducts(cwd, data, base) {
|
|
787
825
|
const current = new Set(base.tools.map((t) => t.id));
|
|
788
826
|
const known = new Set(PROVIDERS.map((p) => p.title));
|
|
789
|
-
const
|
|
827
|
+
const excluded = new Set(((await readDecisions(cwd))?.items ?? []).filter((d) => d.kind === "exclude_tool").map((d) => d.target));
|
|
828
|
+
const tools = data.tools.filter((t) => !excluded.has(t.id) && (current.has(t.id) || !known.has(t.title) && t.category !== "access" && t.category !== "tooling"));
|
|
790
829
|
const ids = new Set(tools.map((t) => t.id));
|
|
791
830
|
const workflows = data.workflows.map((w) => ({ ...w, tools: w.tools.filter((id) => ids.has(id)), steps: w.steps.filter((s) => ids.has(s.tool)) }));
|
|
792
831
|
const google = tools.find((t) => /Google \(Gmail/.test(t.title));
|
|
@@ -840,6 +879,7 @@ var init_project_products = __esm({
|
|
|
840
879
|
init_entrypoints();
|
|
841
880
|
init_catalog();
|
|
842
881
|
init_dashboard_transfer();
|
|
882
|
+
init_store();
|
|
843
883
|
}
|
|
844
884
|
});
|
|
845
885
|
|
|
@@ -861,8 +901,8 @@ var init_sanitize = __esm({
|
|
|
861
901
|
});
|
|
862
902
|
|
|
863
903
|
// src/commands/dashboard.ts
|
|
864
|
-
import { cp, mkdir, readFile as
|
|
865
|
-
import { basename, join as
|
|
904
|
+
import { cp, mkdir as mkdir2, readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
|
|
905
|
+
import { basename, join as join6 } from "path";
|
|
866
906
|
import { fileURLToPath } from "url";
|
|
867
907
|
function dashboardInventory(cwd, inventory) {
|
|
868
908
|
return JSON.parse(sanitizeOutput(JSON.stringify({
|
|
@@ -898,8 +938,8 @@ __export(session_exports, {
|
|
|
898
938
|
writeSessionTask: () => writeSessionTask
|
|
899
939
|
});
|
|
900
940
|
import { spawn as spawn2, spawnSync } from "child_process";
|
|
901
|
-
import { writeFile as
|
|
902
|
-
import { join as
|
|
941
|
+
import { writeFile as writeFile3, readFile as readFile7 } from "fs/promises";
|
|
942
|
+
import { join as join7 } from "path";
|
|
903
943
|
function sessionTask() {
|
|
904
944
|
return `# Prepare this repository for AgentTwin
|
|
905
945
|
|
|
@@ -924,8 +964,8 @@ function installedOpenCode() {
|
|
|
924
964
|
return spawnSync("opencode", ["--version"], { stdio: "ignore", timeout: 5e3 }).status === 0;
|
|
925
965
|
}
|
|
926
966
|
async function writeSessionTask(cwd) {
|
|
927
|
-
const path =
|
|
928
|
-
await
|
|
967
|
+
const path = join7(cwd, ".agenttwin/AGENT-TASK.md");
|
|
968
|
+
await writeFile3(path, sessionTask(), "utf8");
|
|
929
969
|
return path;
|
|
930
970
|
}
|
|
931
971
|
async function launchSession(cwd) {
|
|
@@ -940,14 +980,14 @@ async function launchSession(cwd) {
|
|
|
940
980
|
async function loadDashboardDiscovery(cwd, inventory) {
|
|
941
981
|
let previousHead;
|
|
942
982
|
try {
|
|
943
|
-
previousHead = JSON.parse(await
|
|
983
|
+
previousHead = JSON.parse(await readFile7(join7(cwd, ".agenttwin/git.json"), "utf8")).head;
|
|
944
984
|
} catch {
|
|
945
985
|
}
|
|
946
986
|
const git = await captureGit(cwd, previousHead);
|
|
947
987
|
const base = discoveryTransfer({ ...dashboardInventory(cwd, inventory), ...git ? { git } : {} });
|
|
948
988
|
let raw;
|
|
949
989
|
try {
|
|
950
|
-
raw = await
|
|
990
|
+
raw = await readFile7(join7(cwd, ".agenttwin/agent-discovery.json"), "utf8");
|
|
951
991
|
} catch (error) {
|
|
952
992
|
if (error.code === "ENOENT") return projectProducts(cwd, base, base);
|
|
953
993
|
throw error;
|
|
@@ -977,8 +1017,8 @@ var init_session = __esm({
|
|
|
977
1017
|
init_dashboard_transfer();
|
|
978
1018
|
import { createServer as createServer2 } from "http";
|
|
979
1019
|
import { createHash as createHash2, randomBytes as randomBytes3, randomUUID as randomUUID2, timingSafeEqual } from "crypto";
|
|
980
|
-
import { mkdir as
|
|
981
|
-
import { resolve as resolve2, join as
|
|
1020
|
+
import { mkdir as mkdir4, readFile as readFile9, writeFile as writeFile5, rename, mkdtemp as mkdtemp2, rm as rm2 } from "fs/promises";
|
|
1021
|
+
import { resolve as resolve2, join as join9, dirname as dirname2, extname } from "path";
|
|
982
1022
|
import { tmpdir as tmpdir2 } from "os";
|
|
983
1023
|
|
|
984
1024
|
// src/runtime/transcription.ts
|
|
@@ -1111,9 +1151,9 @@ function portableLocalReceipt(value) {
|
|
|
1111
1151
|
}
|
|
1112
1152
|
|
|
1113
1153
|
// src/commands/run.ts
|
|
1114
|
-
import { mkdtemp, mkdir as
|
|
1154
|
+
import { mkdtemp, mkdir as mkdir3, readFile as readFile8, writeFile as writeFile4, realpath, lstat, rm, copyFile } from "fs/promises";
|
|
1115
1155
|
import { tmpdir } from "os";
|
|
1116
|
-
import { basename as basename2, dirname, isAbsolute, join as
|
|
1156
|
+
import { basename as basename2, dirname, isAbsolute, join as join8, relative as relative2, resolve, sep as sep2 } from "path";
|
|
1117
1157
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1118
1158
|
import { createHash, randomUUID } from "crypto";
|
|
1119
1159
|
|
|
@@ -1525,12 +1565,12 @@ async function stageApplication(root, files, destination) {
|
|
|
1525
1565
|
const source = await realpath(resolve(canonicalRoot, file));
|
|
1526
1566
|
const rel = relative2(canonicalRoot, source);
|
|
1527
1567
|
if (rel.startsWith(`..${sep2}`) || rel === ".." || isAbsolute(rel) || source !== resolve(canonicalRoot, file) || !(await lstat(source)).isFile()) throw new CliError(`Files must be regular files within the project, without symlinks: ${file}`, { code: EXIT.USAGE });
|
|
1528
|
-
const contents = await
|
|
1568
|
+
const contents = await readFile8(source);
|
|
1529
1569
|
bytes += contents.length;
|
|
1530
1570
|
if (bytes > 20 * 1024 * 1024) throw new CliError("Application files exceed 20 MiB; supply a smaller built bundle", { code: EXIT.USAGE });
|
|
1531
|
-
const target =
|
|
1532
|
-
await
|
|
1533
|
-
await
|
|
1571
|
+
const target = join8(destination, file);
|
|
1572
|
+
await mkdir3(dirname(target), { recursive: true });
|
|
1573
|
+
await writeFile4(target, contents, { mode: 420 });
|
|
1534
1574
|
digests[file] = createHash("sha256").update(contents).digest("hex");
|
|
1535
1575
|
}
|
|
1536
1576
|
return digests;
|
|
@@ -1539,14 +1579,14 @@ async function runWorld(cwd, opts) {
|
|
|
1539
1579
|
const file = resolve(cwd, opts.file ?? "agenttwin.world.json");
|
|
1540
1580
|
let spec;
|
|
1541
1581
|
try {
|
|
1542
|
-
spec = parseWorld(JSON.parse(await
|
|
1582
|
+
spec = parseWorld(JSON.parse(await readFile8(file, "utf8")));
|
|
1543
1583
|
} catch (error) {
|
|
1544
1584
|
throw new CliError(String(error), { code: EXIT.USAGE, action: "Create agenttwin.world.json; run agenttwin example --out ./agenttwin-example for a working example." });
|
|
1545
1585
|
}
|
|
1546
1586
|
if (spec.workflowId) {
|
|
1547
1587
|
let inventory;
|
|
1548
1588
|
try {
|
|
1549
|
-
inventory = JSON.parse(await
|
|
1589
|
+
inventory = JSON.parse(await readFile8(join8(cwd, ".agenttwin/inventory.json"), "utf8"));
|
|
1550
1590
|
} catch {
|
|
1551
1591
|
throw new CliError("Linked workflow needs discovery inventory; run init first", { code: EXIT.BLOCKED });
|
|
1552
1592
|
}
|
|
@@ -1559,26 +1599,26 @@ async function runWorld(cwd, opts) {
|
|
|
1559
1599
|
const runtime = opts.runtime ?? "local";
|
|
1560
1600
|
if (!["local", "e2b", "hosted"].includes(runtime)) throw new CliError("runtime must be local, hosted or e2b", { code: EXIT.USAGE });
|
|
1561
1601
|
if (runtime === "e2b" && !process.env.E2B_API_KEY) throw new CliError("E2B_API_KEY is not configured", { code: EXIT.BLOCKED, action: "Configure your E2B key in the shell; it will not be uploaded." });
|
|
1562
|
-
const stage = await mkdtemp(
|
|
1602
|
+
const stage = await mkdtemp(join8(tmpdir(), "agenttwin-world-"));
|
|
1563
1603
|
try {
|
|
1564
|
-
const sourceDigests = await stageApplication(dirname(file), spec.files,
|
|
1565
|
-
await
|
|
1604
|
+
const sourceDigests = await stageApplication(dirname(file), spec.files, join8(stage, "app"));
|
|
1605
|
+
await writeFile4(join8(stage, "world.json"), JSON.stringify(spec), { mode: 420 });
|
|
1566
1606
|
const git = await captureGit(cwd);
|
|
1567
1607
|
let project;
|
|
1568
1608
|
try {
|
|
1569
|
-
project = JSON.parse(await
|
|
1609
|
+
project = JSON.parse(await readFile8(join8(cwd, ".agenttwin/inventory.json"), "utf8")).name ?? basename2(cwd);
|
|
1570
1610
|
} catch {
|
|
1571
1611
|
}
|
|
1572
1612
|
if (runtime === "local") {
|
|
1573
1613
|
opts.log("Testing through the CLI with in-process service twins. No cloud VM or containers.");
|
|
1574
|
-
const report2 = await executeLocal(
|
|
1614
|
+
const report2 = await executeLocal(join8(stage, "app"), spec, opts.signal);
|
|
1575
1615
|
const runId2 = randomUUID();
|
|
1576
1616
|
const receipt2 = { ...report2, runId: runId2, runtime, project, git, createdAt: (/* @__PURE__ */ new Date()).toISOString(), sourceDigests, specDigest: createHash("sha256").update(JSON.stringify(spec)).digest("hex"), provenance: "CLI-reported execution" };
|
|
1577
1617
|
const out2 = resolve(cwd, opts.out ?? `.agenttwin/runs/${runId2}`);
|
|
1578
|
-
await
|
|
1579
|
-
const evidencePath =
|
|
1580
|
-
await
|
|
1581
|
-
await
|
|
1618
|
+
await mkdir3(out2, { recursive: true });
|
|
1619
|
+
const evidencePath = join8(out2, "run.json"), reportPath = join8(out2, "report.md");
|
|
1620
|
+
await writeFile4(evidencePath, JSON.stringify(receipt2, null, 2) + "\n", { mode: 384 });
|
|
1621
|
+
await writeFile4(reportPath, `# ${spec.name}: ${receipt2.status}
|
|
1582
1622
|
|
|
1583
1623
|
Runner: CLI
|
|
1584
1624
|
Git: ${git?.head ?? "unavailable"}
|
|
@@ -1586,8 +1626,8 @@ Git: ${git?.head ?? "unavailable"}
|
|
|
1586
1626
|
` + receipt2.assertions.map((a) => `- ${a.passed ? "PASS" : "FAIL"} ${a.service}${a.path}: expected ${a.count}, observed ${a.actual}`).join("\n") + "\n\nSee run.json for activities, fault and initial/final state.\n");
|
|
1587
1627
|
return { ...receipt2, reportPath, evidencePath };
|
|
1588
1628
|
}
|
|
1589
|
-
const worker =
|
|
1590
|
-
await copyFile(worker,
|
|
1629
|
+
const worker = join8(dirname(fileURLToPath2(import.meta.url)), "worker.js");
|
|
1630
|
+
await copyFile(worker, join8(stage, "worker.js"));
|
|
1591
1631
|
if (runtime === "hosted") {
|
|
1592
1632
|
const receipt2 = await executeRemote(stage, spec, opts.log, { git, project });
|
|
1593
1633
|
if (receipt2.schema !== "agenttwin/run-v1" || typeof receipt2.runId !== "string" || !/^[a-f0-9-]{36}$/.test(receipt2.runId) || !["passed", "failed", "error", "incomplete"].includes(String(receipt2.status))) throw new CliError("Invalid hosted receipt");
|
|
@@ -1595,11 +1635,11 @@ Git: ${git?.head ?? "unavailable"}
|
|
|
1595
1635
|
const reportedSources = receipt2.sourceDigests;
|
|
1596
1636
|
if (receipt2.specDigest !== expectedSpec || !reportedSources || Object.keys(reportedSources).length !== Object.keys(sourceDigests).length || Object.entries(sourceDigests).some(([path, digest]) => reportedSources[path] !== digest)) throw new CliError("Hosted receipt does not match the submitted source/specification");
|
|
1597
1637
|
const out2 = resolve(cwd, opts.out ?? `.agenttwin/runs/${receipt2.runId}`);
|
|
1598
|
-
await
|
|
1599
|
-
const evidencePath =
|
|
1600
|
-
const reportPath =
|
|
1601
|
-
await
|
|
1602
|
-
await
|
|
1638
|
+
await mkdir3(out2, { recursive: true });
|
|
1639
|
+
const evidencePath = join8(out2, "run.json");
|
|
1640
|
+
const reportPath = join8(out2, "report.md");
|
|
1641
|
+
await writeFile4(evidencePath, JSON.stringify(receipt2, null, 2) + "\n", { mode: 384 });
|
|
1642
|
+
await writeFile4(reportPath, `# ${spec.name}: ${receipt2.status}
|
|
1603
1643
|
|
|
1604
1644
|
Run: ${receipt2.runId}
|
|
1605
1645
|
|
|
@@ -1633,12 +1673,12 @@ See run.json for assertions, service state, application output and isolation met
|
|
|
1633
1673
|
isolation
|
|
1634
1674
|
};
|
|
1635
1675
|
const out = resolve(cwd, opts.out ?? `.agenttwin/runs/${runId}`);
|
|
1636
|
-
await
|
|
1637
|
-
await
|
|
1638
|
-
await
|
|
1676
|
+
await mkdir3(out, { recursive: true });
|
|
1677
|
+
await writeFile4(join8(out, "run.json"), JSON.stringify(receipt, null, 2) + "\n", { mode: 384 });
|
|
1678
|
+
await writeFile4(join8(out, "world.json"), JSON.stringify(spec, null, 2) + "\n", { mode: 384 });
|
|
1639
1679
|
const summary = [`# ${spec.name}: ${report.status}`, "", `Run: ${runId}`, `Image: ${image}`, "", "## Assertions", "", ...report.assertions.map((a) => `- ${a.passed ? "PASS" : "FAIL"} ${a.service}${a.path}: expected ${a.count}, observed ${a.actual}`), "", "## Infrastructure assertions", "", ...(report.infrastructureAssertions ?? []).map((a) => `- ${a.passed ? "PASS" : "FAIL"} ${a.service}: ${JSON.stringify(a.actual)}`), "", "## Coverage", "", report.coverage, "", "External networking disabled. Only explicitly listed source files were uploaded. New state and tokens are created for each run.", "", "See run.json for application output, faults, events, and initial/final state.", ""].join("\n");
|
|
1640
|
-
await
|
|
1641
|
-
return { ...receipt, reportPath:
|
|
1680
|
+
await writeFile4(join8(out, "report.md"), summary);
|
|
1681
|
+
return { ...receipt, reportPath: join8(out, "report.md"), evidencePath: join8(out, "run.json") };
|
|
1642
1682
|
} finally {
|
|
1643
1683
|
await rm(stage, { recursive: true, force: true });
|
|
1644
1684
|
}
|
|
@@ -1680,11 +1720,11 @@ async function startGateway(opts) {
|
|
|
1680
1720
|
const chatControllers = /* @__PURE__ */ new Map();
|
|
1681
1721
|
const voiceControllers = /* @__PURE__ */ new Map();
|
|
1682
1722
|
const directory = resolve2(opts.directory);
|
|
1683
|
-
await
|
|
1684
|
-
const
|
|
1723
|
+
await mkdir4(directory, { recursive: true, mode: 448 });
|
|
1724
|
+
const storePath2 = join9(directory, "state.json");
|
|
1685
1725
|
let store;
|
|
1686
1726
|
try {
|
|
1687
|
-
store = JSON.parse(await
|
|
1727
|
+
store = JSON.parse(await readFile9(storePath2, "utf8"));
|
|
1688
1728
|
if (store.schema !== "agenttwin/gateway-v1") throw new Error("Invalid store");
|
|
1689
1729
|
} catch (error) {
|
|
1690
1730
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -1699,9 +1739,9 @@ async function startGateway(opts) {
|
|
|
1699
1739
|
const save = () => {
|
|
1700
1740
|
const snapshot = JSON.stringify(store);
|
|
1701
1741
|
writes = writes.then(async () => {
|
|
1702
|
-
const path =
|
|
1703
|
-
await
|
|
1704
|
-
await rename(path,
|
|
1742
|
+
const path = join9(directory, `state-${randomUUID2()}.tmp`);
|
|
1743
|
+
await writeFile5(path, snapshot, { mode: 384 });
|
|
1744
|
+
await rename(path, storePath2);
|
|
1705
1745
|
}).catch((error) => {
|
|
1706
1746
|
storageHealthy = false;
|
|
1707
1747
|
throw error;
|
|
@@ -1734,16 +1774,16 @@ async function startGateway(opts) {
|
|
|
1734
1774
|
if (!store.accounts.some((a) => a.id === item.job.accountId && a.active)) throw new Error("Account revoked before execution");
|
|
1735
1775
|
item.job.status = "running";
|
|
1736
1776
|
await save();
|
|
1737
|
-
workspace = await mkdtemp2(
|
|
1777
|
+
workspace = await mkdtemp2(join9(tmpdir2(), "agenttwin-api-run-"));
|
|
1738
1778
|
for (const [file, encoded] of Object.entries(item.files)) {
|
|
1739
|
-
const destination =
|
|
1740
|
-
await
|
|
1741
|
-
await
|
|
1779
|
+
const destination = join9(workspace, uploadPath(file));
|
|
1780
|
+
await mkdir4(dirname2(destination), { recursive: true });
|
|
1781
|
+
await writeFile5(destination, Buffer.from(encoded, "base64"), { mode: 384 });
|
|
1742
1782
|
}
|
|
1743
|
-
await
|
|
1783
|
+
await writeFile5(join9(workspace, "agenttwin.world.json"), JSON.stringify(item.spec));
|
|
1744
1784
|
if (item.spec.workflowId) {
|
|
1745
|
-
await
|
|
1746
|
-
await
|
|
1785
|
+
await mkdir4(join9(workspace, ".agenttwin"));
|
|
1786
|
+
await writeFile5(join9(workspace, ".agenttwin/inventory.json"), JSON.stringify({ workflows: [{ id: item.spec.workflowId }] }));
|
|
1747
1787
|
}
|
|
1748
1788
|
const receipt = await runWorld(workspace, { runtime: "e2b", signal: controllers.get(item.job.id)?.signal, log: () => {
|
|
1749
1789
|
} });
|
|
@@ -1788,7 +1828,7 @@ async function startGateway(opts) {
|
|
|
1788
1828
|
return;
|
|
1789
1829
|
}
|
|
1790
1830
|
try {
|
|
1791
|
-
const bytes = await
|
|
1831
|
+
const bytes = await readFile9(file);
|
|
1792
1832
|
const types = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".json": "application/json", ".svg": "image/svg+xml", ".png": "image/png", ".jpg": "image/jpeg", ".ico": "image/x-icon", ".woff2": "font/woff2" };
|
|
1793
1833
|
res.writeHead(200, { "content-type": types[extname(file)] ?? "application/octet-stream", "cache-control": "no-store", "x-content-type-options": "nosniff", "referrer-policy": "no-referrer" });
|
|
1794
1834
|
res.end(bytes);
|
|
@@ -1930,7 +1970,7 @@ async function startGateway(opts) {
|
|
|
1930
1970
|
return;
|
|
1931
1971
|
}
|
|
1932
1972
|
if (path === "/v1/runs" && req.method === "GET") {
|
|
1933
|
-
send(res, 200, { jobs: store.jobs.filter((j) => j.accountId === account.id).sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt)).slice(0, 100).map(({ id, status, createdAt
|
|
1973
|
+
send(res, 200, { jobs: store.jobs.filter((j) => j.accountId === account.id).sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt)).slice(0, 100).map((job) => ({ id: job.id, status: job.status, createdAt: job.createdAt, name: job.name ?? (typeof job.receipt?.name === "string" ? job.receipt.name : void 0), workflowId: job.workflowId ?? (typeof job.receipt?.workflowId === "string" ? job.receipt.workflowId : void 0), project: job.project ?? (typeof job.receipt?.project === "string" ? job.receipt.project : void 0) })) });
|
|
1934
1974
|
return;
|
|
1935
1975
|
}
|
|
1936
1976
|
if (path === "/v1/runs" && req.method === "POST") {
|
|
@@ -1991,7 +2031,7 @@ async function startGateway(opts) {
|
|
|
1991
2031
|
return;
|
|
1992
2032
|
}
|
|
1993
2033
|
store.jobs = store.jobs.filter((j) => Date.parse(j.createdAt) > Date.now() - 30 * 864e5);
|
|
1994
|
-
const job = { id: randomUUID2(), accountId: account.id, status: "queued", createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2034
|
+
const job = { id: randomUUID2(), accountId: account.id, status: "queued", createdAt: (/* @__PURE__ */ new Date()).toISOString(), name: spec.name, workflowId: spec.workflowId, project };
|
|
1995
2035
|
account.runs++;
|
|
1996
2036
|
store.jobs.push(job);
|
|
1997
2037
|
await save();
|