@jinn-network/jinn-layer 0.1.0

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 (131) hide show
  1. package/dist/adapters/contribution-adapter.d.ts +18 -0
  2. package/dist/adapters/contribution-adapter.js +134 -0
  3. package/dist/adapters/corpus-adapter.d.ts +12 -0
  4. package/dist/adapters/corpus-adapter.js +212 -0
  5. package/dist/adapters/index.d.ts +17 -0
  6. package/dist/adapters/index.js +17 -0
  7. package/dist/adapters/json-map-store.d.ts +4 -0
  8. package/dist/adapters/json-map-store.js +31 -0
  9. package/dist/adapters/local-learning-adapter.d.ts +25 -0
  10. package/dist/adapters/local-learning-adapter.js +49 -0
  11. package/dist/adapters/skills-adapter.d.ts +8 -0
  12. package/dist/adapters/skills-adapter.js +55 -0
  13. package/dist/admission-policy.d.ts +38 -0
  14. package/dist/admission-policy.js +17 -0
  15. package/dist/bin/jinn-distill-mcp.d.ts +2 -0
  16. package/dist/bin/jinn-distill-mcp.js +10 -0
  17. package/dist/bin/jinn-layer.d.ts +7 -0
  18. package/dist/bin/jinn-layer.js +15 -0
  19. package/dist/bridge-fetch-evidence.d.ts +102 -0
  20. package/dist/bridge-fetch-evidence.js +677 -0
  21. package/dist/bridge-verdict-source.d.ts +47 -0
  22. package/dist/bridge-verdict-source.js +216 -0
  23. package/dist/bridge.d.ts +247 -0
  24. package/dist/bridge.js +439 -0
  25. package/dist/capture.d.ts +98 -0
  26. package/dist/capture.js +617 -0
  27. package/dist/cli.d.ts +148 -0
  28. package/dist/cli.js +2221 -0
  29. package/dist/cluster-selection.d.ts +34 -0
  30. package/dist/cluster-selection.js +135 -0
  31. package/dist/cluster.d.ts +89 -0
  32. package/dist/cluster.js +142 -0
  33. package/dist/consume.d.ts +139 -0
  34. package/dist/consume.js +280 -0
  35. package/dist/corpus-probes.d.ts +61 -0
  36. package/dist/corpus-probes.js +120 -0
  37. package/dist/corpus-store.d.ts +16 -0
  38. package/dist/corpus-store.js +101 -0
  39. package/dist/distill-captures.d.ts +56 -0
  40. package/dist/distill-captures.js +283 -0
  41. package/dist/distill-feedback.d.ts +20 -0
  42. package/dist/distill-feedback.js +30 -0
  43. package/dist/distill-llm.d.ts +138 -0
  44. package/dist/distill-llm.js +438 -0
  45. package/dist/distill-mcp-server.d.ts +30 -0
  46. package/dist/distill-mcp-server.js +281 -0
  47. package/dist/distill-mode.d.ts +62 -0
  48. package/dist/distill-mode.js +107 -0
  49. package/dist/distill-progress.d.ts +78 -0
  50. package/dist/distill-progress.js +69 -0
  51. package/dist/distill-prompt.d.ts +34 -0
  52. package/dist/distill-prompt.js +85 -0
  53. package/dist/distill-render.d.ts +123 -0
  54. package/dist/distill-render.js +303 -0
  55. package/dist/distill-runs.d.ts +28 -0
  56. package/dist/distill-runs.js +39 -0
  57. package/dist/distill-traces.d.ts +67 -0
  58. package/dist/distill-traces.js +211 -0
  59. package/dist/distill.d.ts +180 -0
  60. package/dist/distill.js +337 -0
  61. package/dist/distiller.d.ts +118 -0
  62. package/dist/distiller.js +142 -0
  63. package/dist/envelope.d.ts +9 -0
  64. package/dist/envelope.js +9 -0
  65. package/dist/eval-prep.d.ts +129 -0
  66. package/dist/eval-prep.js +523 -0
  67. package/dist/execution-publish.d.ts +62 -0
  68. package/dist/execution-publish.js +72 -0
  69. package/dist/gate.d.ts +39 -0
  70. package/dist/gate.js +106 -0
  71. package/dist/index.d.ts +49 -0
  72. package/dist/index.js +50 -0
  73. package/dist/ipfs-cid.d.ts +18 -0
  74. package/dist/ipfs-cid.js +149 -0
  75. package/dist/layer-default.d.ts +3 -0
  76. package/dist/layer-default.js +10 -0
  77. package/dist/ledger.d.ts +62 -0
  78. package/dist/ledger.js +104 -0
  79. package/dist/measurement.d.ts +90 -0
  80. package/dist/measurement.js +134 -0
  81. package/dist/path-safety.d.ts +19 -0
  82. package/dist/path-safety.js +303 -0
  83. package/dist/pipeline.d.ts +94 -0
  84. package/dist/pipeline.js +124 -0
  85. package/dist/plugin-wiring.d.ts +4 -0
  86. package/dist/plugin-wiring.js +54 -0
  87. package/dist/preview.d.ts +31 -0
  88. package/dist/preview.js +28 -0
  89. package/dist/process-contract.d.ts +303 -0
  90. package/dist/process-contract.js +118 -0
  91. package/dist/publish-skill.d.ts +86 -0
  92. package/dist/publish-skill.js +116 -0
  93. package/dist/publish.d.ts +294 -0
  94. package/dist/publish.js +1090 -0
  95. package/dist/scripts/scan-skills-registry.d.ts +24 -0
  96. package/dist/scripts/scan-skills-registry.js +207 -0
  97. package/dist/seed-import/curated-batch.d.ts +36 -0
  98. package/dist/seed-import/curated-batch.js +103 -0
  99. package/dist/seed-import/episode-execute.d.ts +50 -0
  100. package/dist/seed-import/episode-execute.js +235 -0
  101. package/dist/seed-import/episode-fetch.d.ts +119 -0
  102. package/dist/seed-import/episode-fetch.js +137 -0
  103. package/dist/seed-import/episode-plan.d.ts +11 -0
  104. package/dist/seed-import/episode-plan.js +27 -0
  105. package/dist/seed-import/episode-report.d.ts +39 -0
  106. package/dist/seed-import/episode-report.js +36 -0
  107. package/dist/seed-import/execute.d.ts +61 -0
  108. package/dist/seed-import/execute.js +245 -0
  109. package/dist/seed-import/fetch.d.ts +62 -0
  110. package/dist/seed-import/fetch.js +82 -0
  111. package/dist/seed-import/licence.d.ts +17 -0
  112. package/dist/seed-import/licence.js +36 -0
  113. package/dist/seed-import/plan.d.ts +10 -0
  114. package/dist/seed-import/plan.js +21 -0
  115. package/dist/seed-import/report.d.ts +34 -0
  116. package/dist/seed-import/report.js +31 -0
  117. package/dist/seed-import/state.d.ts +72 -0
  118. package/dist/seed-import/state.js +120 -0
  119. package/dist/signal.d.ts +36 -0
  120. package/dist/signal.js +48 -0
  121. package/dist/signing.d.ts +8 -0
  122. package/dist/signing.js +21 -0
  123. package/dist/skill-package.d.ts +70 -0
  124. package/dist/skill-package.js +141 -0
  125. package/dist/skill.d.ts +21 -0
  126. package/dist/skill.js +77 -0
  127. package/dist/snapshot-transcript.d.ts +54 -0
  128. package/dist/snapshot-transcript.js +117 -0
  129. package/dist/three-arm-run.d.ts +27 -0
  130. package/dist/three-arm-run.js +41 -0
  131. package/package.json +60 -0
@@ -0,0 +1,283 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { createEvidenceAdapter } from '@jinn-network/core';
5
+ import { parseCapturedTask } from './capture.js';
6
+ import { parseSkillMarkdown } from './skill-package.js';
7
+ /** Deprecated CapturedTask directory retained as a read-only fallback. */
8
+ export const DEFAULT_CAPTURES_DIR = join(homedir(), '.jinn-client', 'harness-layer', 'captures');
9
+ /** Canonical complete EpisodeV1 records, distinct from legacy distill captures. */
10
+ export const DEFAULT_EPISODES_DIR = join(homedir(), '.jinn-client', 'harness-layer', 'episodes');
11
+ /** Local skills library `distill` installs into by default. */
12
+ export const DEFAULT_SKILLS_INSTALL_DIR = join(homedir(), '.jinn-client', 'harness-layer', 'skills');
13
+ /** How many recent own captures `distill` considers when --limit is unset. */
14
+ export const DEFAULT_DISTILL_CAPTURE_LIMIT = 50;
15
+ /**
16
+ * Load the operator's most recent own captures from `dir`. Every `*.json` file
17
+ * must be a `CapturedTask`; malformed files are skipped with a stderr warning
18
+ * so machine-readable stdout stays parseable.
19
+ */
20
+ export function loadRecentCaptures(dir, limit) {
21
+ if (!existsSync(dir))
22
+ return [];
23
+ const parsed = [];
24
+ for (const file of readdirSync(dir)) {
25
+ if (!file.endsWith('.json'))
26
+ continue;
27
+ try {
28
+ parsed.push(parseCapturedTask(JSON.parse(readFileSync(join(dir, file), 'utf-8'))));
29
+ }
30
+ catch (err) {
31
+ console.warn(`[distill] skipping malformed capture file ${file}: ` +
32
+ `${err instanceof Error ? err.message : String(err)}`);
33
+ }
34
+ }
35
+ parsed.sort((a, b) => b.session.capturedAt.localeCompare(a.session.capturedAt));
36
+ return parsed.slice(0, limit);
37
+ }
38
+ /**
39
+ * Project the canonical local evidence record into the input shape consumed by
40
+ * the existing rung-1 distillation engine. The projection is intentionally
41
+ * lossless for trace facts the distiller understands; episode-only state stays
42
+ * in the canonical record and is never copied to another store.
43
+ */
44
+ export function episodeToCapturedTask(episode) {
45
+ const session = episode.session;
46
+ const task = episode.task;
47
+ const environment = episode.environment;
48
+ const generatorModel = environment.generatorModel;
49
+ const verifier = environment.verifier;
50
+ const cost = episode.cost;
51
+ const attemptGroup = episode.attemptGroup;
52
+ const lineage = episode.lineage;
53
+ return parseCapturedTask({
54
+ session: {
55
+ sessionId: session.sessionId,
56
+ capturedAt: session.capturedAt,
57
+ ...(session.kind ? { kind: session.kind } : {}),
58
+ ...(session.parentSessionId ? { parentSessionId: session.parentSessionId } : {}),
59
+ },
60
+ task: {
61
+ summary: task.summary,
62
+ // Historical CapturedTask files required at least one distribution tag,
63
+ // while EpisodeV1 correctly permits an empty list. The harness identity
64
+ // is a factual local-only fallback needed solely by this compatibility
65
+ // projection.
66
+ distributionTags: task.distributionTags.length > 0
67
+ ? task.distributionTags
68
+ : [environment.harness.name],
69
+ ...(task.repositorySlug ? { repositorySlug: task.repositorySlug } : {}),
70
+ ...(task.baseCommit ? { baseCommit: task.baseCommit } : {}),
71
+ ...(task.createdAt !== undefined ? { createdAt: task.createdAt } : {}),
72
+ ...(task.instanceId ? { instanceId: task.instanceId } : {}),
73
+ },
74
+ environment: {
75
+ harness: {
76
+ name: environment.harness.name,
77
+ version: environment.harness.version,
78
+ },
79
+ model: environment.model,
80
+ tools: environment.tools,
81
+ skillsLoadout: environment.skillsLoadout,
82
+ ...(generatorModel ? {
83
+ generatorModel: {
84
+ id: generatorModel.id,
85
+ source: generatorModel.source,
86
+ ...(generatorModel.provider ? { provider: generatorModel.provider } : {}),
87
+ ...(generatorModel.openWeights !== undefined
88
+ ? { openWeights: generatorModel.openWeights }
89
+ : {}),
90
+ },
91
+ } : {}),
92
+ ...(environment.distributionClass
93
+ ? { distributionClass: environment.distributionClass }
94
+ : {}),
95
+ ...(verifier ? {
96
+ verifier: {
97
+ type: verifier.type,
98
+ failToPass: verifier.failToPass,
99
+ passToPass: verifier.passToPass,
100
+ ...(verifier.evalSemanticsVersion
101
+ ? { evalSemanticsVersion: verifier.evalSemanticsVersion }
102
+ : {}),
103
+ },
104
+ } : {}),
105
+ },
106
+ steps: episode.trajectory.map((step) => ({
107
+ spanId: step.spanId,
108
+ parentSpanId: step.parentSpanId,
109
+ kind: step.kind,
110
+ name: step.name,
111
+ startTimeUnixNano: step.startTimeUnixNano,
112
+ endTimeUnixNano: step.endTimeUnixNano,
113
+ attributes: step.attributes,
114
+ redactedKeys: step.redactedKeys,
115
+ ...(step.events ? {
116
+ events: step.events.map((event) => ({
117
+ timeUnixNano: event.timeUnixNano,
118
+ name: event.name,
119
+ ...(event.attributes ? { attributes: event.attributes } : {}),
120
+ })),
121
+ } : {}),
122
+ ...(step.status ? {
123
+ status: {
124
+ code: step.status.code,
125
+ ...(step.status.message ? { message: step.status.message } : {}),
126
+ },
127
+ } : {}),
128
+ })),
129
+ outcome: {
130
+ status: episode.outcome.status,
131
+ verifiabilityTier: episode.outcome.verificationStrength,
132
+ ...(episode.outcome.summary ? { summary: episode.outcome.summary } : {}),
133
+ },
134
+ cost: {
135
+ durationMs: cost.durationMs,
136
+ ...(cost.tokens ? {
137
+ tokens: { input: cost.tokens.input, output: cost.tokens.output },
138
+ } : {}),
139
+ ...(cost.usdEstimate ? { usdEstimate: cost.usdEstimate } : {}),
140
+ },
141
+ ...(attemptGroup ? {
142
+ attemptGroup: {
143
+ groupId: attemptGroup.groupId,
144
+ attemptId: attemptGroup.attemptId,
145
+ relatedAttemptRefs: attemptGroup.relatedAttemptRefs,
146
+ ...(attemptGroup.groupSize !== undefined ? { groupSize: attemptGroup.groupSize } : {}),
147
+ ...(attemptGroup.nPass !== undefined ? { nPass: attemptGroup.nPass } : {}),
148
+ ...(attemptGroup.nFail !== undefined ? { nFail: attemptGroup.nFail } : {}),
149
+ },
150
+ } : {}),
151
+ ...(lineage ? {
152
+ lineage: {
153
+ episodeId: lineage.episodeId,
154
+ ...(lineage.mintRef ? { mintRef: lineage.mintRef } : {}),
155
+ },
156
+ } : {}),
157
+ provenance: episode.provenance,
158
+ });
159
+ }
160
+ /**
161
+ * Load recent local-learning inputs from the canonical episode store, then
162
+ * merge historical CapturedTask files during the deprecation window. A
163
+ * canonical episode always wins for a duplicate session id, even when the old
164
+ * file has a later timestamp, and the limit is applied only after global sort.
165
+ */
166
+ export async function loadRecentDistillSources(options) {
167
+ const evidence = createEvidenceAdapter({ capturesDir: options.episodesDir });
168
+ const listed = await evidence.list();
169
+ if (listed.status === 'unavailable') {
170
+ console.warn(`[distill] canonical episode store unavailable: ${listed.reason}`);
171
+ }
172
+ const canonicalBySession = new Map();
173
+ if (listed.status !== 'unavailable') {
174
+ for (const episode of listed.value ?? []) {
175
+ try {
176
+ const projected = episodeToCapturedTask(episode);
177
+ const existing = canonicalBySession.get(projected.session.sessionId);
178
+ if (!existing || projected.session.capturedAt > existing.session.capturedAt) {
179
+ canonicalBySession.set(projected.session.sessionId, projected);
180
+ }
181
+ }
182
+ catch (err) {
183
+ console.warn(`[distill] skipping episode ${episode.episodeId} that cannot be projected: ` +
184
+ `${err instanceof Error ? err.message : String(err)}`);
185
+ }
186
+ }
187
+ }
188
+ const merged = new Map(canonicalBySession);
189
+ if (options.legacyCapturesDir) {
190
+ for (const legacy of loadRecentCaptures(options.legacyCapturesDir, Number.MAX_SAFE_INTEGER)) {
191
+ if (!merged.has(legacy.session.sessionId)) {
192
+ merged.set(legacy.session.sessionId, legacy);
193
+ }
194
+ }
195
+ }
196
+ return [...merged.values()]
197
+ .sort((a, b) => b.session.capturedAt.localeCompare(a.session.capturedAt))
198
+ .slice(0, options.limit);
199
+ }
200
+ /** Staging directory beside the active generated-skill directory. */
201
+ export function stagingDirFor(activeDir) {
202
+ return `${activeDir.replace(/[/\\]+$/, '')}-staged`;
203
+ }
204
+ /** Derive local skill history from the canonical active/staged SKILL.md files. */
205
+ export function localSkillProvenance(activeDir, stagedDir = stagingDirFor(activeDir)) {
206
+ const skills = new Map();
207
+ const scan = (dir, state) => {
208
+ if (!existsSync(dir))
209
+ return;
210
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
211
+ if (!entry.isDirectory())
212
+ continue;
213
+ const md = join(dir, entry.name, 'SKILL.md');
214
+ if (!existsSync(md))
215
+ continue;
216
+ try {
217
+ const pkg = parseSkillMarkdown(readFileSync(md, 'utf-8'));
218
+ const sourceSessionIds = pkg.jinn.provenance
219
+ .map((ref) => /^local-capture:(.+)$/.exec(ref)?.[1])
220
+ .filter((sessionId) => Boolean(sessionId));
221
+ if (sourceSessionIds.length === 0)
222
+ continue;
223
+ const ref = `local-skill:${pkg.name}`;
224
+ const existing = skills.get(ref);
225
+ if (existing?.state === 'installed')
226
+ continue;
227
+ skills.set(ref, {
228
+ ref,
229
+ sourceSessionIds: [...new Set(sourceSessionIds)],
230
+ state,
231
+ });
232
+ }
233
+ catch {
234
+ // History is a structured JSON process command. Skip malformed local
235
+ // artifacts without contaminating its process output.
236
+ }
237
+ }
238
+ };
239
+ scan(activeDir, 'installed');
240
+ scan(stagedDir, 'staged');
241
+ return [...skills.values()].sort((a, b) => a.ref.localeCompare(b.ref));
242
+ }
243
+ /**
244
+ * The session ids already covered by a generated skill under any of `dirs`.
245
+ * Both active and staged skill dirs count so `--resume` never re-spends a
246
+ * capture that already produced a skill.
247
+ */
248
+ export function coveredSessionIds(dirs) {
249
+ const covered = new Set();
250
+ for (const dir of dirs) {
251
+ if (!existsSync(dir))
252
+ continue;
253
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
254
+ if (!entry.isDirectory())
255
+ continue;
256
+ const md = join(dir, entry.name, 'SKILL.md');
257
+ if (!existsSync(md))
258
+ continue;
259
+ try {
260
+ const pkg = parseSkillMarkdown(readFileSync(md, 'utf-8'));
261
+ for (const ref of pkg.jinn.provenance) {
262
+ const m = /^local-capture:(.+)$/.exec(ref);
263
+ if (m?.[1])
264
+ covered.add(m[1]);
265
+ }
266
+ }
267
+ catch {
268
+ // A skill dir we can't parse can't prove coverage.
269
+ }
270
+ }
271
+ }
272
+ return covered;
273
+ }
274
+ /**
275
+ * Map local provenance refs to human labels for run panels. Unknown refs remain
276
+ * unchanged so non-local provenance stays auditable.
277
+ */
278
+ export function provenanceLabels(pkg, summaryBySession) {
279
+ return pkg.jinn.provenance.map((ref) => {
280
+ const m = /^local-capture:(.+)$/.exec(ref);
281
+ return m?.[1] ? (summaryBySession.get(m[1]) ?? m[1]) : ref;
282
+ });
283
+ }
@@ -0,0 +1,20 @@
1
+ export type DistillFeedbackVerdict = 'helped' | 'hurt' | 'mixed' | 'unused';
2
+ export interface DistillFeedbackInput {
3
+ skillName: string;
4
+ verdict: DistillFeedbackVerdict;
5
+ sessionId?: string;
6
+ notes?: string;
7
+ acceptedChanges?: string[];
8
+ recordedAt?: string;
9
+ }
10
+ export interface DistillFeedbackRecord extends DistillFeedbackInput {
11
+ schema: 'jinn.distill.feedback.v1';
12
+ recordedAt: string;
13
+ }
14
+ export declare const DEFAULT_DISTILL_FEEDBACK_PATH: string;
15
+ export declare function distillFeedbackPathFromEnv(env?: NodeJS.ProcessEnv): string;
16
+ export declare function recordDistillFeedback(input: DistillFeedbackInput, opts?: {
17
+ path?: string;
18
+ env?: NodeJS.ProcessEnv;
19
+ }): DistillFeedbackRecord;
20
+ export declare function readDistillFeedback(path?: string): DistillFeedbackRecord[];
@@ -0,0 +1,30 @@
1
+ import { appendFileSync, mkdirSync, readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join } from 'node:path';
4
+ export const DEFAULT_DISTILL_FEEDBACK_PATH = join(homedir(), '.jinn-client', 'harness-layer', 'distill-feedback.jsonl');
5
+ export function distillFeedbackPathFromEnv(env = process.env) {
6
+ return env['JINN_LAYER_DISTILL_FEEDBACK_PATH'] ?? DEFAULT_DISTILL_FEEDBACK_PATH;
7
+ }
8
+ export function recordDistillFeedback(input, opts = {}) {
9
+ const record = {
10
+ schema: 'jinn.distill.feedback.v1',
11
+ skillName: input.skillName,
12
+ verdict: input.verdict,
13
+ recordedAt: input.recordedAt ?? new Date().toISOString(),
14
+ ...(input.sessionId ? { sessionId: input.sessionId } : {}),
15
+ ...(input.notes ? { notes: input.notes } : {}),
16
+ ...(input.acceptedChanges && input.acceptedChanges.length > 0 ? { acceptedChanges: input.acceptedChanges } : {}),
17
+ };
18
+ const path = opts.path ?? distillFeedbackPathFromEnv(opts.env);
19
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
20
+ appendFileSync(path, `${JSON.stringify(record)}\n`, { mode: 0o600 });
21
+ return record;
22
+ }
23
+ export function readDistillFeedback(path = DEFAULT_DISTILL_FEEDBACK_PATH) {
24
+ const text = readFileSync(path, 'utf8');
25
+ return text
26
+ .split('\n')
27
+ .map((line) => line.trim())
28
+ .filter(Boolean)
29
+ .map((line) => JSON.parse(line));
30
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * The distill LLM port — runs `jinn-skill-distill-prompt-v1` over one evidence
3
+ * cluster via the `claude` CLI and returns a {@link DistillLLMOutput}
4
+ * (spec/2026-07-06-distillation-v1.md §7). This is the model call that
5
+ * `distillClusters` injects as `DistillDeps.distill`; the distiller itself owns
6
+ * the downstream scrub / contamination / publish steps.
7
+ *
8
+ * The port is deliberately thin: it builds the model input (the versioned
9
+ * distill prompt + a serialized view of the cluster), spawns `claude` in
10
+ * non-interactive mode reading that input on stdin, and parses a strict
11
+ * `{ name, description, body }` JSON object back out of stdout (tolerating a
12
+ * leading/trailing prose wrapper the model may emit around it).
13
+ *
14
+ * Auth is ambient: the `claude` CLI resolves its own credentials (a logged-in
15
+ * session, `CLAUDE_CODE_OAUTH_TOKEN`, or `ANTHROPIC_API_KEY` from the env). This
16
+ * port never reads, sets, or manages a key — matching the daemon's other
17
+ * `claude -p` call sites (`src/runner/claude.ts`, the claude-code adapter).
18
+ */
19
+ import type { DistillCluster, DistillLLMOutput, MetaDistillLLMOutput } from './distill.js';
20
+ import type { MetaCluster } from './cluster.js';
21
+ /**
22
+ * Default distiller model — the strongest available (opus-class), NOT the
23
+ * daemon-wide cheap default (spec §5, v0.5). Distillation is offline, one-shot,
24
+ * and cheap relative to the measurement run, and its quality is model-sensitive
25
+ * — so it pins the best model. Overridable via `JINN_DISTILL_MODEL` at the CLI.
26
+ */
27
+ export declare const DEFAULT_MODEL = "claude-opus-4-8";
28
+ /** Default Codex distiller model for quality runs; override with `JINN_DISTILL_MODEL`. */
29
+ export declare const DEFAULT_CODEX_MODEL = "gpt-5.5";
30
+ /**
31
+ * Default per-cluster subprocess deadline (#1534). A hung `claude`/`codex`
32
+ * child would otherwise block a run forever — silently, since nothing prints
33
+ * until the run completes. On expiry the child is killed and the cluster lands
34
+ * in `errors[]`; the run continues. Override with `--cluster-timeout` /
35
+ * `JINN_DISTILL_CLUSTER_TIMEOUT_S`.
36
+ */
37
+ export declare const DEFAULT_CLUSTER_TIMEOUT_MS = 600000;
38
+ /** A distiller subprocess exceeded its per-cluster deadline and was killed. */
39
+ export declare class DistillTimeoutError extends Error {
40
+ constructor(label: string, timeoutMs: number);
41
+ }
42
+ /** One row of the discoverable distiller catalog (`distill models`). */
43
+ export interface DistillerCatalogEntry {
44
+ provider: 'claude' | 'codex';
45
+ model: string;
46
+ /** `local` runs on this machine; `hosted` sends captures off-machine (none yet). */
47
+ execution: 'local' | 'hosted';
48
+ cost: string;
49
+ privacy: string;
50
+ isDefault?: boolean;
51
+ }
52
+ /**
53
+ * The static, honest catalog of runnable distillers. Every entry is `local`
54
+ * today — no hosted distiller exists (both ports spawn a local CLI). The models
55
+ * reference `DEFAULT_MODEL` / `DEFAULT_CODEX_MODEL` directly so the catalog and
56
+ * the provider defaults cannot drift.
57
+ */
58
+ export declare const DISTILLER_CATALOG: readonly DistillerCatalogEntry[];
59
+ /**
60
+ * The subset of `node:child_process` a spawned child exposes that this port
61
+ * uses. Injected so tests capture the args + feed canned stdout without ever
62
+ * spawning a real `claude`.
63
+ */
64
+ export interface ChildLike {
65
+ stdin: {
66
+ write(chunk: string): void;
67
+ end(): void;
68
+ } | null;
69
+ stdout: {
70
+ on(event: 'data', listener: (chunk: Buffer | string) => void): void;
71
+ } | null;
72
+ stderr: {
73
+ on(event: 'data', listener: (chunk: Buffer | string) => void): void;
74
+ } | null;
75
+ on(event: 'error', listener: (err: Error) => void): void;
76
+ on(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): void;
77
+ /** Optional so pre-#1534 test fakes stay valid; real children always have it. */
78
+ kill?(signal?: NodeJS.Signals): void;
79
+ }
80
+ /** Minimal spawn signature: `(command, args) => child`. Satisfied by `child_process.spawn`. */
81
+ export type SpawnLike = (command: string, args: readonly string[]) => ChildLike;
82
+ /**
83
+ * Build the full model input: the versioned distill prompt, the serialized
84
+ * cluster, and a strict-JSON output contract. The contract is explicit so the
85
+ * downstream parser has an object to extract even if the model wraps it in prose.
86
+ */
87
+ export declare function buildDistillInput(prompt: string, cluster: DistillCluster): string;
88
+ /**
89
+ * Build the distill LLM port. Returns the `DistillDeps.distill` function: given
90
+ * a cluster, spawn `claude -p --model <model>`, pipe the built input on stdin,
91
+ * collect stdout, and parse the strict JSON output.
92
+ *
93
+ * @param opts.claudePath Path to the `claude` executable (default `claude`).
94
+ * @param opts.model Model id (default `claude-opus-4-8` — opus-class, §5).
95
+ * @param opts.spawnImpl Injected spawn (default `child_process.spawn`); tests
96
+ * pass a fake so no real `claude` is invoked.
97
+ */
98
+ export declare function createClaudeDistiller(opts?: {
99
+ claudePath?: string;
100
+ model?: string;
101
+ spawnImpl?: SpawnLike;
102
+ timeoutMs?: number;
103
+ }): (cluster: DistillCluster) => Promise<DistillLLMOutput>;
104
+ /**
105
+ * Build the Codex distill LLM port. This mirrors {@link createClaudeDistiller}
106
+ * but invokes `codex exec` with read-only sandboxing, no approvals, ephemeral
107
+ * session state, and a JSON schema file for structured final output.
108
+ */
109
+ export declare function createCodexDistiller(opts?: {
110
+ codexPath?: string;
111
+ model?: string;
112
+ spawnImpl?: SpawnLike;
113
+ timeoutMs?: number;
114
+ }): (cluster: DistillCluster) => Promise<DistillLLMOutput>;
115
+ /** Build the full meta model input: the versioned meta prompt + the sources + the JSON contract. */
116
+ export declare function buildMetaDistillInput(prompt: string, cluster: MetaCluster): string;
117
+ /**
118
+ * Build the stage-2 meta-distill LLM port (issue #1463). Same spawn/parse
119
+ * scaffolding as {@link createClaudeDistiller}: spawns `claude -p --model`,
120
+ * pipes {@link buildMetaDistillInput} on stdin, and parses the strict JSON
121
+ * (tolerating a prose wrapper via the shared {@link extractJsonObject}).
122
+ */
123
+ export declare function createClaudeMetaDistiller(opts?: {
124
+ claudePath?: string;
125
+ model?: string;
126
+ spawnImpl?: SpawnLike;
127
+ timeoutMs?: number;
128
+ }): (cluster: MetaCluster) => Promise<MetaDistillLLMOutput>;
129
+ /**
130
+ * Build the Codex stage-2 meta-distill port. Same prompt and validation as the
131
+ * Claude meta port, with Codex structured output enforced by JSON schema.
132
+ */
133
+ export declare function createCodexMetaDistiller(opts?: {
134
+ codexPath?: string;
135
+ model?: string;
136
+ spawnImpl?: SpawnLike;
137
+ timeoutMs?: number;
138
+ }): (cluster: MetaCluster) => Promise<MetaDistillLLMOutput>;