@anteros/core 0.0.40 → 0.0.42

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.
Files changed (130) hide show
  1. package/database/audit.ts +143 -0
  2. package/database/collection.ts +6 -4
  3. package/database/deleteLog.ts +98 -0
  4. package/database/file.ts +7 -6
  5. package/database/mongodbadapter.ts +420 -185
  6. package/database/replication.ts +858 -0
  7. package/database/schema.ts +140 -125
  8. package/database/tenant.ts +4 -0
  9. package/database/ttl.ts +68 -0
  10. package/database/vars.ts +405 -0
  11. package/database/workflow.ts +466 -240
  12. package/index.ts +16 -0
  13. package/lib/agent.ts +1327 -0
  14. package/lib/agentMemory.ts +586 -0
  15. package/lib/agents.ts +157 -0
  16. package/lib/ai.ts +0 -0
  17. package/lib/api.ts +246 -0
  18. package/lib/attachment.ts +351 -0
  19. package/lib/audit.ts +271 -0
  20. package/lib/auditResult.ts +133 -0
  21. package/lib/define.ts +62 -1
  22. package/lib/endpoints.ts +80 -0
  23. package/lib/files.ts +66 -12
  24. package/lib/jsonSchema.ts +183 -0
  25. package/lib/lifecycle.ts +147 -0
  26. package/lib/load.ts +17 -0
  27. package/lib/mcp.ts +3 -2
  28. package/lib/messages.ts +299 -0
  29. package/lib/providers.ts +867 -0
  30. package/lib/redact.ts +117 -0
  31. package/lib/routes.ts +14 -1
  32. package/lib/scriptWorker.ts +4 -1
  33. package/lib/scripts.ts +6 -1
  34. package/lib/sockets.ts +3 -1
  35. package/lib/workflow.ts +209 -37
  36. package/lib/zip.ts +109 -0
  37. package/package.json +1 -1
  38. package/server/access.ts +96 -0
  39. package/server/agents.ts +451 -0
  40. package/server/api.ts +121 -95
  41. package/server/boot.ts +196 -53
  42. package/server/flags.ts +136 -0
  43. package/server/hono.ts +9 -4
  44. package/server/mcp.ts +17 -63
  45. package/server/routes.ts +19 -24
  46. package/tests/agent-memory.test.ts +617 -0
  47. package/tests/agent-working-memory.test.ts +402 -0
  48. package/tests/agent.test.ts +1239 -0
  49. package/tests/agents-api.test.ts +593 -0
  50. package/tests/api.test.ts +51 -0
  51. package/tests/attachment.test.ts +241 -0
  52. package/tests/audit-file.test.ts +296 -0
  53. package/tests/audit-operations.test.ts +184 -0
  54. package/tests/audit-redact.test.ts +259 -0
  55. package/tests/audit-results.test.ts +188 -0
  56. package/tests/audit-retention-config.test.ts +76 -0
  57. package/tests/audit-run-action.test.ts +120 -0
  58. package/tests/audit.test.ts +320 -0
  59. package/tests/db-cache.test.ts +7 -7
  60. package/tests/endpoints.test.ts +80 -0
  61. package/tests/file-collection-replication.test.ts +98 -0
  62. package/tests/fixtures/agent-tenant/agents/support.agent.ts +9 -0
  63. package/tests/fixtures/agent-tenant/agents/weather.agent.ts +21 -0
  64. package/tests/fixtures/agents-api/agents/assistant.agent.ts +46 -0
  65. package/tests/fixtures/agents-api/agents/plain.agent.ts +23 -0
  66. package/tests/fixtures/agents-api/agents/private.agent.ts +15 -0
  67. package/tests/fixtures/agents-api/collections/notes.model.ts +26 -0
  68. package/tests/fixtures/agents-api/routes/probe.route.ts +21 -0
  69. package/tests/fixtures/agents-api/services/probe.service.ts +19 -0
  70. package/tests/fixtures/attachment/data.csv +2 -0
  71. package/tests/fixtures/fake-provider.ts +128 -0
  72. package/tests/fixtures/lc-disabled/lifecycle.ts +11 -0
  73. package/tests/fixtures/lc-hang/lifecycle.ts +13 -0
  74. package/tests/fixtures/lc-normal/lifecycle.ts +23 -0
  75. package/tests/fixtures/lc-throw/lifecycle.ts +6 -0
  76. package/tests/fixtures/repl-only/app.ts +26 -0
  77. package/tests/fixtures/repl-only/tenant/collections/orders.model.ts +14 -0
  78. package/tests/fixtures/repl-only/tenant/lifecycle.ts +14 -0
  79. package/tests/fixtures/repl-only/tenant/scripts/probe.run.ts +8 -0
  80. package/tests/fixtures/replication-meta/agents/chat.agent.ts +16 -0
  81. package/tests/fixtures/replication-meta/collections/counters.model.ts +13 -0
  82. package/tests/fixtures/replication-meta/collections/orders.model.ts +9 -0
  83. package/tests/fixtures/replication-meta/workflows/run.workflow.ts +8 -0
  84. package/tests/fixtures/vars-tenant/vars/config.var.ts +23 -0
  85. package/tests/fixtures/vars-tenant/vars/private.var.ts +9 -0
  86. package/tests/fixtures/workflow-engine/workflows/failing.workflow.ts +34 -0
  87. package/tests/fixtures/workflow-engine/workflows/flaky.workflow.ts +23 -0
  88. package/tests/fixtures/workflow-engine/workflows/hang.workflow.ts +17 -0
  89. package/tests/fixtures/workflow-engine/workflows/simple.workflow.ts +19 -0
  90. package/tests/fixtures/workflow-engine/workflows/slow.workflow.ts +23 -0
  91. package/tests/fixtures/workflow-engine/workflows/versioned.workflow.ts +19 -0
  92. package/tests/fixtures/workflow-tenant/workflows/broken.workflow.ts +3 -0
  93. package/tests/fixtures/workflow-tenant/workflows/disabled.workflow.ts +8 -0
  94. package/tests/fixtures/workflow-tenant/workflows/invoice.workflow.ts +21 -0
  95. package/tests/fixtures/workflow-tenant/workflows/no-id.workflow.ts +7 -0
  96. package/tests/fixtures/workflow-tenant/workflows/order-duplicate.workflow.ts +13 -0
  97. package/tests/fixtures/workflow-tenant/workflows/order.workflow.ts +17 -0
  98. package/tests/hono.test.ts +6 -3
  99. package/tests/lifecycle.test.ts +96 -0
  100. package/tests/locks.test.ts +120 -0
  101. package/tests/mcp.test.ts +11 -11
  102. package/tests/messages.test.ts +258 -0
  103. package/tests/replication-control.test.ts +128 -0
  104. package/tests/replication-initial-sync.test.ts +86 -0
  105. package/tests/replication-meta.test.ts +243 -0
  106. package/tests/replication-only.test.ts +358 -0
  107. package/tests/replication.test.ts +188 -0
  108. package/tests/upload.test.ts +141 -8
  109. package/tests/vars-api.test.ts +123 -0
  110. package/tests/vars-replication.test.ts +96 -0
  111. package/tests/vars.test.ts +216 -0
  112. package/tests/workflow-engine.test.ts +340 -0
  113. package/tests/workflow-retention.test.ts +103 -0
  114. package/tests/workflow.test.ts +186 -0
  115. package/types/agent.d.ts +837 -0
  116. package/types/collection.d.ts +3 -0
  117. package/types/config.d.ts +80 -0
  118. package/types/file.d.ts +11 -8
  119. package/types/hook.d.ts +6 -0
  120. package/types/lifecycle.d.ts +77 -0
  121. package/types/mcp.d.ts +11 -1
  122. package/types/replication.d.ts +228 -0
  123. package/types/route.d.ts +7 -1
  124. package/types/scripts.d.ts +6 -0
  125. package/types/service.d.ts +7 -1
  126. package/types/tenant.d.ts +36 -0
  127. package/types/vars.d.ts +147 -0
  128. package/types/websocket.d.ts +6 -0
  129. package/types/workflow.d.ts +50 -3
  130. package/utils/func.ts +67 -52
@@ -0,0 +1,136 @@
1
+ /**
2
+ * What a process actually runs.
3
+ *
4
+ * `api: false` disables everything the HTTP server carries — routes, services,
5
+ * MCP tools, middlewares, websockets — and the scripts that run alongside it.
6
+ */
7
+ export type BootCapabilities = {
8
+ /** HTTP server: routes, services, MCP tools, middlewares, `/health` */
9
+ api: boolean
10
+ /** Replication engine: schedule, tombstones, `_replication_` state */
11
+ replication: boolean
12
+ /** `runScripts()` shortly after boot */
13
+ scripts: boolean
14
+ /** Socket.IO handlers and the `/socket.io/` endpoint */
15
+ sockets: boolean
16
+ }
17
+
18
+ /**
19
+ * Named boot modes (configurable through `server.mode`, overridable by flags).
20
+ *
21
+ * - `full` (default): everything.
22
+ * - `replication-only`: replication engine only — no API at all.
23
+ * - `no-replication`: full server that never starts the replication engine.
24
+ * - `api-only`: the HTTP API and nothing around it (no replication, no scripts, no sockets).
25
+ */
26
+ export type BootMode = 'full' | 'replication-only' | 'no-replication' | 'api-only'
27
+
28
+ export type BootFlags = {
29
+ /** `--replication-only` — replication engine only, no API */
30
+ replicationOnly: boolean
31
+ /** `--no-replication` — full server, replication engine not started */
32
+ noReplication: boolean
33
+ /** `--api-only` — the HTTP API and nothing around it */
34
+ apiOnly: boolean
35
+ /** `--no-scripts` — never run the tenant scripts (`*.run.ts` under `scripts/`) */
36
+ noScripts: boolean
37
+ /** `--no-sockets` — no Socket.IO handlers, no `/socket.io/` endpoint */
38
+ noSockets: boolean
39
+ }
40
+
41
+ export const FULL_CAPABILITIES: BootCapabilities = {
42
+ api: true,
43
+ replication: true,
44
+ scripts: true,
45
+ sockets: true,
46
+ }
47
+
48
+ const REPLICATION_ONLY: BootCapabilities = {
49
+ api: false,
50
+ replication: true,
51
+ scripts: false,
52
+ sockets: false,
53
+ }
54
+
55
+ const API_ONLY: BootCapabilities = {
56
+ api: true,
57
+ replication: false,
58
+ scripts: false,
59
+ sockets: false,
60
+ }
61
+
62
+ function bootModeCapabilities(mode?: BootMode): BootCapabilities {
63
+ switch (mode) {
64
+ case 'replication-only': return { ...REPLICATION_ONLY }
65
+ case 'api-only': return { ...API_ONLY }
66
+ case 'no-replication': return { ...FULL_CAPABILITIES, replication: false }
67
+ default: return { ...FULL_CAPABILITIES }
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Read the boot flags from the command line (camelCase spellings accepted).
73
+ *
74
+ * @example bun index.ts --replication-only
75
+ * @example bun index.ts --api-only
76
+ * @example bun index.ts --no-scripts --no-sockets
77
+ */
78
+ function parseBootFlags(argv: string[] = process.argv): BootFlags {
79
+ const has = (...names: string[]) => names.some((name) => argv.includes(name))
80
+ return {
81
+ replicationOnly: has('--replication-only', '--replicationOnly'),
82
+ noReplication: has('--no-replication', '--noReplication'),
83
+ apiOnly: has('--api-only', '--apiOnly'),
84
+ noScripts: has('--no-scripts', '--noScripts'),
85
+ noSockets: has('--no-sockets', '--noSockets'),
86
+ }
87
+ }
88
+
89
+ /** True when the flags ask for contradictory things — worth warning the operator */
90
+ function conflictingBootFlags(flags: BootFlags): boolean {
91
+ return flags.replicationOnly && (flags.noReplication || flags.apiOnly)
92
+ }
93
+
94
+ /**
95
+ * What the process will run: `server.mode` first, then the flags, which win.
96
+ * `--api-only` is the preset "API and nothing around it".
97
+ */
98
+ function resolveCapabilities(flags: BootFlags, configured?: BootMode): BootCapabilities {
99
+ // A flag always wins over the configured mode
100
+ if (flags.replicationOnly) return { ...REPLICATION_ONLY }
101
+ if (flags.apiOnly) return { ...API_ONLY }
102
+
103
+ const caps = bootModeCapabilities(configured)
104
+ if (flags.noReplication) caps.replication = false
105
+ if (flags.noScripts) caps.scripts = false
106
+ if (flags.noSockets) caps.sockets = false
107
+
108
+ // Scripts and sockets belong to the API process
109
+ if (!caps.api) {
110
+ caps.scripts = false
111
+ caps.sockets = false
112
+ }
113
+
114
+ return caps
115
+ }
116
+
117
+ /** Disabled capabilities, for the banner and logs — e.g. `['scripts', 'sockets']` */
118
+ function offCapabilities(caps: BootCapabilities): (keyof BootCapabilities)[] {
119
+ return (Object.keys(caps) as (keyof BootCapabilities)[]).filter((key) => !caps[key])
120
+ }
121
+
122
+ /** Closest named mode, used by the banner — all flags off is simply `full` */
123
+ function capabilitiesLabel(caps: BootCapabilities): BootMode {
124
+ if (!caps.api && caps.replication) return 'replication-only'
125
+ if (caps.api && !caps.replication && !caps.scripts && !caps.sockets) return 'api-only'
126
+ // `--no-scripts` / `--no-sockets` alone stay in `full`: the banner lists what is off
127
+ return 'full'
128
+ }
129
+
130
+ export {
131
+ parseBootFlags,
132
+ conflictingBootFlags,
133
+ resolveCapabilities,
134
+ offCapabilities,
135
+ capabilitiesLabel,
136
+ }
package/server/hono.ts CHANGED
@@ -11,8 +11,10 @@ import { Redis } from "ioredis"
11
11
  import { cfg } from "./config"
12
12
  import { rateLimit, createRedisStore, createMemoryStore, type RateLimitStore } from "./security"
13
13
  import { initializeRoutes } from "./routes";
14
+ import { patterns } from "../lib/endpoints";
14
15
  import { asyncContextStorage, requestCtxStorage } from "../lib/asyncContextStorage";
15
16
  import { initializeApi } from "./api";
17
+ import { initializeAgents } from "./agents";
16
18
  import { initializeMcp } from "./mcp";
17
19
  import { getGlobalMiddlewares } from "../lib/middleware";
18
20
  import { jwt } from "../utils/func";
@@ -326,8 +328,8 @@ function createApp(): Hono<{ Variables: HonoVariables }> {
326
328
  keyGenerator: keyByClientIp(''),
327
329
  }));
328
330
 
329
- // stricter limit for login endpoints
330
- app.use('/api/*/login', rateLimit({
331
+ // stricter limit for login endpoints — the login is a collection action
332
+ app.use(patterns.login, rateLimit({
331
333
  windowMs: cfg.server.rateLimit?.login?.windowMs ?? 60_000,
332
334
  max: cfg.server.rateLimit?.login?.max ?? 10,
333
335
  store,
@@ -345,7 +347,7 @@ function createApp(): Hono<{ Variables: HonoVariables }> {
345
347
 
346
348
 
347
349
  const requestCtx: Record<string, string> = c.req.header();
348
- // Faille 1: never log sensitive headers (JWT, cookies)
350
+ // Flaw 1: never log sensitive headers (JWT, cookies)
349
351
  delete requestCtx.authorization;
350
352
  delete requestCtx.cookie;
351
353
  requestCtxStorage.set('meta', {
@@ -399,7 +401,10 @@ function createApp(): Hono<{ Variables: HonoVariables }> {
399
401
  // initialize api
400
402
  initializeApi(app);
401
403
 
402
- // initialize MCP (tools served at /mcp/:tenant_id)
404
+ // initialize the agents API (/api/:tenant_id/agents/:agent/:action)
405
+ initializeAgents(app);
406
+
407
+ // initialize MCP (tools served at /api/:tenant_id/mcp)
403
408
  initializeMcp(app);
404
409
 
405
410
 
package/server/mcp.ts CHANGED
@@ -10,72 +10,18 @@ import {
10
10
  ErrorCode,
11
11
  } from "@modelcontextprotocol/sdk/types.js";
12
12
  import { StreamableHTTPTransport } from "@hono/mcp";
13
- import type Joi from "joi";
14
13
  import { cfg } from "./config";
15
14
  import { useRest } from "../database/rest";
15
+ import { createAgents } from "../lib/agents";
16
+ import { createApi } from "../lib/api";
16
17
  import { logger } from "../utils/logger";
17
18
  import type { McpTool, McpResource } from "../types/mcp";
18
19
  import type { HonoVariables } from "./env";
20
+ import { joiToJsonSchema } from "../lib/jsonSchema";
21
+ import { patterns } from "../lib/endpoints";
19
22
 
20
- // ─── Joi → JSON Schema (for the MCP protocol) ────────────────────────────
21
-
22
- function joiTypeToJson(type: string): string {
23
- switch (type) {
24
- case 'number': return 'number';
25
- case 'integer': return 'integer';
26
- case 'boolean': return 'boolean';
27
- case 'date': return 'string';
28
- default: return 'string';
29
- }
30
- }
31
-
32
- /** Minimal Joi → JSON Schema converter (covers the common field types). */
33
- function joiToJsonSchema(schema: Joi.Schema, describe = schema.describe() as any): any {
34
- const node: any = {};
35
- const type = describe?.type;
36
-
37
- switch (type) {
38
- case 'object': {
39
- node.type = 'object';
40
- const properties: Record<string, any> = {};
41
- const required: string[] = [];
42
- for (const [key, child] of Object.entries(describe.keys ?? {})) {
43
- properties[key] = joiToJsonSchema(child as any, child);
44
- if ((child as any)?.flags?.presence === 'required') required.push(key);
45
- }
46
- node.properties = properties;
47
- if (required.length) node.required = required;
48
- break;
49
- }
50
- case 'array': {
51
- node.type = 'array';
52
- if (describe.items?.length) node.items = joiToJsonSchema(describe.items[0], describe.items[0]);
53
- break;
54
- }
55
- default: {
56
- node.type = joiTypeToJson(type);
57
- if (type === 'date') node.format = 'date-time';
58
- if (type === 'string') {
59
- for (const rule of describe.rules ?? []) {
60
- if (['email', 'uri', 'uuid', 'isoDate', 'ip'].includes(rule.name)) {
61
- node.format = rule.name;
62
- }
63
- if (rule.name === 'pattern' && rule.args?.regex) {
64
- node.pattern = String(rule.args.regex).replace(/^\/|\/[gimsuy]*$/g, '');
65
- }
66
- }
67
- }
68
- if (describe.valids?.length && describe.valids.length <= 50) {
69
- node.enum = describe.valids;
70
- }
71
- break;
72
- }
73
- }
74
-
75
- if (describe?.flags?.presence === 'required') node.description = node.description ?? 'required';
76
- if (describe?.flags?.description) node.description = describe.flags.description;
77
- return node;
78
- }
23
+ // The Joi → JSON Schema converter is shared with the agent runtime — a tool
24
+ // must look identical whether it is exposed over MCP or to a model.
79
25
 
80
26
  // ─── URI template matching (users://{id} → params) ───────────────────────
81
27
 
@@ -148,7 +94,13 @@ function buildServer(tenantId: string, tools: McpTool[], resources: McpResource[
148
94
 
149
95
  const rest = new useRest({ tenant_id: tenantId });
150
96
  try {
151
- const result = await tool.exec({ c: undefined as any, rest, args: value });
97
+ const result = await tool.exec({
98
+ c: undefined as any,
99
+ rest,
100
+ args: value,
101
+ agents: createAgents(tenantId, rest),
102
+ api: createApi(rest),
103
+ });
152
104
  const isError = !!(result as any)?.isError;
153
105
  logger[isError ? 'warn' : 'info']('MCP tools/call', { method: 'tools/call', tenant: tenantId, tool: req.params.name, args: value, isError, duration: ms(start) });
154
106
  if (result && Array.isArray(result.content)) return result;
@@ -205,6 +157,8 @@ function buildServer(tenantId: string, tools: McpTool[], resources: McpResource[
205
157
  const result = await matched.resource.read({
206
158
  c: undefined as any,
207
159
  rest,
160
+ agents: createAgents(tenantId, rest),
161
+ api: createApi(rest),
208
162
  params: matched.params,
209
163
  uri,
210
164
  });
@@ -221,7 +175,7 @@ function buildServer(tenantId: string, tools: McpTool[], resources: McpResource[
221
175
 
222
176
  /**
223
177
  * Expose each tenant's MCP tools & resources over the Model Context Protocol
224
- * at `GET/POST /mcp/:tenant_id` (Streamable HTTP transport — works with
178
+ * at `GET/POST /api/:tenant_id/mcp` (Streamable HTTP transport — works with
225
179
  * Claude, Cursor, VS Code, and any MCP client).
226
180
  */
227
181
  export function initializeMcp(app: Hono<{ Variables: HonoVariables }>) {
@@ -238,7 +192,7 @@ export function initializeMcp(app: Hono<{ Variables: HonoVariables }>) {
238
192
  resourcesByTenant.set(resource._tenant_ ?? '', list);
239
193
  }
240
194
 
241
- app.all('/mcp/:tenant_id', async (c: Context<{ Variables: HonoVariables }>) => {
195
+ app.all(patterns.mcp, async (c: Context<{ Variables: HonoVariables }>) => {
242
196
  const tenantId = c.req.param('tenant_id') ?? '';
243
197
  const tools = toolsByTenant.get(tenantId) ?? [];
244
198
  const resources = resourcesByTenant.get(tenantId) ?? [];
package/server/routes.ts CHANGED
@@ -3,10 +3,26 @@ import { cfg } from "./config";
3
3
  import path from "path";
4
4
  import { jwt } from "../utils/func";
5
5
  import { useRest } from "../database/rest";
6
+ import { createAgents } from "../lib/agents";
7
+ import { createApi } from "../lib/api";
6
8
  import { io } from "./io";
7
9
  import { requestCtxStorage } from "../lib/asyncContextStorage";
8
10
  import type { HonoVariables } from "./env";
9
11
 
12
+ /** The context of a route handler — `rest` and `agents` share the same tenant. */
13
+ function routeContext(c: any, tenantId: string) {
14
+ const rest = new useRest({ tenant_id: tenantId });
15
+ return {
16
+ c,
17
+ rest,
18
+ // Tenant-scoped LLM registry, bound to this request's `rest`
19
+ agents: createAgents(tenantId, rest),
20
+ api: createApi(rest),
21
+ jwt: jwt,
22
+ io: io,
23
+ };
24
+ }
25
+
10
26
  function initializeRoutes(app: Hono<{ Variables: HonoVariables }>) {
11
27
  for (let route of cfg.routes ?? []) {
12
28
  if (route._prefix_) {
@@ -24,42 +40,21 @@ function initializeRoutes(app: Hono<{ Variables: HonoVariables }>) {
24
40
  if (route.method == "GET") {
25
41
  app.get(routePath, async (c) => { //
26
42
  resolveTenant();
27
- return route.handler({
28
- c,
29
- rest: new useRest({
30
- tenant_id: route._tenant_,
31
- }),
32
- jwt: jwt,
33
- io: io,
34
- });
43
+ return route.handler(routeContext(c, route._tenant_!));
35
44
  });
36
45
  }
37
46
 
38
47
  if (route.method == 'POST') {
39
48
  app.post(routePath, async (c) => {
40
49
  resolveTenant();
41
- return route.handler({
42
- c,
43
- rest: new useRest({
44
- tenant_id: route._tenant_,
45
- }),
46
- jwt: jwt,
47
- io: io,
48
- });
50
+ return route.handler(routeContext(c, route._tenant_!));
49
51
  });
50
52
  }
51
53
 
52
54
  if (route.method == "PUT") {
53
55
  app.put(routePath, async (c) => { //
54
56
  resolveTenant();
55
- return route.handler({
56
- c,
57
- rest: new useRest({
58
- tenant_id: route._tenant_,
59
- }),
60
- jwt: jwt,
61
- io: io,
62
- });
57
+ return route.handler(routeContext(c, route._tenant_!));
63
58
  });
64
59
  }
65
60
  }