@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
@@ -0,0 +1,115 @@
1
+ import { createRequire } from 'node:module';
2
+ import { isAbsolute, join } from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { defineTool } from './tools.js';
5
+ const pluginName = /^[a-z][a-z0-9-]{0,63}$/;
6
+ const registered = [];
7
+ /** Identity helper that type-checks a plugin definition. */
8
+ export function definePlugin(plugin) {
9
+ return plugin;
10
+ }
11
+ function isRecord(value) {
12
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
13
+ }
14
+ function assertPlugin(plugin, source) {
15
+ if (!isRecord(plugin) || typeof plugin.name !== 'string' || !pluginName.test(plugin.name))
16
+ throw new Error(`Plugin ${source} must export an object with a lowercase name`);
17
+ for (const hook of ['registerTools', 'routes', 'jobHandlers', 'jobOwnership', 'peerTool'])
18
+ if (plugin[hook] !== undefined && typeof plugin[hook] !== 'function')
19
+ throw new Error(`Plugin ${plugin.name} hook ${hook} must be a function`);
20
+ if (plugin.cliCommands !== undefined) {
21
+ if (!isRecord(plugin.cliCommands))
22
+ throw new Error(`Plugin ${plugin.name} cliCommands must be an object`);
23
+ for (const [name, command] of Object.entries(plugin.cliCommands))
24
+ if (!pluginName.test(name) || !isRecord(command) || typeof command.run !== 'function')
25
+ throw new Error(`Plugin ${plugin.name} CLI command ${name} is invalid`);
26
+ }
27
+ }
28
+ function validate(plugin, source) {
29
+ assertPlugin(plugin, source);
30
+ return plugin;
31
+ }
32
+ /** Register a plugin programmatically (for embedders that construct Ardent themselves). */
33
+ export function registerPlugin(plugin) {
34
+ const valid = validate(plugin, 'registerPlugin');
35
+ if (registered.some(item => item.name === valid.name))
36
+ throw new Error(`Duplicate plugin: ${valid.name}`);
37
+ registered.push(valid);
38
+ }
39
+ /** Programmatically registered plugins. */
40
+ export function registeredPlugins() { return [...registered]; }
41
+ /** Remove programmatic registrations (tests and embedders). */
42
+ export function clearRegisteredPlugins() { registered.length = 0; }
43
+ async function importSpecifier(specifier, profileDir) {
44
+ if (specifier.startsWith('file://'))
45
+ return import(specifier);
46
+ if (isAbsolute(specifier))
47
+ return import(pathToFileURL(specifier).href);
48
+ // Resolve a package from the profile directory first, so a plugin may be
49
+ // installed next to the profile rather than next to Ardent itself.
50
+ try {
51
+ const resolved = createRequire(join(profileDir, 'package.json')).resolve(specifier);
52
+ return await import(pathToFileURL(resolved).href);
53
+ }
54
+ catch (error) {
55
+ if (!(error && typeof error === 'object' && 'code' in error && error.code === 'MODULE_NOT_FOUND'))
56
+ throw error;
57
+ return import(specifier);
58
+ }
59
+ }
60
+ /**
61
+ * Load the plugins named by `config.plugins`, plus any registered
62
+ * programmatically. A module's default export (or named `plugin` export) may
63
+ * be one plugin or an array of plugins.
64
+ */
65
+ export async function loadPlugins(config, profileDir) {
66
+ const plugins = [...registered];
67
+ for (const specifier of config.plugins) {
68
+ const module = await importSpecifier(specifier, profileDir);
69
+ if (!isRecord(module))
70
+ throw new Error(`Plugin ${specifier} did not load as a module`);
71
+ const exported = module.default ?? module.plugin;
72
+ for (const item of Array.isArray(exported) ? exported : [exported]) {
73
+ const plugin = validate(item, specifier);
74
+ if (plugins.some(existing => existing.name === plugin.name))
75
+ throw new Error(`Duplicate plugin: ${plugin.name}`);
76
+ plugins.push(plugin);
77
+ }
78
+ }
79
+ return plugins;
80
+ }
81
+ /**
82
+ * Context for offline job-definition validation (`ardent jobs definitions
83
+ * validate`). No runtime exists there, so `runtime` and `tools` throw; a
84
+ * plugin's `jobHandlers` must only touch them inside `run`.
85
+ */
86
+ class ValidationContext {
87
+ config;
88
+ profileDir;
89
+ env;
90
+ defineTool = defineTool;
91
+ constructor(config, profileDir, env) {
92
+ this.config = config;
93
+ this.profileDir = profileDir;
94
+ this.env = env;
95
+ }
96
+ get runtime() { throw new Error('The Ardent runtime is unavailable during offline validation'); }
97
+ get tools() { throw new Error('The tool registry is unavailable during offline validation'); }
98
+ }
99
+ export function validationContext(config, profileDir, env) {
100
+ return new ValidationContext(config, profileDir, env);
101
+ }
102
+ export function pluginContext(runtime, env) {
103
+ return { runtime, config: runtime.config, profileDir: runtime.profileDir, env, tools: runtime.tools, defineTool };
104
+ }
105
+ /** Merge plugin job handlers into a base registry; a duplicate handler ID is an error. */
106
+ export function pluginJobHandlers(plugins, ctx, base = {}) {
107
+ const registry = { ...base };
108
+ for (const plugin of plugins)
109
+ for (const [id, handler] of Object.entries(plugin.jobHandlers?.(ctx) ?? {})) {
110
+ if (id in registry)
111
+ throw new Error(`Duplicate job handler ${id} from plugin ${plugin.name}`);
112
+ registry[id] = handler;
113
+ }
114
+ return registry;
115
+ }
@@ -0,0 +1,110 @@
1
+ import { z } from 'zod';
2
+ export declare const ResearchBriefSchema: z.ZodObject<{
3
+ question: z.ZodString;
4
+ scope: z.ZodString;
5
+ as_of: z.ZodString;
6
+ findings: z.ZodArray<z.ZodString>;
7
+ claims: z.ZodArray<z.ZodObject<{
8
+ text: z.ZodString;
9
+ source_ids: z.ZodArray<z.ZodString>;
10
+ qualification: z.ZodString;
11
+ }, z.core.$strip>>;
12
+ contrary_evidence: z.ZodArray<z.ZodString>;
13
+ coverage_gaps: z.ZodArray<z.ZodString>;
14
+ artifact_ids: z.ZodArray<z.ZodString>;
15
+ answer_markdown: z.ZodString;
16
+ source_assessments: z.ZodOptional<z.ZodArray<z.ZodObject<{
17
+ source_id: z.ZodString;
18
+ reporting_role: z.ZodEnum<{
19
+ unknown: "unknown";
20
+ original: "original";
21
+ secondary: "secondary";
22
+ }>;
23
+ basis: z.ZodString;
24
+ }, z.core.$strip>>>;
25
+ source_register: z.ZodOptional<z.ZodArray<z.ZodObject<{
26
+ source_id: z.ZodString;
27
+ url: z.ZodString;
28
+ evidence_kind: z.ZodEnum<{
29
+ search_snippet: "search_snippet";
30
+ extracted_article: "extracted_article";
31
+ }>;
32
+ reporting_role: z.ZodEnum<{
33
+ unknown: "unknown";
34
+ original: "original";
35
+ secondary: "secondary";
36
+ }>;
37
+ role_basis: z.ZodNullable<z.ZodString>;
38
+ title: z.ZodString;
39
+ published_at: z.ZodNullable<z.ZodString>;
40
+ retrieved_at: z.ZodNullable<z.ZodString>;
41
+ content_sha256: z.ZodNullable<z.ZodString>;
42
+ }, z.core.$strip>>>;
43
+ candidate_slate: z.ZodOptional<z.ZodArray<z.ZodObject<{
44
+ source_id: z.ZodString;
45
+ headline: z.ZodString;
46
+ summary: z.ZodString;
47
+ why_positive: z.ZodString;
48
+ topic: z.ZodString;
49
+ location: z.ZodString;
50
+ scores: z.ZodObject<{
51
+ recency: z.ZodNumber;
52
+ reliability: z.ZodNumber;
53
+ impact: z.ZodNumber;
54
+ specificity: z.ZodNumber;
55
+ constructive_value: z.ZodNumber;
56
+ user_relevance: z.ZodNumber;
57
+ }, z.core.$strict>;
58
+ }, z.core.$strict>>>;
59
+ syndication_groups: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodString>>>;
60
+ team_result: z.ZodOptional<z.ZodObject<{
61
+ version: z.ZodLiteral<"1.0">;
62
+ mission_id: z.ZodString;
63
+ mission_revision: z.ZodNumber;
64
+ task_id: z.ZodString;
65
+ outcome: z.ZodEnum<{
66
+ unknown: "unknown";
67
+ failed: "failed";
68
+ completed: "completed";
69
+ input_required: "input_required";
70
+ partial: "partial";
71
+ cancelled: "cancelled";
72
+ blocked: "blocked";
73
+ }>;
74
+ summary: z.ZodString;
75
+ checks: z.ZodArray<z.ZodObject<{
76
+ criterion: z.ZodString;
77
+ status: z.ZodEnum<{
78
+ unknown: "unknown";
79
+ pass: "pass";
80
+ fail: "fail";
81
+ }>;
82
+ evidence: z.ZodArray<z.ZodString>;
83
+ }, z.core.$strict>>;
84
+ artifacts: z.ZodArray<z.ZodObject<{
85
+ name: z.ZodString;
86
+ reference: z.ZodString;
87
+ sha256: z.ZodString;
88
+ }, z.core.$strict>>;
89
+ uncertain_effects: z.ZodArray<z.ZodString>;
90
+ clarification: z.ZodOptional<z.ZodObject<{
91
+ missing_fact: z.ZodString;
92
+ choices: z.ZodArray<z.ZodObject<{
93
+ id: z.ZodString;
94
+ label: z.ZodString;
95
+ }, z.core.$strict>>;
96
+ blocked_task: z.ZodString;
97
+ intended_respondent: z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<"operator">]>;
98
+ }, z.core.$strict>>;
99
+ }, z.core.$strict>>;
100
+ }, z.core.$strict>;
101
+ export type ResearchBrief = z.infer<typeof ResearchBriefSchema>;
102
+ /** Correct observed read-only formatting slips, then run the full research and
103
+ * team schemas. An aliased URL must match that source ID's retained evidence. */
104
+ export declare function repairObservedResearchFormat(text: string, sourceIds: ReadonlySet<string>, artifactIds: ReadonlySet<string>, sourceUrls: ReadonlySet<string>, sourceUrlById?: ReadonlyMap<string, string>): string | null;
105
+ /** Wrap only single-string research arrays after all other fields and citations validate. */
106
+ export declare function repairResearchArrayStrings(text: string, sourceIds: ReadonlySet<string>, artifactIds: ReadonlySet<string>): {
107
+ text: string;
108
+ fields: Array<'findings' | 'coverage_gaps'>;
109
+ } | null;
110
+ export declare function parseResearchBrief(text: string, sourceIds: ReadonlySet<string>, artifactIds: ReadonlySet<string>): ResearchBrief;
@@ -0,0 +1,164 @@
1
+ import { z } from 'zod';
2
+ import { TeamResult } from './team-contract.js';
3
+ export const ResearchBriefSchema = z.object({
4
+ question: z.string().min(1),
5
+ scope: z.string().min(1),
6
+ as_of: z.string().datetime({ offset: true }),
7
+ findings: z.array(z.string().min(1)).max(20),
8
+ claims: z.array(z.object({ text: z.string().min(1), source_ids: z.array(z.string().min(1)).min(1), qualification: z.string() })).max(50),
9
+ contrary_evidence: z.array(z.string()).max(20),
10
+ coverage_gaps: z.array(z.string()).max(20),
11
+ artifact_ids: z.array(z.string().regex(/^[a-f0-9]{64}$/)).max(20),
12
+ answer_markdown: z.string().min(1),
13
+ source_assessments: z.array(z.object({ source_id: z.string(), reporting_role: z.enum(['original', 'secondary', 'unknown']), basis: z.string().min(1).max(500) })).max(100).optional(),
14
+ source_register: z.array(z.object({ source_id: z.string(), url: z.string().url(), evidence_kind: z.enum(['search_snippet', 'extracted_article']), reporting_role: z.enum(['original', 'secondary', 'unknown']), role_basis: z.string().nullable(), title: z.string(), published_at: z.string().nullable(), retrieved_at: z.string().nullable(), content_sha256: z.string().nullable() })).max(100).optional(),
15
+ candidate_slate: z.array(z.object({ source_id: z.string().min(1), headline: z.string().trim().min(1).max(180),
16
+ summary: z.string().trim().min(1).max(600), why_positive: z.string().trim().min(1).max(240),
17
+ topic: z.string().trim().min(1).max(80), location: z.string().trim().min(1).max(100),
18
+ scores: z.object({ recency: z.number().int().min(1).max(5), reliability: z.number().int().min(1).max(5),
19
+ impact: z.number().int().min(1).max(5), specificity: z.number().int().min(1).max(5),
20
+ constructive_value: z.number().int().min(1).max(5), user_relevance: z.number().int().min(1).max(5) }).strict() }).strict()).min(1).max(20).optional(),
21
+ syndication_groups: z.array(z.array(z.string()).min(2)).max(50).optional(),
22
+ team_result: TeamResult.optional()
23
+ }).strict();
24
+ /** Correct observed read-only formatting slips, then run the full research and
25
+ * team schemas. An aliased URL must match that source ID's retained evidence. */
26
+ export function repairObservedResearchFormat(text, sourceIds, artifactIds, sourceUrls, sourceUrlById) {
27
+ let raw;
28
+ try {
29
+ raw = JSON.parse(text);
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ const object = z.record(z.string(), z.unknown()).safeParse(raw);
35
+ if (!object.success || Buffer.byteLength(text, 'utf8') > 64_000)
36
+ return null;
37
+ const candidate = { ...object.data };
38
+ let changed = false;
39
+ if (Array.isArray(candidate.source_register) && candidate.source_register.length <= 100) {
40
+ const normalized = [];
41
+ for (const entry of candidate.source_register) {
42
+ const source = z.record(z.string(), z.unknown()).safeParse(entry);
43
+ if (!source.success)
44
+ return null;
45
+ if (!('exact_url' in source.data)) {
46
+ normalized.push(entry);
47
+ continue;
48
+ }
49
+ const { exact_url, ...fields } = source.data;
50
+ if (typeof fields.source_id !== 'string' || typeof exact_url !== 'string' ||
51
+ sourceUrlById?.get(fields.source_id) !== exact_url ||
52
+ (fields.url !== undefined && fields.url !== exact_url))
53
+ return null;
54
+ normalized.push({ ...fields, url: exact_url });
55
+ changed = true;
56
+ }
57
+ candidate.source_register = normalized;
58
+ }
59
+ if (Array.isArray(candidate.findings) && candidate.findings.length <= 20) {
60
+ const finding = z.object({ source_url: z.string().url(), retrieved_at: z.string().datetime({ offset: true }),
61
+ publisher_date: z.string().max(500), event_date: z.string().max(500) }).strict();
62
+ const normalized = [];
63
+ for (const entry of candidate.findings) {
64
+ if (typeof entry === 'string') {
65
+ normalized.push(entry);
66
+ continue;
67
+ }
68
+ const parsed = finding.safeParse(entry);
69
+ if (!parsed.success || !sourceUrls.has(parsed.data.source_url))
70
+ return null;
71
+ normalized.push(JSON.stringify(parsed.data));
72
+ changed = true;
73
+ }
74
+ candidate.findings = normalized;
75
+ }
76
+ const team = z.record(z.string(), z.unknown()).safeParse(candidate.team_result);
77
+ if (team.success && Array.isArray(team.data.checks) && team.data.checks.length === 1) {
78
+ const check = z.record(z.string(), z.unknown()).safeParse(team.data.checks[0]);
79
+ if (check.success && typeof check.data.evidence === 'string') {
80
+ const reference = check.data.evidence;
81
+ if (!reference.startsWith('source:') || !sourceUrls.has(reference.slice(7)))
82
+ return null;
83
+ candidate.team_result = { ...team.data, checks: [{ ...check.data, evidence: [reference] }] };
84
+ changed = true;
85
+ }
86
+ }
87
+ if (!changed)
88
+ return null;
89
+ const corrected = JSON.stringify(candidate);
90
+ try {
91
+ parseResearchBrief(corrected, sourceIds, artifactIds);
92
+ }
93
+ catch {
94
+ return null;
95
+ }
96
+ return corrected;
97
+ }
98
+ /** Wrap only single-string research arrays after all other fields and citations validate. */
99
+ export function repairResearchArrayStrings(text, sourceIds, artifactIds) {
100
+ let raw;
101
+ try {
102
+ raw = JSON.parse(text);
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ const object = z.record(z.string(), z.unknown()).safeParse(raw);
108
+ if (!object.success)
109
+ return null;
110
+ const { coverage_gaps: gaps, ...other } = object.data;
111
+ if (typeof gaps !== 'string' || !gaps.trim() || Buffer.byteLength(gaps, 'utf8') > 2000)
112
+ return null;
113
+ const findings = other.findings;
114
+ if (typeof findings === 'string' && (!findings.trim() || Buffer.byteLength(findings, 'utf8') > 2000))
115
+ return null;
116
+ const fields = typeof findings === 'string'
117
+ ? ['findings', 'coverage_gaps'] : ['coverage_gaps'];
118
+ const normalized = typeof findings === 'string' ? { ...other, findings: [findings] } : other;
119
+ if (!ResearchBriefSchema.omit({ coverage_gaps: true }).safeParse(normalized).success)
120
+ return null;
121
+ const corrected = JSON.stringify({ ...normalized, coverage_gaps: [gaps] });
122
+ try {
123
+ parseResearchBrief(corrected, sourceIds, artifactIds);
124
+ }
125
+ catch {
126
+ return null;
127
+ }
128
+ return { text: corrected, fields };
129
+ }
130
+ export function parseResearchBrief(text, sourceIds, artifactIds) {
131
+ let value;
132
+ try {
133
+ value = JSON.parse(text);
134
+ }
135
+ catch {
136
+ throw new Error('Research result is not JSON');
137
+ }
138
+ if (typeof value === 'object' && value !== null && !Array.isArray(value) && 'findings' in value && typeof value.findings === 'string') {
139
+ // A single finding is unambiguous; keep citation and claim validation strict.
140
+ value = { ...value, findings: value.findings.trim() ? [value.findings] : [] };
141
+ }
142
+ const brief = ResearchBriefSchema.parse(value);
143
+ for (const claim of brief.claims)
144
+ for (const id of claim.source_ids)
145
+ if (!sourceIds.has(id))
146
+ throw new Error(`Research claim cites unknown source ${id}`);
147
+ for (const source of brief.source_assessments ?? [])
148
+ if (!sourceIds.has(source.source_id))
149
+ throw new Error(`Research assessment cites unknown source ${source.source_id}`);
150
+ for (const source of brief.source_register ?? [])
151
+ if (!sourceIds.has(source.source_id))
152
+ throw new Error(`Research register cites unknown source ${source.source_id}`);
153
+ for (const candidate of brief.candidate_slate ?? [])
154
+ if (!sourceIds.has(candidate.source_id))
155
+ throw new Error(`Research candidate cites unknown source ${candidate.source_id}`);
156
+ for (const id of brief.artifact_ids)
157
+ if (!artifactIds.has(id))
158
+ throw new Error(`Research result cites unknown artifact ${id}`);
159
+ if (!sourceIds.size && brief.claims.length)
160
+ throw new Error('Research result contains claims without retrieved sources');
161
+ if (!sourceIds.size && (brief.findings.length || !brief.coverage_gaps.length))
162
+ throw new Error('Empty retrieval requires empty findings and an explicit coverage gap');
163
+ return brief;
164
+ }
@@ -0,0 +1,33 @@
1
+ import { type ArdentConfig } from './config.js';
2
+ import { ArtifactStore } from './artifacts.js';
3
+ import { type ResearchBrief } from './research.js';
4
+ import { TaskStore, type Task } from './store.js';
5
+ import { ToolRegistry } from './tools.js';
6
+ import { type ArdentPlugin } from './plugins.js';
7
+ export type RunResult = {
8
+ task: Task;
9
+ answer: string;
10
+ brief?: ResearchBrief;
11
+ };
12
+ export declare class ArdentRuntime {
13
+ #private;
14
+ readonly profileDir: string;
15
+ readonly config: ArdentConfig;
16
+ readonly store: TaskStore;
17
+ readonly artifacts: ArtifactStore;
18
+ readonly tools: ToolRegistry;
19
+ readonly recovered: {
20
+ queued: Task[];
21
+ interrupted: number;
22
+ };
23
+ get draining(): boolean;
24
+ get plugins(): readonly ArdentPlugin[];
25
+ constructor(profileDir: string, config: ArdentConfig, env?: NodeJS.ProcessEnv);
26
+ /** Install plugins once, before the runtime accepts work. Plugin tools join the shared registry. */
27
+ usePlugins(plugins: readonly ArdentPlugin[], env?: NodeJS.ProcessEnv): Promise<void>;
28
+ close(): void;
29
+ drain(timeoutMs?: number): Promise<boolean>;
30
+ accept(prompt: string, caller?: string, context?: 'direct' | 'peer' | 'scheduled', idempotencyKey?: string, sessionId?: string, originalDeadlineMs?: number, backendOverride?: 'llamacpp' | 'codex'): Task;
31
+ cancel(id: string): Task;
32
+ run(taskId: string, env?: NodeJS.ProcessEnv, onText?: (chunk: string, step: number) => void): Promise<RunResult>;
33
+ }