@hivemagnus/ardent 0.0.0-stage → 1.0.2

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 (115) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +274 -2
  3. package/dist/a2a-client.d.ts +35 -0
  4. package/dist/a2a-client.js +172 -0
  5. package/dist/a2a-hermes-alias.d.ts +3 -0
  6. package/dist/a2a-hermes-alias.js +57 -0
  7. package/dist/a2a-idempotency.d.ts +13 -0
  8. package/dist/a2a-idempotency.js +86 -0
  9. package/dist/a2a-peers.d.ts +28 -0
  10. package/dist/a2a-peers.js +108 -0
  11. package/dist/a2a-push-inbox.d.ts +22 -0
  12. package/dist/a2a-push-inbox.js +84 -0
  13. package/dist/a2a-push.d.ts +27 -0
  14. package/dist/a2a-push.js +188 -0
  15. package/dist/a2a-server.d.ts +37 -0
  16. package/dist/a2a-server.js +186 -0
  17. package/dist/a2a-store.d.ts +10 -0
  18. package/dist/a2a-store.js +122 -0
  19. package/dist/a2a.d.ts +19 -0
  20. package/dist/a2a.js +466 -0
  21. package/dist/api.d.ts +6 -0
  22. package/dist/api.js +381 -0
  23. package/dist/artifacts.d.ts +15 -0
  24. package/dist/artifacts.js +41 -0
  25. package/dist/backend-events.d.ts +49 -0
  26. package/dist/backend-events.js +8 -0
  27. package/dist/channels.d.ts +52 -0
  28. package/dist/channels.js +184 -0
  29. package/dist/cli.d.ts +2 -0
  30. package/dist/cli.js +845 -0
  31. package/dist/codex-output-budget.d.ts +17 -0
  32. package/dist/codex-output-budget.js +31 -0
  33. package/dist/codex-output-budget.test.d.ts +1 -0
  34. package/dist/codex-output-budget.test.js +29 -0
  35. package/dist/codex.d.ts +82 -0
  36. package/dist/codex.js +431 -0
  37. package/dist/config.d.ts +171 -0
  38. package/dist/config.js +244 -0
  39. package/dist/context-checkpoints.d.ts +49 -0
  40. package/dist/context-checkpoints.js +248 -0
  41. package/dist/context.d.ts +16 -0
  42. package/dist/context.js +101 -0
  43. package/dist/gateway-lock.d.ts +18 -0
  44. package/dist/gateway-lock.js +104 -0
  45. package/dist/hermes-compat.d.ts +15 -0
  46. package/dist/hermes-compat.js +62 -0
  47. package/dist/identity.d.ts +15 -0
  48. package/dist/identity.js +52 -0
  49. package/dist/index.d.ts +9 -0
  50. package/dist/index.js +8 -0
  51. package/dist/job-engine.d.ts +222 -0
  52. package/dist/job-engine.js +497 -0
  53. package/dist/job-news-handlers.d.ts +54 -0
  54. package/dist/job-news-handlers.js +153 -0
  55. package/dist/job-ownership.d.ts +9 -0
  56. package/dist/job-ownership.js +17 -0
  57. package/dist/json-schema.d.ts +9 -0
  58. package/dist/json-schema.js +20 -0
  59. package/dist/llamacpp.d.ts +49 -0
  60. package/dist/llamacpp.js +216 -0
  61. package/dist/magnus-control.d.ts +3 -0
  62. package/dist/magnus-control.js +99 -0
  63. package/dist/magnus-node-dependency-impact.d.ts +1 -0
  64. package/dist/magnus-node-dependency-impact.js +126 -0
  65. package/dist/magnus-owner-reads.d.ts +4 -0
  66. package/dist/magnus-owner-reads.js +99 -0
  67. package/dist/magnus-readiness.d.ts +1 -0
  68. package/dist/magnus-readiness.js +108 -0
  69. package/dist/mattermost.d.ts +18 -0
  70. package/dist/mattermost.js +303 -0
  71. package/dist/memory-tools.d.ts +5 -0
  72. package/dist/memory-tools.js +49 -0
  73. package/dist/memory.d.ts +40 -0
  74. package/dist/memory.js +128 -0
  75. package/dist/model-policy.d.ts +64 -0
  76. package/dist/model-policy.js +56 -0
  77. package/dist/news.d.ts +10 -0
  78. package/dist/news.js +291 -0
  79. package/dist/owner-read.d.ts +32 -0
  80. package/dist/owner-read.js +130 -0
  81. package/dist/plugins.d.ts +74 -0
  82. package/dist/plugins.js +115 -0
  83. package/dist/research.d.ts +110 -0
  84. package/dist/research.js +164 -0
  85. package/dist/runtime.d.ts +33 -0
  86. package/dist/runtime.js +665 -0
  87. package/dist/skills.d.ts +13 -0
  88. package/dist/skills.js +87 -0
  89. package/dist/store.d.ts +142 -0
  90. package/dist/store.js +322 -0
  91. package/dist/team-artifacts.d.ts +138 -0
  92. package/dist/team-artifacts.js +241 -0
  93. package/dist/team-child.d.ts +21 -0
  94. package/dist/team-child.js +130 -0
  95. package/dist/team-contract.d.ts +116 -0
  96. package/dist/team-contract.js +84 -0
  97. package/dist/team-control.d.ts +8 -0
  98. package/dist/team-control.js +152 -0
  99. package/dist/telegram.d.ts +35 -0
  100. package/dist/telegram.js +284 -0
  101. package/dist/tools.d.ts +48 -0
  102. package/dist/tools.js +58 -0
  103. package/dist/version.d.ts +1 -0
  104. package/dist/version.js +5 -0
  105. package/model-policy.default.json +20 -0
  106. package/package.json +84 -4
  107. package/profiles/assistant/IDENTITY.md +9 -0
  108. package/profiles/assistant/SOUL.md +8 -0
  109. package/profiles/assistant/config.yaml +88 -0
  110. package/profiles/assistant/peers.yaml +12 -0
  111. package/profiles/assistant/skills/grounded-answers/SKILL.md +18 -0
  112. package/protocol/LICENSE-A2A.txt +202 -0
  113. package/protocol/README.md +12 -0
  114. package/protocol/a2a.proto +811 -0
  115. package/protocol/lock.json +9 -0
package/dist/api.js ADDED
@@ -0,0 +1,381 @@
1
+ import { timingSafeEqual } from 'node:crypto';
2
+ import { Hono } from 'hono';
3
+ import { streamSSE } from 'hono/streaming';
4
+ import { z } from 'zod';
5
+ import { ARDENT_VERSION } from './version.js';
6
+ import { ConfigSchema } from './config.js';
7
+ import { runtimeProblems, saveConfig, loadConfig, resolveReference, parseConfig } from './config.js';
8
+ import { listSkills, setSkillEnabled } from './skills.js';
9
+ import { HiveMemClient } from './memory.js';
10
+ import { llamaEndpoint, LlamaCppBackend } from './llamacpp.js';
11
+ import { assertCodexProtocol, CodexAppServer } from './codex.js';
12
+ import { toJsonSchema } from './json-schema.js';
13
+ import { readIdentity, writeIdentity, identityHistory, restoreIdentity } from './identity.js';
14
+ import { inboundTokenEnv, listPeers, outboundTokenEnv, parsePeersConfig, readPeersConfig, savePeersConfig } from './a2a-peers.js';
15
+ import { ArdentA2AClient } from './a2a-client.js';
16
+ import { TelegramAdapter } from './telegram.js';
17
+ import { MattermostAdapter } from './mattermost.js';
18
+ import { EventSchema, SessionMessageSchema, SessionSchema, TaskSchema, TaskState } from './store.js';
19
+ import { pluginContext } from './plugins.js';
20
+ const Submit = z.object({ prompt: z.string().trim().min(1).max(16000), session_id: z.string().guid().optional(), idempotency_key: z.string().min(1).max(128).optional() }).strict();
21
+ const SessionOpen = z.object({ key: z.string().trim().min(1).max(256), reset: z.boolean().optional() }).strict();
22
+ const ConfigEdit = z.object({ text: z.string().min(1).max(200000), revision: z.string().regex(/^[a-f0-9]{64}$/) }).strict();
23
+ const SkillEdit = z.object({ enabled: z.boolean(), revision: z.string().regex(/^[a-f0-9]{64}$/) }).strict();
24
+ const IdentityEdit = z.object({ text: z.string().min(1).max(128 * 1024), revision: z.string().regex(/^[a-f0-9]{64}$/) }).strict();
25
+ const IdentityRestore = z.object({ history_name: z.string().min(1).max(128), revision: z.string().regex(/^[a-f0-9]{64}$/) }).strict();
26
+ const PageLimit = z.coerce.number().int().min(1).max(100);
27
+ const EventCursor = z.coerce.number().int().min(0);
28
+ function authEqual(actual, expected) {
29
+ const a = Buffer.from(actual);
30
+ const b = Buffer.from(expected);
31
+ return a.length === b.length && timingSafeEqual(a, b);
32
+ }
33
+ function parseJson(body, schema) {
34
+ const result = schema.safeParse(body);
35
+ if (!result.success)
36
+ throw new Error(result.error.issues.map(issue => `${issue.path.join('.')}: ${issue.message}`).join('; '));
37
+ return result.data;
38
+ }
39
+ const jsonSchema = (schema) => toJsonSchema(schema, 'openapi-3.0');
40
+ const jsonResponse = (description, schema) => ({ description, content: { 'application/json': { schema: jsonSchema(schema) } } });
41
+ const a2aServer = [{ url: 'http://localhost:9901', description: 'Example A2A listener. Use the HTTP+JSON interface URL advertised by the Agent Card.' }];
42
+ const a2aVersion = { in: 'header', name: 'A2A-Version', required: true, schema: { type: 'string', enum: ['1.0'] }, description: 'Negotiated A2A protocol version' };
43
+ const a2aTaskId = { in: 'path', name: 'taskId', required: true, schema: { type: 'string' } };
44
+ const a2aConfigId = { in: 'path', name: 'configId', required: true, schema: { type: 'string' } };
45
+ const a2aBody = { required: true, content: { 'application/json': { schema: { type: 'object', additionalProperties: true }, example: { message: { messageId: 'request-1', role: 'user', parts: [{ kind: 'text', text: 'Research the current update' }] } } } } };
46
+ const a2aResult = { '200': { description: 'A2A v1 task or message; see the pinned A2A protocol contract', content: { 'application/json': { schema: { type: 'object', additionalProperties: true } } } }, '400': { description: 'Invalid A2A request' }, '401': { description: 'Peer authentication failed' }, '404': { description: 'Task or configuration absent' } };
47
+ const taskExample = { id: '11111111-1111-4111-8111-111111111111', caller: 'management', context: 'direct', session_id: null, prompt: 'Research the current update', content_hash: '0'.repeat(64), idempotency_key: 'research-1', backend: 'llamacpp', state: 'queued', result: null, error: null, deadline_ms: 1790438400000, created_ms: 1790431200000, updated_ms: 1790431200000 };
48
+ const eventExample = { sequence: 1, task_id: taskExample.id, kind: 'task_accepted', detail: { backend: 'llamacpp' }, created_ms: 1790431200000 };
49
+ const sseExample = `event: task.event\nid: 1\ndata: ${JSON.stringify(eventExample)}\n\n`;
50
+ export function ardentOpenApi() {
51
+ return {
52
+ openapi: '3.1.0', info: { title: 'Magnus Ardent API', version: ARDENT_VERSION },
53
+ servers: [{ url: '/' }],
54
+ components: { securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer' } }, schemas: {
55
+ Task: jsonSchema(TaskSchema), TaskEvent: jsonSchema(EventSchema),
56
+ Error: { type: 'object', required: ['error'], properties: { error: { type: 'object', required: ['code', 'message'], properties: { code: { type: 'string' }, message: { type: 'string' } } } } }
57
+ } },
58
+ paths: {
59
+ '/health/live': { get: { operationId: 'getLiveness', responses: { '200': { description: 'Process is running' } } } },
60
+ '/health/ready': { get: { operationId: 'getReadiness', responses: { '200': { description: 'Configured runtime ready' }, '503': { description: 'Runtime dependency unavailable' } } } },
61
+ '/a2a/rest/extendedAgentCard': { get: { operationId: 'a2aGetExtendedAgentCard', description: 'Authenticated A2A v1 extended Agent Card on the separate A2A listener.', servers: a2aServer, security: [{ bearerAuth: [] }], parameters: [a2aVersion], responses: a2aResult } },
62
+ '/a2a/rest/message:send': { post: { operationId: 'a2aSendMessage', description: 'Canonical A2A v1 HTTP+JSON send operation on the separate A2A listener.', servers: a2aServer, security: [{ bearerAuth: [] }], parameters: [a2aVersion], requestBody: a2aBody, responses: a2aResult } },
63
+ '/a2a/rest/message:stream': { post: { operationId: 'a2aStreamMessage', description: 'Canonical A2A v1 HTTP+JSON streaming send.', servers: a2aServer, security: [{ bearerAuth: [] }], parameters: [a2aVersion], requestBody: a2aBody, responses: { ...a2aResult, '200': { description: 'A2A task/status/artifact SSE events', content: { 'text/event-stream': { schema: { type: 'string' } } } } } } },
64
+ '/a2a/rest/tasks': { get: { operationId: 'a2aListTasks', servers: a2aServer, security: [{ bearerAuth: [] }], parameters: [a2aVersion, { in: 'query', name: 'pageSize', schema: { type: 'integer', minimum: 1, maximum: 100 } }, { in: 'query', name: 'pageToken', schema: { type: 'string' } }], responses: a2aResult } },
65
+ '/a2a/rest/tasks/{taskId}': { get: { operationId: 'a2aGetTask', servers: a2aServer, security: [{ bearerAuth: [] }], parameters: [a2aVersion, a2aTaskId, { in: 'query', name: 'historyLength', schema: { type: 'integer', minimum: 0 } }], responses: a2aResult } },
66
+ '/a2a/rest/tasks/{taskId}:cancel': { post: { operationId: 'a2aCancelTask', servers: a2aServer, security: [{ bearerAuth: [] }], parameters: [a2aVersion, a2aTaskId], responses: a2aResult } },
67
+ '/a2a/rest/tasks/{taskId}:subscribe': { get: { operationId: 'a2aSubscribeTask', servers: a2aServer, security: [{ bearerAuth: [] }], parameters: [a2aVersion, a2aTaskId], responses: { ...a2aResult, '200': { description: 'A2A task/status/artifact SSE events', content: { 'text/event-stream': { schema: { type: 'string' } } } } } }, post: { operationId: 'a2aResumeTaskStream', servers: a2aServer, security: [{ bearerAuth: [] }], parameters: [a2aVersion, a2aTaskId], responses: { ...a2aResult, '200': { description: 'A2A task/status/artifact SSE events', content: { 'text/event-stream': { schema: { type: 'string' } } } } } } },
68
+ '/a2a/rest/tasks/{taskId}/pushNotificationConfigs': { post: { operationId: 'a2aSetPushConfig', servers: a2aServer, security: [{ bearerAuth: [] }], parameters: [a2aVersion, a2aTaskId], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', additionalProperties: true } } } }, responses: { '201': { description: 'Created push configuration', content: { 'application/json': { schema: { type: 'object', additionalProperties: true } } } }, '400': { description: 'Invalid configuration' }, '401': { description: 'Peer authentication failed' }, '404': { description: 'Task absent' } } }, get: { operationId: 'a2aListPushConfigs', servers: a2aServer, security: [{ bearerAuth: [] }], parameters: [a2aVersion, a2aTaskId], responses: a2aResult } },
69
+ '/a2a/rest/tasks/{taskId}/pushNotificationConfigs/{configId}': { get: { operationId: 'a2aGetPushConfig', servers: a2aServer, security: [{ bearerAuth: [] }], parameters: [a2aVersion, a2aTaskId, a2aConfigId], responses: a2aResult }, delete: { operationId: 'a2aDeletePushConfig', servers: a2aServer, security: [{ bearerAuth: [] }], parameters: [a2aVersion, a2aTaskId, a2aConfigId], responses: { '204': { description: 'Push configuration deleted' }, '401': { description: 'Peer authentication failed' }, '404': { description: 'Configuration absent' } } } },
70
+ '/api/v1/tasks': {
71
+ post: { operationId: 'submitTask', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: jsonSchema(Submit), example: { prompt: 'Research today\'s topic', idempotency_key: 'topic-2026-09-26' } } } }, responses: { '202': { ...jsonResponse('Accepted durable task', z.object({ task_id: z.string().guid(), state: TaskState })), content: { 'application/json': { schema: jsonSchema(z.object({ task_id: z.string().guid(), state: TaskState })), example: { task_id: taskExample.id, state: 'queued' } } } }, '400': { description: 'Invalid request' }, '401': { description: 'Unauthorized' }, '409': { description: 'Conflicting idempotency key' } } },
72
+ get: { operationId: 'listTasks', security: [{ bearerAuth: [] }], parameters: [{ in: 'query', name: 'limit', description: 'Maximum number of recent tasks', schema: { type: 'integer', minimum: 1, maximum: 100, default: 50 } }], responses: { '200': { description: 'Recent tasks', content: { 'application/json': { schema: { type: 'object', required: ['tasks'], properties: { tasks: { type: 'array', items: { $ref: '#/components/schemas/Task' } } } }, example: { tasks: [taskExample] } } } } } }
73
+ },
74
+ '/api/v1/sessions': { get: { operationId: 'listSessions', security: [{ bearerAuth: [] }], responses: { '200': jsonResponse('Caller sessions', z.object({ sessions: z.array(SessionSchema) })) } }, post: { operationId: 'openSession', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: jsonSchema(SessionOpen) } } }, responses: { '200': jsonResponse('Existing or new session', SessionSchema) } } },
75
+ '/api/v1/sessions/{id}/messages': { get: { operationId: 'getSessionMessages', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string', format: 'uuid' } }], responses: { '200': jsonResponse('Recent caller session messages', z.object({ messages: z.array(SessionMessageSchema) })), '404': { description: 'Session absent' } } } },
76
+ '/api/v1/tasks/{id}': { get: { operationId: 'getTask', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'Task status', content: { 'application/json': { schema: { $ref: '#/components/schemas/Task' }, example: taskExample } } }, '404': { description: 'Task absent' } } } },
77
+ '/api/v1/tasks/{id}/events': { get: { operationId: 'getTaskEvents', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }, { in: 'query', name: 'after', description: 'Return events with sequence greater than this cursor', schema: { type: 'integer', minimum: 0, default: 0 } }], responses: { '200': { description: 'Journal events after the supplied sequence cursor', content: { 'application/json': { schema: { type: 'object', required: ['events'], properties: { events: { type: 'array', items: { $ref: '#/components/schemas/TaskEvent' } } } }, example: { events: [eventExample] } } } } } } },
78
+ '/api/v1/tasks/{id}/stream': { get: { operationId: 'streamTaskEvents', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }, { in: 'query', name: 'after', schema: { type: 'integer', minimum: 0 } }, { in: 'header', name: 'Last-Event-ID', description: 'Resume after a journal sequence', schema: { type: 'integer', minimum: 0 } }], responses: { '200': { description: 'SSE task.event carries TaskEvent JSON; task.done carries Task JSON', content: { 'text/event-stream': { schema: { type: 'string' }, example: sseExample } } }, '404': { description: 'Task absent' } } } },
79
+ '/api/v1/tasks/{id}/cancel': { post: { operationId: 'cancelTask', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'Cancellation requested' } } } },
80
+ '/api/v1/artifacts/{id}': { get: { operationId: 'getArtifact', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'Artifact bytes' }, '404': { description: 'Artifact absent' } } } },
81
+ '/api/v1/status': { get: { operationId: 'getOperationalStatus', security: [{ bearerAuth: [] }], responses: { '200': jsonResponse('Active profile summary', z.object({ profile: z.string(), backend: z.enum(['llamacpp', 'codex']), tasks: z.number().int() })) } } },
82
+ '/api/v1/jobs/definitions/status': { get: { operationId: 'getJobDefinitionsStatus', security: [{ bearerAuth: [] }], responses: { '200': { description: 'Active profile job definition revision, digest, and redacted window status' }, '503': { description: 'Job engine unavailable' } } } },
83
+ '/api/v1/profile': { get: { operationId: 'getProfile', security: [{ bearerAuth: [] }], responses: { '200': jsonResponse('Profile identity and path', z.object({ profile: z.string(), name: z.string(), product: z.string(), timezone: z.string(), path: z.string() })) } } },
84
+ '/api/v1/config': { get: { operationId: 'getConfig', security: [{ bearerAuth: [] }], responses: { '200': jsonResponse('Raw YAML and revision; secrets remain references', z.object({ revision: z.string(), text: z.string() })) } }, put: { operationId: 'saveConfig', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: jsonSchema(ConfigEdit) } } }, responses: { '200': jsonResponse('Saved; activation required', z.object({ revision: z.string(), activation_required: z.boolean() })), '409': { description: 'Revision conflict' } } } },
85
+ '/api/v1/config/validate': { post: { operationId: 'validateConfig', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: toJsonSchema(z.object({ text: z.string().min(1).max(200000) }).strict(), 'openapi-3.0') } } }, responses: { '200': { description: 'Validation result' }, '400': { description: 'Invalid YAML or configuration' } } } },
86
+ '/api/v1/config/schema': { get: { operationId: 'getConfigSchema', security: [{ bearerAuth: [] }], responses: { '200': { description: 'Generated JSON Schema for versioned YAML configuration' } } } },
87
+ '/api/v1/identity/{kind}': { get: { operationId: 'getIdentity', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'kind', required: true, schema: { type: 'string', enum: ['soul', 'identity'] } }], responses: { '200': { description: 'Identity text and revision' } } }, put: { operationId: 'saveIdentity', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'kind', required: true, schema: { type: 'string', enum: ['soul', 'identity'] } }], requestBody: { required: true, content: { 'application/json': { schema: toJsonSchema(IdentityEdit, 'openapi-3.0') } } }, responses: { '200': { description: 'Saved identity revision' }, '409': { description: 'Revision conflict' } } } },
88
+ '/api/v1/identity/{kind}/history': { get: { operationId: 'getIdentityHistory', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'kind', required: true, schema: { type: 'string', enum: ['soul', 'identity'] } }], responses: { '200': { description: 'Previous versions' } } } },
89
+ '/api/v1/identity/{kind}/restore': { post: { operationId: 'restoreIdentity', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'kind', required: true, schema: { type: 'string', enum: ['soul', 'identity'] } }], requestBody: { required: true, content: { 'application/json': { schema: toJsonSchema(IdentityRestore, 'openapi-3.0') } } }, responses: { '200': { description: 'Restored identity revision' }, '409': { description: 'Revision conflict' } } } },
90
+ '/api/v1/providers': { get: { operationId: 'getProviders', security: [{ bearerAuth: [] }], responses: { '200': { description: 'Configured providers and Codex account mode' } } } },
91
+ '/api/v1/providers/test': { post: { operationId: 'testProvider', security: [{ bearerAuth: [] }], responses: { '200': { description: 'Selected provider probe' }, '503': { description: 'Provider unavailable' } } } },
92
+ '/api/v1/channels': { get: { operationId: 'getChannels', security: [{ bearerAuth: [] }], responses: { '200': { description: 'Channel configuration and credential presence' } } } },
93
+ '/api/v1/channels/{name}/test': { post: { operationId: 'testChannel', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'name', required: true, schema: { type: 'string', enum: ['telegram', 'mattermost'] } }], responses: { '200': { description: 'Read-only channel connection probe' }, '503': { description: 'Channel unavailable' } } } },
94
+ '/api/v1/peers': { get: { operationId: 'getPeers', security: [{ bearerAuth: [] }], responses: { '200': { description: 'Configured peer inventory' } } } },
95
+ '/api/v1/peers/config': { get: { operationId: 'getPeersConfig', security: [{ bearerAuth: [] }], responses: { '200': jsonResponse('Peer YAML and revision', z.object({ text: z.string(), revision: z.string() })) } },
96
+ put: { operationId: 'savePeersConfig', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: jsonSchema(ConfigEdit) } } }, responses: { '200': jsonResponse('Saved peer configuration; restart required', z.object({ revision: z.string(), activation_required: z.boolean() })), '409': { description: 'Revision conflict' } } } },
97
+ '/api/v1/peers/validate': { post: { operationId: 'validatePeersConfig', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: jsonSchema(z.object({ text: z.string().min(1).max(200000) }).strict()) } } }, responses: { '200': jsonResponse('Validated peer count', z.object({ valid: z.boolean(), peers: z.number().int() })), '400': { description: 'Invalid peer configuration' } } } },
98
+ '/api/v1/peers/{id}/test': { post: { operationId: 'testPeer', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'Peer card discovery result' }, '503': { description: 'Peer unavailable' } } } },
99
+ '/api/v1/skills': { get: { operationId: 'listSkills', security: [{ bearerAuth: [] }], responses: { '200': { description: 'Profile skill index' } } } },
100
+ '/api/v1/skills/{name}': { put: { operationId: 'setSkillEnabled', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'name', required: true, schema: { type: 'string' } }], requestBody: { required: true, content: { 'application/json': { schema: toJsonSchema(SkillEdit, 'openapi-3.0') } } }, responses: { '200': { description: 'Saved skill selection revision' }, '409': { description: 'Revision conflict' } } } },
101
+ '/api/v1/memory/search': { post: { operationId: 'searchMemory', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] } } } }, responses: { '200': { description: 'Permitted memories' }, '503': { description: 'HiveMem unavailable' } } } },
102
+ '/api/v1/memory/status': { get: { operationId: 'getMemoryStatus', security: [{ bearerAuth: [] }], responses: { '200': jsonResponse('HiveMem readiness and capability summary', z.object({ status: z.enum(['ready', 'degraded']), agent_id: z.string(), workspace_id: z.string(), retrieval: z.string().nullable(), storage: z.string().nullable(), reason: z.string().nullable() })) } } }
103
+ }
104
+ };
105
+ }
106
+ export function createArdentApi(runtime, loaded, env = process.env, jobEngine) {
107
+ const app = new Hono();
108
+ let current = loaded;
109
+ // A saved configuration requires activation; it must not rotate the live
110
+ // management credential before the gateway restarts with it.
111
+ const apiTokenEnv = loaded.config.api.token_env;
112
+ // Readiness spawns Codex. Cache one result briefly and share an in-flight probe.
113
+ let codexReadiness;
114
+ let codexProbe;
115
+ const probeCodex = async () => {
116
+ try {
117
+ await assertCodexProtocol(runtime.config.providers.codex.command, env);
118
+ const server = new CodexAppServer(runtime.config.providers.codex.command, runtime.profileDir, env);
119
+ try {
120
+ await server.initialize();
121
+ const auth = await server.accountStatus();
122
+ return auth === runtime.config.providers.codex.auth_mode ? null : auth === 'login-required' ? 'Codex login required' : 'Codex account uses the wrong auth mode';
123
+ }
124
+ finally {
125
+ server.close();
126
+ }
127
+ }
128
+ catch (error) {
129
+ return error instanceof Error ? error.message : 'Codex App Server unavailable';
130
+ }
131
+ };
132
+ const codexReadinessProblem = async () => {
133
+ if (codexReadiness && Date.now() - codexReadiness.at < 30_000)
134
+ return codexReadiness.problem;
135
+ codexProbe ??= probeCodex().then(problem => { codexReadiness = { at: Date.now(), problem }; return problem; })
136
+ .finally(() => { codexProbe = undefined; });
137
+ return codexProbe;
138
+ };
139
+ app.onError((error, c) => {
140
+ const message = error instanceof Error ? error.message : 'Request failed';
141
+ const status = /revision conflict|Idempotency key reused|already in progress/i.test(message) ? 409
142
+ : /not found|absent/i.test(message) ? 404
143
+ : /unavailable|stale|unconfigured|missing|login required|wrong auth mode|CODEX_LOGIN_REQUIRED|CODEX_WRONG_AUTH_MODE|fetch failed|ECONNREFUSED|ETIMEDOUT|timeout/i.test(message) ? 503
144
+ : error instanceof z.ZodError || error instanceof SyntaxError || /invalid|must be|exceeds|requires/i.test(message) ? 400 : 500;
145
+ const code = status === 409 ? 'CONFLICT' : status === 404 ? 'NOT_FOUND' : status === 503 ? 'DEPENDENCY_UNAVAILABLE' : status === 400 ? 'INVALID_REQUEST' : 'INTERNAL_ERROR';
146
+ return c.json({ error: { code, message: status === 500 ? 'Internal request error' : message.slice(0, 500) } }, status);
147
+ });
148
+ app.get('/health/live', c => c.json({ status: 'live' }));
149
+ app.get('/health/ready', async (c) => {
150
+ const problems = runtimeProblems(runtime.config, env);
151
+ if (!problems.length && runtime.config.execution.backend === 'codex') {
152
+ const problem = await codexReadinessProblem();
153
+ if (problem)
154
+ problems.push(problem);
155
+ }
156
+ if (!problems.length && runtime.config.execution.backend === 'llamacpp') {
157
+ const base = resolveReference(runtime.config.providers.llamacpp.base_url, env);
158
+ if (base) {
159
+ try {
160
+ const token = env[runtime.config.providers.llamacpp.api_key_env];
161
+ const response = await fetch(llamaEndpoint(base, 'models'), { headers: token ? { Authorization: `Bearer ${token}` } : {}, signal: AbortSignal.timeout(2000), redirect: 'manual' });
162
+ if (!response.ok)
163
+ problems.push(`llama.cpp model discovery HTTP ${response.status}`);
164
+ }
165
+ catch {
166
+ problems.push('llama.cpp model endpoint is unavailable');
167
+ }
168
+ }
169
+ }
170
+ return c.json({ status: problems.length ? 'degraded' : 'ready', problems }, problems.length ? 503 : 200);
171
+ });
172
+ app.get('/openapi.json', c => c.json(ardentOpenApi()));
173
+ app.use('/api/v1/*', async (c, next) => {
174
+ const expected = env[apiTokenEnv];
175
+ const supplied = c.req.header('Authorization')?.replace(/^Bearer /, '') ?? '';
176
+ if (!expected || !authEqual(supplied, expected))
177
+ return c.json({ error: { code: 'UNAUTHORIZED', message: 'Bearer token required' } }, 401);
178
+ return next();
179
+ });
180
+ app.get('/api/v1/status', c => c.json({ profile: runtime.config.profile, backend: runtime.config.execution.backend, tasks: runtime.store.list(10).length }));
181
+ app.get('/api/v1/jobs/definitions/status', async (c) => {
182
+ if (!jobEngine)
183
+ throw new Error('Job engine is unavailable');
184
+ const status = await jobEngine.status();
185
+ return c.json({ profile: runtime.config.profile, revision: status.active_revision,
186
+ sha256: status.active_sha256, ...status });
187
+ });
188
+ app.get('/api/v1/profile', c => c.json({ profile: runtime.config.profile, name: runtime.config.name, product: runtime.config.product, timezone: runtime.config.timezone, path: runtime.profileDir }));
189
+ app.get('/api/v1/config', c => c.json({ revision: current.revision, text: current.text }));
190
+ app.get('/api/v1/config/schema', c => c.json(toJsonSchema(ConfigSchema)));
191
+ app.post('/api/v1/config/validate', async (c) => {
192
+ const input = parseJson(await c.req.json(), z.object({ text: z.string().min(1).max(200000) }).strict());
193
+ const config = parseConfig(input.text);
194
+ return c.json({ valid: true, profile: config.profile, runtime_problems: runtimeProblems(config, env) });
195
+ });
196
+ app.put('/api/v1/config', async (c) => {
197
+ const edit = parseJson(await c.req.json(), ConfigEdit);
198
+ const revision = await saveConfig(runtime.profileDir, edit.text, edit.revision);
199
+ current = await loadConfig(runtime.profileDir);
200
+ return c.json({ revision, activation_required: true });
201
+ });
202
+ app.get('/api/v1/skills', async (c) => c.json({ skills: await listSkills(runtime.profileDir, runtime.config) }));
203
+ app.get('/api/v1/identity/:kind', async (c) => {
204
+ const kind = z.enum(['soul', 'identity']).parse(c.req.param('kind'));
205
+ return c.json(await readIdentity(runtime.profileDir, runtime.config, kind));
206
+ });
207
+ app.put('/api/v1/identity/:kind', async (c) => {
208
+ const kind = z.enum(['soul', 'identity']).parse(c.req.param('kind'));
209
+ const edit = parseJson(await c.req.json(), IdentityEdit);
210
+ return c.json(await writeIdentity(runtime.profileDir, runtime.config, kind, edit.text, edit.revision));
211
+ });
212
+ app.get('/api/v1/identity/:kind/history', async (c) => {
213
+ const kind = z.enum(['soul', 'identity']).parse(c.req.param('kind'));
214
+ return c.json({ history: await identityHistory(runtime.profileDir, kind) });
215
+ });
216
+ app.post('/api/v1/identity/:kind/restore', async (c) => {
217
+ const kind = z.enum(['soul', 'identity']).parse(c.req.param('kind'));
218
+ const input = parseJson(await c.req.json(), IdentityRestore);
219
+ return c.json(await restoreIdentity(runtime.profileDir, runtime.config, kind, input.history_name, input.revision));
220
+ });
221
+ app.get('/api/v1/providers', async (c) => {
222
+ let codex = 'unconfigured';
223
+ try {
224
+ await assertCodexProtocol(runtime.config.providers.codex.command, env);
225
+ const server = new CodexAppServer(runtime.config.providers.codex.command, runtime.profileDir, env);
226
+ try {
227
+ await server.initialize();
228
+ codex = await server.accountStatus();
229
+ }
230
+ finally {
231
+ server.close();
232
+ }
233
+ }
234
+ catch (error) {
235
+ codex = error instanceof Error ? error.message : 'unavailable';
236
+ }
237
+ return c.json({ selected: runtime.config.execution.backend, llamacpp: { base_url: runtime.config.providers.llamacpp.base_url, model: runtime.config.providers.llamacpp.model }, codex: { model: runtime.config.providers.codex.model, auth_mode: codex } });
238
+ });
239
+ app.post('/api/v1/providers/test', async (c) => {
240
+ if (runtime.config.execution.backend === 'codex') {
241
+ await assertCodexProtocol(runtime.config.providers.codex.command, env);
242
+ const server = new CodexAppServer(runtime.config.providers.codex.command, runtime.profileDir, env);
243
+ try {
244
+ await server.initialize();
245
+ const auth = await server.accountStatus();
246
+ if (auth !== 'chatgpt')
247
+ throw new Error(auth === 'login-required' ? 'Codex ChatGPT login required' : 'Codex account uses the wrong auth mode');
248
+ return c.json({ backend: 'codex', auth_mode: auth });
249
+ }
250
+ finally {
251
+ server.close();
252
+ }
253
+ }
254
+ const result = await new LlamaCppBackend(runtime.config.providers.llamacpp, env).complete([{ role: 'user', content: 'Reply with READY.' }], [], AbortSignal.timeout(20000), false);
255
+ return c.json({ backend: 'llamacpp', answer: result.content, usage: result.usage ?? null });
256
+ });
257
+ app.get('/api/v1/channels', c => c.json({ telegram: { enabled: runtime.config.channels.telegram.enabled, token_configured: Boolean(env[runtime.config.channels.telegram.token_env]), allowed_user_count: runtime.config.channels.telegram.allowed_user_ids.length, allowed_chat_count: runtime.config.channels.telegram.allowed_chat_ids.length }, mattermost: { enabled: runtime.config.channels.mattermost.enabled, token_configured: Boolean(env[runtime.config.channels.mattermost.token_env]), allowed_user_count: runtime.config.channels.mattermost.allowed_user_ids.length, allowed_channel_count: runtime.config.channels.mattermost.allowed_channel_ids.length } }));
258
+ app.post('/api/v1/channels/:name/test', async (c) => {
259
+ const name = z.enum(['telegram', 'mattermost']).parse(c.req.param('name'));
260
+ if (name === 'telegram') {
261
+ const result = await new TelegramAdapter(runtime, runtime.config.channels.telegram, env).initialize(AbortSignal.timeout(10000));
262
+ return c.json({ name, status: 'reachable', bot: result, receive_mode: 'polling', polling_started: false });
263
+ }
264
+ const result = await new MattermostAdapter(runtime, runtime.config.channels.mattermost, env).initialize();
265
+ return c.json({ name, status: 'reachable', bot: result, socket_started: false });
266
+ });
267
+ app.get('/api/v1/peers', async (c) => c.json({ peers: (await listPeers(runtime.profileDir, runtime.config)).map(peer => ({ ...peer,
268
+ inbound_credential_configured: Boolean(env[inboundTokenEnv(peer)]), outbound_credential_configured: Boolean(env[outboundTokenEnv(peer)]),
269
+ credential_configured: Boolean(env[inboundTokenEnv(peer)] && env[outboundTokenEnv(peer)]) })) }));
270
+ app.get('/api/v1/peers/config', async (c) => c.json(await readPeersConfig(runtime.profileDir, runtime.config)));
271
+ app.post('/api/v1/peers/validate', async (c) => {
272
+ const input = parseJson(await c.req.json(), z.object({ text: z.string().min(1).max(200000) }).strict());
273
+ return c.json({ valid: true, peers: parsePeersConfig(input.text).length });
274
+ });
275
+ app.put('/api/v1/peers/config', async (c) => {
276
+ const input = parseJson(await c.req.json(), ConfigEdit);
277
+ return c.json({ revision: await savePeersConfig(runtime.profileDir, runtime.config, input.text, input.revision), activation_required: true });
278
+ });
279
+ app.post('/api/v1/peers/:id/test', async (c) => {
280
+ const { card } = await new ArdentA2AClient(runtime.profileDir, runtime.config, env).open(c.req.param('id'));
281
+ return c.json({ peer: c.req.param('id'), status: 'reachable', card });
282
+ });
283
+ app.put('/api/v1/skills/:name', async (c) => {
284
+ const edit = parseJson(await c.req.json(), SkillEdit);
285
+ const revision = await setSkillEnabled(runtime.profileDir, c.req.param('name'), edit.enabled, edit.revision);
286
+ current = await loadConfig(runtime.profileDir);
287
+ return c.json({ revision, activation_required: true });
288
+ });
289
+ app.post('/api/v1/memory/search', async (c) => {
290
+ const input = parseJson(await c.req.json(), z.object({ query: z.string().trim().min(1).max(1000) }).strict());
291
+ if (runtime.config.memory.provider !== 'hivemem')
292
+ throw new Error('Memory provider is unconfigured');
293
+ const client = new HiveMemClient(runtime.config.memory, env);
294
+ return c.json({ results: await client.search(input.query, 'private') });
295
+ });
296
+ app.get('/api/v1/memory/status', async (c) => {
297
+ const memory = runtime.config.memory;
298
+ if (memory.provider !== 'hivemem')
299
+ return c.json({ status: 'degraded', agent_id: '', workspace_id: '', retrieval: null, storage: null,
300
+ reason: 'Memory provider is not configured (memory.provider: none)' });
301
+ try {
302
+ const client = new HiveMemClient(runtime.config.memory, env);
303
+ const capabilities = await client.capabilities();
304
+ return c.json({ status: 'ready', agent_id: client.agentId, workspace_id: client.workspaceId,
305
+ retrieval: typeof capabilities.retrieval === 'string' ? capabilities.retrieval : null,
306
+ storage: typeof capabilities.storage === 'string' ? capabilities.storage : null, reason: null });
307
+ }
308
+ catch (error) {
309
+ return c.json({ status: 'degraded', agent_id: memory.agent_id,
310
+ workspace_id: memory.workspace_id, retrieval: null, storage: null,
311
+ reason: error instanceof Error ? error.message.slice(0, 200) : 'HiveMem unavailable' });
312
+ }
313
+ });
314
+ app.get('/api/v1/sessions', c => c.json({ sessions: runtime.store.listSessions('management', 'direct') }));
315
+ app.post('/api/v1/sessions', async (c) => {
316
+ const input = parseJson(await c.req.json(), SessionOpen);
317
+ return c.json(input.reset ? runtime.store.resetSession('management', 'direct', input.key) : runtime.store.session('management', 'direct', input.key));
318
+ });
319
+ app.get('/api/v1/sessions/:id/messages', c => c.json({ messages: runtime.store.sessionMessages(c.req.param('id'), 'management') }));
320
+ app.post('/api/v1/tasks', async (c) => {
321
+ const input = parseJson(await c.req.json(), Submit);
322
+ const task = runtime.accept(input.prompt, 'management', 'direct', input.idempotency_key, input.session_id);
323
+ if (task.state === 'queued')
324
+ void runtime.run(task.id, env).catch(() => { });
325
+ return c.json({ task_id: task.id, state: task.state }, 202);
326
+ });
327
+ app.get('/api/v1/tasks', c => c.json({ tasks: runtime.store.list(PageLimit.parse(c.req.query('limit') ?? 50)) }));
328
+ app.get('/api/v1/tasks/:id', c => {
329
+ const task = runtime.store.get(c.req.param('id'));
330
+ if (!task)
331
+ return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
332
+ return c.json(task);
333
+ });
334
+ app.get('/api/v1/tasks/:id/events', c => {
335
+ const id = c.req.param('id');
336
+ if (!runtime.store.get(id))
337
+ return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
338
+ return c.json({ events: runtime.store.events(id, EventCursor.parse(c.req.query('after') ?? 0)) });
339
+ });
340
+ app.get('/api/v1/tasks/:id/stream', c => {
341
+ const id = c.req.param('id');
342
+ if (!runtime.store.get(id))
343
+ return c.json({ error: { code: 'NOT_FOUND', message: 'Task not found' } }, 404);
344
+ let after = EventCursor.parse(c.req.query('after') ?? c.req.header('Last-Event-ID') ?? 0);
345
+ return streamSSE(c, async (stream) => {
346
+ const until = Date.now() + 60_000;
347
+ while (!stream.aborted && Date.now() < until) {
348
+ const events = runtime.store.events(id, after, 100);
349
+ for (const event of events) {
350
+ await stream.writeSSE({ event: 'task.event', id: String(event.sequence), data: JSON.stringify(event) });
351
+ after = event.sequence;
352
+ }
353
+ const task = runtime.store.get(id);
354
+ if (!task || ['completed', 'partial', 'failed', 'cancelled', 'unknown'].includes(task.state)) {
355
+ await stream.writeSSE({ event: 'task.done', data: JSON.stringify(task ?? { id, state: 'missing' }) });
356
+ break;
357
+ }
358
+ await stream.sleep(250);
359
+ }
360
+ });
361
+ });
362
+ app.post('/api/v1/tasks/:id/cancel', c => c.json(runtime.cancel(c.req.param('id'))));
363
+ app.get('/api/v1/artifacts/:id', async (c) => {
364
+ try {
365
+ return c.body(new Uint8Array(await runtime.artifacts.get(c.req.param('id'))), 200, { 'Content-Type': 'application/octet-stream', 'X-Content-Type-Options': 'nosniff' });
366
+ }
367
+ catch {
368
+ return c.json({ error: { code: 'NOT_FOUND', message: 'Artifact not found' } }, 404);
369
+ }
370
+ });
371
+ // Plugin routes mount after the bearer-token middleware above.
372
+ const ctx = pluginContext(runtime, env);
373
+ for (const plugin of runtime.plugins) {
374
+ if (!plugin.routes)
375
+ continue;
376
+ const sub = new Hono();
377
+ plugin.routes(sub, ctx);
378
+ app.route(`/api/v1/plugins/${plugin.name}`, sub);
379
+ }
380
+ return app;
381
+ }
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+ export declare const ArtifactSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ sha256: z.ZodString;
5
+ bytes: z.ZodNumber;
6
+ media_type: z.ZodString;
7
+ name: z.ZodString;
8
+ }, z.core.$strict>;
9
+ export type Artifact = z.infer<typeof ArtifactSchema>;
10
+ export declare class ArtifactStore {
11
+ readonly directory: string;
12
+ constructor(directory: string);
13
+ put(name: string, mediaType: string, bytes: Uint8Array): Promise<Artifact>;
14
+ get(id: string): Promise<Uint8Array>;
15
+ }
@@ -0,0 +1,41 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { constants } from 'node:fs';
3
+ import { mkdir, open, rename, writeFile } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { z } from 'zod';
6
+ export const ArtifactSchema = z.object({ id: z.string().regex(/^[a-f0-9]{64}$/), sha256: z.string().regex(/^[a-f0-9]{64}$/), bytes: z.number().int().nonnegative(), media_type: z.string(), name: z.string() }).strict();
7
+ export class ArtifactStore {
8
+ directory;
9
+ constructor(directory) {
10
+ this.directory = directory;
11
+ }
12
+ async put(name, mediaType, bytes) {
13
+ if (!/^[A-Za-z0-9._-]{1,128}$/.test(name))
14
+ throw new Error('Invalid artifact name');
15
+ if (bytes.length > 2 * 1024 * 1024)
16
+ throw new Error('Artifact exceeds 2 MiB');
17
+ const digest = createHash('sha256').update(bytes).digest('hex');
18
+ await mkdir(this.directory, { recursive: true, mode: 0o700 });
19
+ const temporary = join(this.directory, `.${digest}.${process.pid}.${Date.now()}`);
20
+ await writeFile(temporary, bytes, { mode: 0o600, flag: 'wx' });
21
+ await rename(temporary, join(this.directory, digest));
22
+ return { id: digest, sha256: digest, bytes: bytes.length, media_type: mediaType, name };
23
+ }
24
+ async get(id) {
25
+ if (!/^[a-f0-9]{64}$/.test(id))
26
+ throw new Error('Invalid artifact ID');
27
+ const file = await open(join(this.directory, id), constants.O_RDONLY | constants.O_NOFOLLOW);
28
+ try {
29
+ const stat = await file.stat();
30
+ if (!stat.isFile() || stat.size > 2 * 1024 * 1024)
31
+ throw new Error('Artifact file is unsafe');
32
+ const bytes = await file.readFile();
33
+ if (createHash('sha256').update(bytes).digest('hex') !== id)
34
+ throw new Error('Artifact bytes differ from their ID');
35
+ return bytes;
36
+ }
37
+ finally {
38
+ await file.close();
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,49 @@
1
+ import { z } from 'zod';
2
+ export declare const BackendEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
3
+ kind: z.ZodLiteral<"started">;
4
+ backend: z.ZodEnum<{
5
+ llamacpp: "llamacpp";
6
+ codex: "codex";
7
+ }>;
8
+ }, z.core.$strict>, z.ZodObject<{
9
+ kind: z.ZodLiteral<"text_delta">;
10
+ backend: z.ZodEnum<{
11
+ llamacpp: "llamacpp";
12
+ codex: "codex";
13
+ }>;
14
+ text: z.ZodString;
15
+ }, z.core.$strict>, z.ZodObject<{
16
+ kind: z.ZodLiteral<"tool_result">;
17
+ backend: z.ZodEnum<{
18
+ llamacpp: "llamacpp";
19
+ codex: "codex";
20
+ }>;
21
+ name: z.ZodString;
22
+ result: z.ZodDiscriminatedUnion<[z.ZodObject<{
23
+ ok: z.ZodLiteral<true>;
24
+ value: z.ZodOptional<z.ZodUnknown>;
25
+ }, z.core.$strict>, z.ZodObject<{
26
+ ok: z.ZodLiteral<false>;
27
+ code: z.ZodString;
28
+ message: z.ZodString;
29
+ effect: z.ZodEnum<{
30
+ none: "none";
31
+ unknown: "unknown";
32
+ }>;
33
+ }, z.core.$strict>], "ok">;
34
+ }, z.core.$strict>, z.ZodObject<{
35
+ kind: z.ZodLiteral<"completed">;
36
+ backend: z.ZodEnum<{
37
+ llamacpp: "llamacpp";
38
+ codex: "codex";
39
+ }>;
40
+ output_chars: z.ZodNumber;
41
+ tool_calls: z.ZodNumber;
42
+ reasoning_chars: z.ZodNumber;
43
+ usage: z.ZodNullable<z.ZodObject<{
44
+ promptTokens: z.ZodOptional<z.ZodNumber>;
45
+ completionTokens: z.ZodOptional<z.ZodNumber>;
46
+ }, z.core.$strip>>;
47
+ }, z.core.$strict>], "kind">;
48
+ export type BackendEvent = z.infer<typeof BackendEventSchema>;
49
+ export type BackendEventSink = (event: BackendEvent) => void;
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+ import { ToolResultSchema } from './tools.js';
3
+ export const BackendEventSchema = z.discriminatedUnion('kind', [
4
+ z.object({ kind: z.literal('started'), backend: z.enum(['llamacpp', 'codex']) }).strict(),
5
+ z.object({ kind: z.literal('text_delta'), backend: z.enum(['llamacpp', 'codex']), text: z.string() }).strict(),
6
+ z.object({ kind: z.literal('tool_result'), backend: z.enum(['llamacpp', 'codex']), name: z.string(), result: ToolResultSchema }).strict(),
7
+ z.object({ kind: z.literal('completed'), backend: z.enum(['llamacpp', 'codex']), output_chars: z.number().int().nonnegative(), tool_calls: z.number().int().nonnegative(), reasoning_chars: z.number().int().nonnegative(), usage: z.object({ promptTokens: z.number().optional(), completionTokens: z.number().optional() }).nullable() }).strict()
8
+ ]);
@@ -0,0 +1,52 @@
1
+ import { z } from 'zod';
2
+ import type { ArdentRuntime } from './runtime.js';
3
+ import type { JobEngine } from './job-engine.js';
4
+ export declare const ChannelEvent: z.ZodObject<{
5
+ platform: z.ZodEnum<{
6
+ telegram: "telegram";
7
+ mattermost: "mattermost";
8
+ }>;
9
+ account: z.ZodString;
10
+ channel_type: z.ZodOptional<z.ZodEnum<{
11
+ telegram_private: "telegram_private";
12
+ telegram_group: "telegram_group";
13
+ telegram_supergroup: "telegram_supergroup";
14
+ mattermost_dm: "mattermost_dm";
15
+ mattermost_channel: "mattermost_channel";
16
+ }>>;
17
+ user: z.ZodString;
18
+ conversation: z.ZodString;
19
+ thread: z.ZodNullable<z.ZodString>;
20
+ update_id: z.ZodString;
21
+ reply_to: z.ZodNullable<z.ZodString>;
22
+ text: z.ZodString;
23
+ attachments: z.ZodArray<z.ZodObject<{
24
+ name: z.ZodString;
25
+ media_type: z.ZodString;
26
+ bytes: z.ZodOptional<z.ZodInstanceOf<Uint8Array<ArrayBuffer>>>;
27
+ external_id: z.ZodOptional<z.ZodString>;
28
+ }, z.core.$strict>>;
29
+ authorized: z.ZodLiteral<true>;
30
+ bot_message: z.ZodBoolean;
31
+ }, z.core.$strict>;
32
+ export type ChannelEvent = z.infer<typeof ChannelEvent>;
33
+ export type ChannelReply = {
34
+ text: string;
35
+ taskId: string | null;
36
+ artifactIds: string[];
37
+ };
38
+ export type ChannelDelivery = (reply: ChannelReply) => Promise<string | null>;
39
+ export declare class DeliveryRejectedError extends Error {
40
+ }
41
+ export declare class ChannelCore {
42
+ #private;
43
+ readonly runtime: ArdentRuntime;
44
+ readonly env: NodeJS.ProcessEnv;
45
+ readonly jobs?: JobEngine | undefined;
46
+ constructor(runtime: ArdentRuntime, env?: NodeJS.ProcessEnv, jobs?: JobEngine | undefined);
47
+ handle(raw: unknown, deliver: ChannelDelivery): Promise<{
48
+ duplicate: boolean;
49
+ taskId: string | null;
50
+ delivery: string | null;
51
+ }>;
52
+ }