@anteros/core 0.0.41 → 0.0.44
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/database/audit.ts +9 -59
- package/database/collection.ts +3 -1
- package/database/deleteLog.ts +67 -3
- package/database/file.ts +4 -3
- package/database/mongodbadapter.ts +103 -42
- package/database/replication.ts +724 -58
- package/database/ttl.ts +68 -0
- package/database/workflow.ts +466 -240
- package/index.ts +6 -0
- package/lib/agent.ts +1567 -0
- package/lib/agentMemory.ts +689 -0
- package/lib/agents.ts +180 -0
- package/lib/ai.ts +0 -0
- package/lib/api.ts +246 -0
- package/lib/attachment.ts +351 -0
- package/lib/auditResult.ts +13 -0
- package/lib/define.ts +43 -1
- package/lib/endpoints.ts +80 -0
- package/lib/files.ts +2 -1
- package/lib/jsonSchema.ts +183 -0
- package/lib/lifecycle.ts +7 -1
- package/lib/load.ts +17 -0
- package/lib/mcp.ts +3 -2
- package/lib/mcpClient.ts +128 -0
- package/lib/messages.ts +299 -0
- package/lib/providers.ts +970 -0
- package/lib/routes.ts +14 -1
- package/lib/scriptWorker.ts +4 -1
- package/lib/scripts.ts +6 -1
- package/lib/skills.ts +481 -0
- package/lib/sockets.ts +3 -1
- package/lib/workflow.ts +209 -37
- package/lib/zip.ts +109 -0
- package/package.json +1 -1
- package/server/access.ts +96 -0
- package/server/agents.ts +472 -0
- package/server/api.ts +80 -65
- package/server/boot.ts +11 -0
- package/server/hono.ts +8 -3
- package/server/mcp.ts +17 -63
- package/server/routes.ts +19 -24
- package/tests/agent-mcp.test.ts +116 -0
- package/tests/agent-memory.test.ts +617 -0
- package/tests/agent-provider.test.ts +288 -0
- package/tests/agent-runtime.test.ts +369 -0
- package/tests/agent-skills.test.ts +177 -0
- package/tests/agent-summarize.test.ts +124 -0
- package/tests/agent-working-memory.test.ts +404 -0
- package/tests/agent.test.ts +1241 -0
- package/tests/agents-api.test.ts +593 -0
- package/tests/api-private-fields.test.ts +196 -0
- package/tests/api.test.ts +51 -0
- package/tests/attachment.test.ts +241 -0
- package/tests/audit-operations.test.ts +1 -1
- package/tests/audit-redact.test.ts +29 -3
- package/tests/audit-run-action.test.ts +1 -1
- package/tests/endpoints.test.ts +80 -0
- package/tests/file-collection-replication.test.ts +2 -0
- package/tests/fixtures/agent-tenant/agents/support.agent.ts +9 -0
- package/tests/fixtures/agent-tenant/agents/weather.agent.ts +21 -0
- package/tests/fixtures/agents-api/agents/assistant.agent.ts +46 -0
- package/tests/fixtures/agents-api/agents/plain.agent.ts +23 -0
- package/tests/fixtures/agents-api/agents/private.agent.ts +15 -0
- package/tests/fixtures/agents-api/collections/notes.model.ts +26 -0
- package/tests/fixtures/agents-api/routes/probe.route.ts +21 -0
- package/tests/fixtures/agents-api/services/probe.service.ts +19 -0
- package/tests/fixtures/attachment/data.csv +2 -0
- package/tests/fixtures/fake-provider.ts +128 -0
- package/tests/fixtures/repl-only/app.ts +3 -0
- package/tests/fixtures/replication-meta/agents/chat.agent.ts +16 -0
- package/tests/fixtures/replication-meta/collections/counters.model.ts +13 -0
- package/tests/fixtures/replication-meta/collections/orders.model.ts +9 -0
- package/tests/fixtures/replication-meta/workflows/run.workflow.ts +8 -0
- package/tests/fixtures/skills-tenant/agents/docs.agent.ts +23 -0
- package/tests/fixtures/skills-tenant/agents/incomplete.agent.ts +14 -0
- package/tests/fixtures/skills-tenant/broken/bad-name.md +6 -0
- package/tests/fixtures/skills-tenant/broken/dup-a.md +6 -0
- package/tests/fixtures/skills-tenant/broken/dup-b.md +6 -0
- package/tests/fixtures/skills-tenant/broken/empty-body.md +4 -0
- package/tests/fixtures/skills-tenant/broken/long-compat.md +7 -0
- package/tests/fixtures/skills-tenant/broken/long-description.md +6 -0
- package/tests/fixtures/skills-tenant/broken/mismatch/SKILL.md +6 -0
- package/tests/fixtures/skills-tenant/broken/no-description.md +5 -0
- package/tests/fixtures/skills-tenant/broken/no-name.md +5 -0
- package/tests/fixtures/skills-tenant/broken/plain.md +4 -0
- package/tests/fixtures/skills-tenant/broken/unknown-key.md +9 -0
- package/tests/fixtures/skills-tenant/docs/pdf-forms.md +18 -0
- package/tests/fixtures/skills-tenant/guide/ml.md +9 -0
- package/tests/fixtures/skills-tenant/guides/pdf/SKILL.md +9 -0
- package/tests/fixtures/skills-tenant/runbooks/incident.md +9 -0
- package/tests/fixtures/skills-tenant/runbooks/notes.txt +2 -0
- package/tests/fixtures/vars-tenant/vars/private.var.ts +4 -1
- package/tests/fixtures/workflow-engine/workflows/failing.workflow.ts +34 -0
- package/tests/fixtures/workflow-engine/workflows/flaky.workflow.ts +23 -0
- package/tests/fixtures/workflow-engine/workflows/hang.workflow.ts +17 -0
- package/tests/fixtures/workflow-engine/workflows/simple.workflow.ts +19 -0
- package/tests/fixtures/workflow-engine/workflows/slow.workflow.ts +23 -0
- package/tests/fixtures/workflow-engine/workflows/versioned.workflow.ts +19 -0
- package/tests/fixtures/workflow-tenant/workflows/broken.workflow.ts +3 -0
- package/tests/fixtures/workflow-tenant/workflows/disabled.workflow.ts +8 -0
- package/tests/fixtures/workflow-tenant/workflows/invoice.workflow.ts +21 -0
- package/tests/fixtures/workflow-tenant/workflows/no-id.workflow.ts +7 -0
- package/tests/fixtures/workflow-tenant/workflows/order-duplicate.workflow.ts +13 -0
- package/tests/fixtures/workflow-tenant/workflows/order.workflow.ts +17 -0
- package/tests/hono.test.ts +6 -3
- package/tests/locks.test.ts +120 -0
- package/tests/mcp.test.ts +11 -11
- package/tests/messages.test.ts +258 -0
- package/tests/replication-change-stream.test.ts +240 -0
- package/tests/replication-control.test.ts +4 -2
- package/tests/replication-initial-sync.test.ts +2 -0
- package/tests/replication-meta.test.ts +246 -0
- package/tests/replication.test.ts +2 -0
- package/tests/upload.test.ts +44 -12
- package/tests/vars-api.test.ts +1 -1
- package/tests/vars-replication.test.ts +5 -2
- package/tests/workflow-engine.test.ts +340 -0
- package/tests/workflow-retention.test.ts +103 -0
- package/tests/workflow.test.ts +186 -0
- package/types/agent.d.ts +1067 -0
- package/types/config.d.ts +16 -0
- package/types/file.d.ts +7 -7
- package/types/hook.d.ts +6 -0
- package/types/lifecycle.d.ts +6 -0
- package/types/mcp.d.ts +11 -1
- package/types/replication.d.ts +70 -4
- package/types/route.d.ts +7 -1
- package/types/scripts.d.ts +6 -0
- package/types/service.d.ts +7 -1
- package/types/tenant.d.ts +13 -0
- package/types/vars.d.ts +2 -2
- package/types/websocket.d.ts +6 -0
- package/types/workflow.d.ts +50 -3
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { define, v } from "../../../../index";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Loader fixture — `{tenant.dir}/agents/**\/*.agent.ts`.
|
|
5
|
+
* No call is ever made against the provider here: only the loader is exercised.
|
|
6
|
+
*/
|
|
7
|
+
export default define.Agent({
|
|
8
|
+
id: "weather",
|
|
9
|
+
name: "Weather Agent",
|
|
10
|
+
description: "Answers weather questions with a tool call",
|
|
11
|
+
instructions: "You are a concise weather assistant.",
|
|
12
|
+
provider: { model: "test-model", protocol: "openai", apiKey: "fixture-key" },
|
|
13
|
+
tools: {
|
|
14
|
+
forecast: define.Tool({
|
|
15
|
+
id: "forecast",
|
|
16
|
+
description: "Current weather for a city",
|
|
17
|
+
inputSchema: v.object({ city: v.string().required() }),
|
|
18
|
+
execute: async ({ city }) => ({ city, celsius: 21 }),
|
|
19
|
+
}),
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { define, v, agents } from "../../../../index";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HTTP-API fixture — exposed through `POST /api/:tenant_id/agents/:agent/:action`.
|
|
5
|
+
*
|
|
6
|
+
* The provider endpoint comes from the environment so the test can point it at
|
|
7
|
+
* its own fake provider (the port is chosen by the OS).
|
|
8
|
+
*/
|
|
9
|
+
export default define.Agent({
|
|
10
|
+
id: "assistant",
|
|
11
|
+
name: "Assistant",
|
|
12
|
+
description: "Fixture agent for the HTTP API",
|
|
13
|
+
instructions: "You are a fixture agent.",
|
|
14
|
+
provider: {
|
|
15
|
+
model: "test-model",
|
|
16
|
+
protocol: "openai",
|
|
17
|
+
apiKey: "test-key",
|
|
18
|
+
baseUrl: Bun.env.AGENT_API_TEST_URL ?? "http://127.0.0.1:1",
|
|
19
|
+
options: { retries: 0 },
|
|
20
|
+
},
|
|
21
|
+
maxSteps: 3,
|
|
22
|
+
memory: new agents.memory.InMemory(),
|
|
23
|
+
tools: {
|
|
24
|
+
echo: define.Tool({
|
|
25
|
+
id: "echo",
|
|
26
|
+
description: "Echoes the given text",
|
|
27
|
+
inputSchema: v.object({ text: v.string().required() }),
|
|
28
|
+
execute: async ({ text }) => ({ echoed: text }),
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
api: {
|
|
32
|
+
access: {
|
|
33
|
+
// public metadata — no token needed
|
|
34
|
+
info: true,
|
|
35
|
+
generate: (ctx) => !!ctx.token.value,
|
|
36
|
+
stream: (ctx) => !!ctx.token.value,
|
|
37
|
+
object: (ctx) => !!ctx.token.value,
|
|
38
|
+
history: (ctx) => !!ctx.token.value,
|
|
39
|
+
threads: (ctx) => !!ctx.token.value,
|
|
40
|
+
clear: (ctx) => !!ctx.token.value,
|
|
41
|
+
},
|
|
42
|
+
object: {
|
|
43
|
+
schema: v.object({ city: v.string().required(), celsius: v.number().required() }),
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { define } from "../../../../index";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Public agent — `generate` is allowed without a token. Declares no memory and
|
|
5
|
+
* no `api.object`, to exercise `AGENT_NO_MEMORY` / `AGENT_NO_OBJECT_SCHEMA`.
|
|
6
|
+
*/
|
|
7
|
+
export default define.Agent({
|
|
8
|
+
id: "plain",
|
|
9
|
+
description: "A plain agent with no memory and no object schema.",
|
|
10
|
+
instructions: "You are a plain agent.",
|
|
11
|
+
provider: {
|
|
12
|
+
model: "test-model",
|
|
13
|
+
protocol: "openai",
|
|
14
|
+
apiKey: "test-key",
|
|
15
|
+
baseUrl: Bun.env.AGENT_API_TEST_URL ?? "http://127.0.0.1:1",
|
|
16
|
+
options: { retries: 0 },
|
|
17
|
+
},
|
|
18
|
+
api: {
|
|
19
|
+
// public on purpose: `object` also needs a rule so the *missing schema*
|
|
20
|
+
// error can be exercised (otherwise the action is denied first)
|
|
21
|
+
access: { generate: true, object: true },
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { define } from "../../../../index";
|
|
2
|
+
|
|
3
|
+
/** Declares no `api` at all — every action must be denied (secure by default). */
|
|
4
|
+
export default define.Agent({
|
|
5
|
+
id: "private",
|
|
6
|
+
description: "Not exposed over HTTP.",
|
|
7
|
+
instructions: "You are a private agent.",
|
|
8
|
+
provider: {
|
|
9
|
+
model: "test-model",
|
|
10
|
+
protocol: "openai",
|
|
11
|
+
apiKey: "test-key",
|
|
12
|
+
baseUrl: Bun.env.AGENT_API_TEST_URL ?? "http://127.0.0.1:1",
|
|
13
|
+
options: { retries: 0 },
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { define } from "../../../../index";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A collection whose hook uses `agents` — proving the LLM registry is injected
|
|
5
|
+
* into the hook context exactly like `rest` and `io`.
|
|
6
|
+
*
|
|
7
|
+
* The observation is published on `globalThis` (the test reads it back): a hook
|
|
8
|
+
* must not stuff arbitrary keys into `meta.data`, the collection schema rejects
|
|
9
|
+
* them (`allowUnknown: false`).
|
|
10
|
+
*/
|
|
11
|
+
export default define.Collection({
|
|
12
|
+
slug: "notes",
|
|
13
|
+
fields: [{ name: "title", type: "string" }],
|
|
14
|
+
hooks: {
|
|
15
|
+
beforeOperation: define.Hook(async ({ agents, api, rest, meta }) => {
|
|
16
|
+
if (meta.action !== "insertOne") return;
|
|
17
|
+
(globalThis as any).__agentsHookProbe = {
|
|
18
|
+
ids: agents.ids().sort(),
|
|
19
|
+
bound: agents.get("assistant")?.getRest() === rest,
|
|
20
|
+
// The in-process facade, bound to the same `rest`
|
|
21
|
+
slug: api.collection("notes").getSlug(),
|
|
22
|
+
notes: await api.collection("notes").countDocuments(),
|
|
23
|
+
};
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { define } from "../../../../index";
|
|
2
|
+
|
|
3
|
+
/** A route reading the LLM registry **and the in-process facade** from its context. */
|
|
4
|
+
export default define.Route({
|
|
5
|
+
method: "GET",
|
|
6
|
+
path: "/probe",
|
|
7
|
+
handler: async ({ agents, api, rest }) => Response.json({
|
|
8
|
+
agents: agents.ids().sort(),
|
|
9
|
+
hasAssistant: !!agents.get("assistant"),
|
|
10
|
+
// `api` groups the same calls as `rest` — bound and namespaced
|
|
11
|
+
slug: api.collection("notes").getSlug(),
|
|
12
|
+
notes: await api.collection("notes").countDocuments(),
|
|
13
|
+
sameAsRest: (await api.collection("notes").find()) .length === (await rest.find("notes")).length,
|
|
14
|
+
agentBound: api.agent("assistant")?.getRest() === rest,
|
|
15
|
+
unknownAgent: api.agent("ghost"),
|
|
16
|
+
vars: typeof api.vars.get,
|
|
17
|
+
serviceRun: typeof api.service("probe").run,
|
|
18
|
+
serviceCall: await api.service("probe").run("echo", { from: "route" }),
|
|
19
|
+
fileUrl: api.files.url("invoices", "f1.pdf", { width: 800 }),
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { define } from "../../../../index";
|
|
2
|
+
|
|
3
|
+
/** A service action reading the LLM registry and the facade from its context. */
|
|
4
|
+
export default define.Service({
|
|
5
|
+
name: "probe",
|
|
6
|
+
enabled: true,
|
|
7
|
+
actions: {
|
|
8
|
+
check: async ({ agents, api, rest }) => ({
|
|
9
|
+
agents: agents.ids().sort(),
|
|
10
|
+
hasAssistant: !!agents.get("assistant"),
|
|
11
|
+
bound: agents.get("assistant")?.getRest() === rest,
|
|
12
|
+
tenant: rest.tenant_id,
|
|
13
|
+
// `api.service(name).run` is the grouped `rest.runService`
|
|
14
|
+
nested: await api.service("probe").run("echo", { from: "api" }),
|
|
15
|
+
varType: typeof api.vars.get,
|
|
16
|
+
}),
|
|
17
|
+
echo: async ({ data }) => data,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fake model providers for the agent tests — one local HTTP endpoint per
|
|
3
|
+
* protocol, recording what the runtime sent.
|
|
4
|
+
*
|
|
5
|
+
* Shared by `agent.test.ts` (runtime) and `agents-api.test.ts` (HTTP surface),
|
|
6
|
+
* so a change in the wire format is fixed in one place.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type Recorded = { path: string; body: any; headers: Record<string, string> };
|
|
10
|
+
|
|
11
|
+
export type Fake = {
|
|
12
|
+
requests: Recorded[];
|
|
13
|
+
url: string;
|
|
14
|
+
hits: () => number;
|
|
15
|
+
stop: () => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** A local endpoint that records the requests and answers from `handler`. */
|
|
19
|
+
export function fakeProvider(handler: (body: any, req: Request, hits: number) => Response | Promise<Response>): Fake {
|
|
20
|
+
const requests: Recorded[] = [];
|
|
21
|
+
let hits = 0;
|
|
22
|
+
const server = Bun.serve({
|
|
23
|
+
port: 0,
|
|
24
|
+
hostname: "127.0.0.1",
|
|
25
|
+
fetch: async (req) => {
|
|
26
|
+
const body = await req.json().catch(() => null);
|
|
27
|
+
requests.push({
|
|
28
|
+
path: new URL(req.url).pathname,
|
|
29
|
+
body,
|
|
30
|
+
headers: Object.fromEntries(req.headers.entries()),
|
|
31
|
+
});
|
|
32
|
+
hits += 1;
|
|
33
|
+
return handler(body, req, hits);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
requests,
|
|
38
|
+
url: `http://127.0.0.1:${server.port}`,
|
|
39
|
+
hits: () => hits,
|
|
40
|
+
stop: () => server.stop(true),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Typed element access (`noUncheckedIndexedAccess` is on). */
|
|
45
|
+
export function at<T>(list: T[], index = 0): T {
|
|
46
|
+
const item = list[index];
|
|
47
|
+
if (item === undefined) throw new Error(`No element #${index} (length ${list.length})`);
|
|
48
|
+
return item;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function request(provider: Fake, index = 0): Recorded {
|
|
52
|
+
const recorded = provider.requests[index];
|
|
53
|
+
if (!recorded) throw new Error(`No request #${index} was recorded (got ${provider.requests.length})`);
|
|
54
|
+
return recorded;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const USAGE = { prompt_tokens: 11, completion_tokens: 4, total_tokens: 15 };
|
|
58
|
+
export const A_USAGE = { input_tokens: 13, output_tokens: 6 };
|
|
59
|
+
|
|
60
|
+
/** An SSE response from a list of already-serialized events. */
|
|
61
|
+
export function sse(events: string[]): Response {
|
|
62
|
+
return new Response(events.map((event) => `data: ${event}\n\n`).join(""), {
|
|
63
|
+
headers: { "Content-Type": "text/event-stream" },
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── OpenAI-compatible ───────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
export function openaiText(text: string, finishReason = "stop"): Response {
|
|
70
|
+
return Response.json({
|
|
71
|
+
choices: [{ message: { role: "assistant", content: text }, finish_reason: finishReason }],
|
|
72
|
+
usage: USAGE,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function openaiTool(name: string, args: any, id = "call_1"): Response {
|
|
77
|
+
return Response.json({
|
|
78
|
+
choices: [{
|
|
79
|
+
message: {
|
|
80
|
+
role: "assistant",
|
|
81
|
+
content: null,
|
|
82
|
+
tool_calls: [{ id, type: "function", function: { name, arguments: JSON.stringify(args) } }],
|
|
83
|
+
},
|
|
84
|
+
finish_reason: "tool_calls",
|
|
85
|
+
}],
|
|
86
|
+
usage: USAGE,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function openaiStream(pieces: string[]): Response {
|
|
91
|
+
const events = pieces.map((text) => JSON.stringify({ choices: [{ delta: { content: text } }] }));
|
|
92
|
+
events.push(JSON.stringify({ choices: [], usage: USAGE }));
|
|
93
|
+
events.push(JSON.stringify({ choices: [{ delta: {}, finish_reason: "stop" }] }));
|
|
94
|
+
events.push("[DONE]");
|
|
95
|
+
return sse(events);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ─── Anthropic ───────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
export function anthropicText(text: string, stopReason = "end_turn"): Response {
|
|
101
|
+
return Response.json({
|
|
102
|
+
content: [{ type: "text", text }],
|
|
103
|
+
stop_reason: stopReason,
|
|
104
|
+
usage: A_USAGE,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function anthropicTool(name: string, input: any, id = "toolu_1"): Response {
|
|
109
|
+
return Response.json({
|
|
110
|
+
content: [{ type: "tool_use", id, name, input }],
|
|
111
|
+
stop_reason: "tool_use",
|
|
112
|
+
usage: A_USAGE,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function anthropicStream(pieces: string[]): Response {
|
|
117
|
+
const events: string[] = [
|
|
118
|
+
JSON.stringify({ type: "message_start", message: { usage: { input_tokens: 9, output_tokens: 0 } } }),
|
|
119
|
+
JSON.stringify({ type: "content_block_start", index: 0, content_block: { type: "text", text: "" } }),
|
|
120
|
+
];
|
|
121
|
+
for (const text of pieces) {
|
|
122
|
+
events.push(JSON.stringify({ type: "content_block_delta", index: 0, delta: { type: "text_delta", text } }));
|
|
123
|
+
}
|
|
124
|
+
events.push(JSON.stringify({ type: "content_block_stop", index: 0 }));
|
|
125
|
+
events.push(JSON.stringify({ type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { output_tokens: 5 } }));
|
|
126
|
+
events.push(JSON.stringify({ type: "message_stop" }));
|
|
127
|
+
return sse(events);
|
|
128
|
+
}
|
|
@@ -18,6 +18,9 @@ await bootApp({
|
|
|
18
18
|
database: { uri: Bun.env.RO_SRC ?? "mongodb://localhost:27017/_RO_SRC" },
|
|
19
19
|
replication: {
|
|
20
20
|
enabled: true,
|
|
21
|
+
// Boot capabilities are under test here, not the replication engine:
|
|
22
|
+
// the scan mode keeps the assertions deterministic
|
|
23
|
+
mode: "scan",
|
|
21
24
|
runOnBoot: true,
|
|
22
25
|
schedule: { interval: "1h" },
|
|
23
26
|
destinations: [{ id: "backup", uri: Bun.env.RO_DST ?? "mongodb://localhost:27017/_RO_DST" }],
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { define, agents } from "../../../../index";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The collection this tenant names for its agent memory. The loader publishes it
|
|
5
|
+
* (`cfg.agentMemories`), so the replication engine copies it like a declared
|
|
6
|
+
* collection — `replication.exclude: ['memory']` is the opt-out.
|
|
7
|
+
*/
|
|
8
|
+
export const AGENT_THREADS = "agent_threads";
|
|
9
|
+
|
|
10
|
+
export default define.Agent({
|
|
11
|
+
id: "chat",
|
|
12
|
+
description: "Fixture agent whose memory lives in a tenant-named collection.",
|
|
13
|
+
instructions: "Fixture agent — no model call is ever made in these tests.",
|
|
14
|
+
provider: { model: "test-model", protocol: "openai", apiKey: "fixture" },
|
|
15
|
+
memory: new agents.memory.Mongo({ collection: AGENT_THREADS }),
|
|
16
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Test fixture — a collection whose replication key is a **number** (not a date).
|
|
2
|
+
// The delete guard must still match on the destination.
|
|
3
|
+
export default {
|
|
4
|
+
_isCollection_: true,
|
|
5
|
+
_isTimeSerie_: false,
|
|
6
|
+
type: "document",
|
|
7
|
+
slug: "counters",
|
|
8
|
+
fields: [
|
|
9
|
+
{ name: "seq", type: "number" },
|
|
10
|
+
{ name: "title", type: "string" },
|
|
11
|
+
],
|
|
12
|
+
replication: { enabled: true, key: "seq" },
|
|
13
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { define } from "../../../../index";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fixture — an agent whose system prompt carries skills, declared every way a
|
|
5
|
+
* source can be: a glob of a folder, one file, a regex over the tenant, and the
|
|
6
|
+
* `SKILL.md` directory layout.
|
|
7
|
+
*
|
|
8
|
+
* The last two patterns overlap on purpose (`./docs/*.md` and the `docs` regex):
|
|
9
|
+
* a file matched twice is one skill.
|
|
10
|
+
*/
|
|
11
|
+
export default define.Agent({
|
|
12
|
+
id: "docs",
|
|
13
|
+
description: "Answers from the tenant's own documentation.",
|
|
14
|
+
instructions: "Answer from the documentation you were given.",
|
|
15
|
+
provider: { model: "claude-test", protocol: "anthropic", apiKey: "fixture-key" },
|
|
16
|
+
skills: [
|
|
17
|
+
"./docs/*.md",
|
|
18
|
+
"./guide/ml.md",
|
|
19
|
+
/^docs\/.*\.md$/,
|
|
20
|
+
/^runbooks\/.*$/,
|
|
21
|
+
"./guides/**/SKILL.md",
|
|
22
|
+
],
|
|
23
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { define } from "../../../../index";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fixture — an agent whose skill is malformed (`broken/no-name.md` has no `name`).
|
|
5
|
+
*
|
|
6
|
+
* A broken skill refuses **this agent** at load, and must not hide the others.
|
|
7
|
+
*/
|
|
8
|
+
export default define.Agent({
|
|
9
|
+
id: "incomplete",
|
|
10
|
+
description: "Carries a skill that does not follow the format.",
|
|
11
|
+
instructions: "Nothing to see here.",
|
|
12
|
+
provider: { model: "claude-test", protocol: "anthropic", apiKey: "fixture-key" },
|
|
13
|
+
skills: ["./broken/no-name.md"],
|
|
14
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: long-compat
|
|
3
|
+
description: A skill whose compatibility line is too long.
|
|
4
|
+
compatibility: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Body.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: long-description
|
|
3
|
+
description: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Body.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pdf-forms
|
|
3
|
+
description: Fill, flatten and merge PDF forms. Use it for any AcroForm work.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Needs the `pdftk` binary on the host.
|
|
6
|
+
metadata:
|
|
7
|
+
owner: platform
|
|
8
|
+
reviewed: 2026-09-24
|
|
9
|
+
allowed-tools: read_pdf write_pdf
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Filling a form
|
|
13
|
+
|
|
14
|
+
1. Dump the fields: `pdftk form.pdf dump_data_fields`.
|
|
15
|
+
2. Build the FDF, then fill it: `pdftk form.pdf fill_form data.fdf output out.pdf`.
|
|
16
|
+
3. Flatten: `pdftk out.pdf output flat.pdf flatten`.
|
|
17
|
+
|
|
18
|
+
Never flatten before the signature step — it makes the form read-only.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ml-conventions
|
|
3
|
+
description: The tenant's ML conventions — feature naming, training splits, review gates.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Conventions
|
|
7
|
+
|
|
8
|
+
- Features are `snake_case`, prefixed by their source (`crm_`, `billing_`).
|
|
9
|
+
- A model only ships with a held-out split and a written evaluation.
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
// Test fixture — a namespace WITHOUT `api.access` (must be denied over HTTP)
|
|
1
|
+
// Test fixture — a namespace WITHOUT `api.access` (must be denied over HTTP),
|
|
2
|
+
// and which explicitly opts OUT of replication.
|
|
2
3
|
export default {
|
|
3
4
|
_isVars_: true,
|
|
4
5
|
namespace: "private",
|
|
5
6
|
vars: { secret: "shh" },
|
|
7
|
+
// Framework collections are replicated by default: opting out is explicit
|
|
8
|
+
replication: { enabled: false },
|
|
6
9
|
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Test fixture — `b` always fails, `a` must be compensated (once, never twice).
|
|
2
|
+
// Also proves the step ctx `rest` is a real tenant client.
|
|
3
|
+
const state = ((globalThis as any).__wfEngine ??= { log: [], attempts: {} });
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
_isWorkflow_: true,
|
|
7
|
+
id: "failing",
|
|
8
|
+
name: "Failing",
|
|
9
|
+
version: 1,
|
|
10
|
+
steps: [
|
|
11
|
+
{
|
|
12
|
+
id: "a",
|
|
13
|
+
exec: async ({ rest }: any) => {
|
|
14
|
+
state.log.push(`failing:a:rest=${typeof rest?.find === "function" ? "client" : "engine"}`);
|
|
15
|
+
// A sub-workflow, through the client handed to the step
|
|
16
|
+
await rest.workflow.run("simple", { echo: "sub" });
|
|
17
|
+
state.log.push("failing:a:sub-workflow-done");
|
|
18
|
+
return { a: 1 };
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{ id: "b", exec: async () => { state.log.push("failing:b"); throw new Error("boom-b"); } },
|
|
22
|
+
{ id: "c", exec: async () => { state.log.push("failing:c"); return { c: 1 }; } },
|
|
23
|
+
],
|
|
24
|
+
compensations: [
|
|
25
|
+
{
|
|
26
|
+
id: "undo-a",
|
|
27
|
+
depend: ["a"],
|
|
28
|
+
exec: async () => {
|
|
29
|
+
state.attempts["undo-a"] = (state.attempts["undo-a"] ?? 0) + 1;
|
|
30
|
+
state.log.push(`failing:undo-a:${state.attempts["undo-a"]}`);
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Test fixture — a step that fails until `data.failUntil` attempts are reached,
|
|
2
|
+
// with retries. Used to prove a transient failure does not roll the saga back.
|
|
3
|
+
const state = ((globalThis as any).__wfEngine ??= { log: [], attempts: {} });
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
_isWorkflow_: true,
|
|
7
|
+
id: "flaky",
|
|
8
|
+
name: "Flaky",
|
|
9
|
+
version: 1,
|
|
10
|
+
steps: [
|
|
11
|
+
{
|
|
12
|
+
id: "unstable",
|
|
13
|
+
retries: 2,
|
|
14
|
+
backoffMs: 1,
|
|
15
|
+
exec: async ({ data }: any) => {
|
|
16
|
+
const n = (state.attempts.unstable = (state.attempts.unstable ?? 0) + 1);
|
|
17
|
+
state.log.push(`flaky:attempt:${n}`);
|
|
18
|
+
if (n <= (data?.failUntil ?? 0)) throw new Error(`transient failure ${n}`);
|
|
19
|
+
return { attempts: n };
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Test fixture — a step that outlives its `timeout` (150ms vs 1s by default).
|
|
2
|
+
export default {
|
|
3
|
+
_isWorkflow_: true,
|
|
4
|
+
id: "hang",
|
|
5
|
+
name: "Hang",
|
|
6
|
+
version: 1,
|
|
7
|
+
steps: [
|
|
8
|
+
{
|
|
9
|
+
id: "hang",
|
|
10
|
+
timeout: "150ms",
|
|
11
|
+
exec: async ({ data }: any) => {
|
|
12
|
+
await new Promise((resolve) => setTimeout(resolve, data?.ms ?? 1000));
|
|
13
|
+
return { done: true };
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|