agenttwin 0.2.0 → 0.2.1
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 +13 -1
- package/dist/api.js +53 -0
- package/dist/cli.js +389 -179
- package/dist/dashboard/assets/dashboard/dashboard.css +5 -0
- package/dist/dashboard/assets/dashboard/dashboard.js +21 -4
- package/dist/dashboard/assets/dashboard/hosted.js +7 -2
- package/dist/dashboard/index.html +3 -4
- package/dist/gateway.js +53 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,6 @@ AgentTwin 0.2.0 executes application code in E2B. No local application container
|
|
|
9
9
|
Node.js 22+ is required. Run discovery from your project; npx installs the CLI automatically:
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
npm install --no-save --ignore-scripts /path/to/agenttwin-0.2.0.tgz
|
|
13
12
|
npx agenttwin init
|
|
14
13
|
npx agenttwin example --out ./agenttwin-example
|
|
15
14
|
```
|
|
@@ -104,3 +103,16 @@ npx agenttwin run --file .agenttwin/prepared/notification/agenttwin.world.json
|
|
|
104
103
|
The output contains your unchanged source, a world spec, a SHA-256 manifest, and `agenttwin.bundle.json`. In Hosted runs, connect with an individual API token, choose that bundle, review the upload and execute. Jobs can be refreshed, cancelled and inspected. Tokens stay in page memory. Discovery imports remain in the browser; executing uploads the explicit bundle to the API.
|
|
105
104
|
|
|
106
105
|
Supported mapping contracts: bounded Slack operations, PostgreSQL, Redis and custom JSON collections (`GET /path` / `POST /path`). Preparation rejects missing/unreviewed mappings and unsupported vendor SDK operations. Discovery and mappings remain user-reviewed declarations, not proof that every operation or dependency in arbitrary source was discovered. Hosted execution checks the declared assertions.
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
### Coding session and dashboard discovery
|
|
109
|
+
|
|
110
|
+
Run `npx agenttwin init` inside your repository (Node.js 22+). Interactive init offers an OpenCode session when the CLI is installed; `npx agenttwin init --agent opencode` launches explicitly with your configured provider and its normal permission controls. Provider authentication and usage belong to your coding-agent account. Other coding agents can use `.agenttwin/AGENT-TASK.md`.
|
|
111
|
+
|
|
112
|
+
The session traces workflows across files, records intents and production failure hypotheses in `.agenttwin/WORKFLOWS.md`, and writes metadata to `.agenttwin/agent-discovery.json`. Static candidates retain their original evidence. Agent candidates remain unverified until the workflow is explicitly prepared and executed. Static discovery preserves distinct exported side-effect entrypoints, including repositories with 100 candidates; it does not prove that every business workflow has been found.
|
|
113
|
+
|
|
114
|
+
After reviewing discovery, configure `AGENTTWIN_TOKEN` using your individual account token (never paste it into an agent prompt) and run `npx agenttwin sync`. Optional `AGENTTWIN_API_URL` selects your API. The website loads account discovery after you connect in Settings, then refreshes while visible. Sync sends names, operations and relative entrypoint metadata; application source uploads happen separately through reviewed bundles. Avoid sensitive names or identifiers in discovery metadata.
|
|
115
|
+
|
|
116
|
+
To execute a workflow, supply an executable Node entrypoint, reviewed supported dependency mappings, synthetic fixtures and expected observable outcomes. Run `npx agenttwin prepare --file preparation.json`, review the bundle, and run it through your AgentTwin API or E2B account. Browser/desktop automation and arbitrary vendor SDKs require additional runtimes/adapters. Every application execution is hosted; no local Docker is used.
|
|
117
|
+
|
|
118
|
+
A run receipt's **Fix & retest** tab exports an evidence-based task for your coding agent. Edit the code in the original project, prepare a new bundle, execute in a fresh sandbox, and compare receipts. This is an interactive coding loop, not an unattended universal repair service.
|
package/dist/api.js
CHANGED
|
@@ -7,6 +7,43 @@ import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2, rename
|
|
|
7
7
|
import { resolve as resolve2, join as join4, dirname as dirname2, extname } from "path";
|
|
8
8
|
import { tmpdir as tmpdir2 } from "os";
|
|
9
9
|
|
|
10
|
+
// src/inventory/dashboard-transfer.ts
|
|
11
|
+
function discoveryTransfer(value) {
|
|
12
|
+
const input = value;
|
|
13
|
+
if (!input || input.schema !== "company-inventory/v1" || typeof input.name !== "string" || !Array.isArray(input.tools) || !Array.isArray(input.workflows) || input.tools.length > 1e3 || input.workflows.length > 5e3) throw new Error("Invalid discovery metadata");
|
|
14
|
+
const text = (v, limit = 240) => {
|
|
15
|
+
if (typeof v !== "string" || v.length > limit || /[\x00-\x1f]/.test(v)) throw new Error("Invalid discovery string");
|
|
16
|
+
return v;
|
|
17
|
+
};
|
|
18
|
+
const strings = (v) => {
|
|
19
|
+
if (!Array.isArray(v) || v.length > 1e3) throw new Error("Invalid discovery list");
|
|
20
|
+
return v.map((s) => text(s));
|
|
21
|
+
};
|
|
22
|
+
const entrypoint = (v) => {
|
|
23
|
+
const path = text(v, 500);
|
|
24
|
+
if (path.startsWith("/") || path.includes("\\") || /(^|[\/])\.\.([\/#]|$)/.test(path) || /^[A-Za-z][A-Za-z0-9+.-]*:/.test(path)) throw new Error("Entrypoint must be relative to the project");
|
|
25
|
+
return path;
|
|
26
|
+
};
|
|
27
|
+
const tools = input.tools.map((raw) => {
|
|
28
|
+
const t = raw;
|
|
29
|
+
return { id: text(t.id), title: text(t.title), kind: text(t.kind), category: text(t.category), confidence: text(t.confidence), interface: text(t.interface) };
|
|
30
|
+
});
|
|
31
|
+
const workflows = input.workflows.map((raw) => {
|
|
32
|
+
const w = raw;
|
|
33
|
+
if (!Array.isArray(w.steps) || w.steps.length > 1e3) throw new Error("Invalid workflow steps");
|
|
34
|
+
return { id: text(w.id), title: text(w.title), confidence: text(w.confidence), supported: w.supported === true, verified: false, tools: strings(w.tools), steps: w.steps.map((rawStep) => {
|
|
35
|
+
const step = rawStep;
|
|
36
|
+
return { tool: text(step.tool), operations: strings(step.operations) };
|
|
37
|
+
}), ...w.entrypoint ? { entrypoint: entrypoint(w.entrypoint) } : {} };
|
|
38
|
+
});
|
|
39
|
+
if (new Set(tools.map((t) => t.id)).size !== tools.length || new Set(workflows.map((w) => w.id)).size !== workflows.length) throw new Error("Duplicate discovery IDs");
|
|
40
|
+
const ids = new Set(tools.map((t) => t.id));
|
|
41
|
+
if (workflows.some((w) => w.tools.some((id) => !ids.has(id)) || w.steps.some((s) => !ids.has(s.tool)))) throw new Error("Unknown workflow tool");
|
|
42
|
+
const result = { schema: "company-inventory/v1", name: text(input.name), generatedAt: input.generatedAt ? text(input.generatedAt) : "", tools, workflows };
|
|
43
|
+
if (Buffer.byteLength(JSON.stringify(result)) > 2 * 1024 * 1024) throw new Error("Discovery exceeds 2 MiB");
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
10
47
|
// src/runtime/spec.ts
|
|
11
48
|
var object = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
12
49
|
var namePattern = /^[a-z][a-z0-9_]{0,39}$/;
|
|
@@ -535,6 +572,22 @@ async function startGateway(opts) {
|
|
|
535
572
|
send(res, 401, { error: "unauthorized" });
|
|
536
573
|
return;
|
|
537
574
|
}
|
|
575
|
+
if (path === "/v1/discovery" && req.method === "GET") {
|
|
576
|
+
send(res, 200, { inventory: store.discoveries?.[account.id] ?? null });
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (path === "/v1/discovery" && req.method === "POST") {
|
|
580
|
+
if (stopping) {
|
|
581
|
+
send(res, 503, { error: "stopping" });
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
const inventory = discoveryTransfer(await body(req));
|
|
585
|
+
store.discoveries ??= {};
|
|
586
|
+
store.discoveries[account.id] = inventory;
|
|
587
|
+
await save();
|
|
588
|
+
send(res, 200, { synced: true, tools: inventory.tools.length, workflows: inventory.workflows.length });
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
538
591
|
if (path === "/v1/runs" && req.method === "GET") {
|
|
539
592
|
send(res, 200, { jobs: store.jobs.filter((j) => j.accountId === account.id).slice(-100).reverse().map(({ id, status, createdAt }) => ({ id, status, createdAt })) });
|
|
540
593
|
return;
|