@evomap/evolver-core 2.0.0-beta.1 → 2.0.0-beta.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.
- package/assets/gep/genes.jsonl +5 -0
- package/dist/algo/candidateAssembly.js +17 -11
- package/dist/algo/capabilityCandidates.d.ts +2 -0
- package/dist/algo/capabilityCandidates.js +5 -0
- package/dist/algo/conversationSniffer.d.ts +18 -0
- package/dist/algo/conversationSniffer.js +132 -0
- package/dist/algo/cycleEngine.js +2 -0
- package/dist/algo/cycleFailureClassifier.js +13 -5
- package/dist/algo/geneIntake.d.ts +14 -4
- package/dist/algo/geneIntake.js +37 -9
- package/dist/algo/geneSelection.d.ts +6 -0
- package/dist/algo/geneSelection.js +19 -11
- package/dist/algo/index.d.ts +1 -0
- package/dist/algo/index.js +1 -0
- package/dist/assetstore/assetSyncLedger.d.ts +28 -0
- package/dist/assetstore/assetSyncLedger.js +86 -0
- package/dist/assetstore/index.d.ts +3 -1
- package/dist/assetstore/index.js +3 -1
- package/dist/assetstore/localJsonl.d.ts +9 -2
- package/dist/assetstore/localJsonl.js +75 -23
- package/dist/assetstore/pendingSignals.js +3 -1
- package/dist/assetstore/provenance.d.ts +7 -2
- package/dist/assetstore/provenance.js +85 -25
- package/dist/assetstore/reviewFilter.js +2 -1
- package/dist/assetstore/reviewLedger.d.ts +6 -0
- package/dist/assetstore/reviewLedger.js +9 -0
- package/dist/assetstore/seedGenes.d.ts +3 -0
- package/dist/assetstore/seedGenes.js +134 -0
- package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
- package/dist/benchmark/antiGeneBenchmark.js +11 -1
- package/dist/benchmark/antiGeneImpact.d.ts +16 -0
- package/dist/benchmark/antiGeneImpact.js +34 -0
- package/dist/benchmark/antiGeneRollout.d.ts +2 -0
- package/dist/benchmark/antiGeneRollout.js +13 -1
- package/dist/events/eventArchive.d.ts +65 -0
- package/dist/events/eventArchive.js +343 -0
- package/dist/events/eventStore.js +2 -12
- package/dist/events/public.d.ts +3 -1
- package/dist/events/public.js +2 -1
- package/dist/events/retention.d.ts +24 -1
- package/dist/events/retention.js +133 -37
- package/dist/exec/autoExec.js +3 -0
- package/dist/exec/claudeBridge.js +23 -0
- package/dist/exec/runnerRegistry.d.ts +8 -6
- package/dist/exec/runnerRegistry.js +71 -18
- package/dist/exec/selfPrObfuscation.d.ts +2 -1
- package/dist/exec/selfPrObfuscation.js +19 -6
- package/dist/hub/agentDirectory.d.ts +90 -0
- package/dist/hub/agentDirectory.js +104 -0
- package/dist/hub/bindings.js +13 -3
- package/dist/hub/capability.d.ts +14 -2
- package/dist/hub/fake.d.ts +4 -2
- package/dist/hub/fake.js +3 -2
- package/dist/hub/index.d.ts +1 -0
- package/dist/hub/index.js +1 -0
- package/dist/mailbox/catalog.js +3 -0
- package/dist/mailbox/ipcServer.js +2 -2
- package/dist/mailbox/store.d.ts +14 -0
- package/dist/mailbox/store.js +44 -2
- package/dist/material/consumer.js +9 -6
- package/dist/material/index.d.ts +1 -0
- package/dist/material/index.js +1 -0
- package/dist/material/materialArchive.d.ts +81 -0
- package/dist/material/materialArchive.js +466 -0
- package/dist/material/materialStore.d.ts +1 -0
- package/dist/material/materialStore.js +6 -13
- package/dist/schema/common.d.ts +1 -1
- package/dist/schema/common.js +1 -1
- package/dist/schema/material.d.ts +5 -5
- package/dist/trace/trajectoryExport.js +2 -2
- package/dist/wire/geneHints.d.ts +42 -1
- package/dist/wire/geneHints.js +52 -1
- package/dist/wire/index.d.ts +14 -2
- package/dist/wire/index.js +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{"type":"Gene","schema_version":"1.12.1","id":"gene_seed_publish_feishu_doc","category":"innovate","signals_match":["publish_markdown_to_feishu","create_feishu_doc","export_report_to_feishu","publish_results_to_lark_doc","feishu_doc","lark_doc"],"preconditions":["lark-cli is installed and available on PATH.","lark-cli auth status reports a ready user or bot identity."],"strategy":["Run lark-cli doctor and require an ok result before publishing.","Use the Docs v2 API and avoid deprecated Docs v1 flags.","Write long Markdown content to a temporary file and pass it as an @file input.","Create or update the document with the intended user or bot identity, then return the document URL.","Never overwrite local lark-cli credential files while preparing the document."],"constraints":{"max_files":2,"forbidden_paths":[".git","node_modules","~/.lark-cli/config.json"]},"validation":["node --version"],"summary":"Publish Markdown content as a Feishu/Lark document through the official CLI, using file-backed content and Docs v2.","asset_id":"sha256:4cdcf2b2a30bcb29a3310360c3cb16c7fc7d427d23a43c836ffa5076cfe3c041"}
|
|
2
|
+
{"type":"Gene","schema_version":"1.12.1","id":"gene_seed_conventional_git_commit","category":"optimize","signals_match":["git_commit","create_commit","commit_changes","conventional_commit","stage_and_commit","write_commit_message"],"preconditions":["A git repository has staged or unstaged changes that should become one logical commit."],"strategy":["Inspect git status plus the staged or unstaged diff before choosing a commit shape.","Pick a Conventional Commits type and optional scope from the actual changed files.","Stage only the intended logical files and never stage credentials or unrelated changes.","Write a present-tense imperative subject under 72 characters, with body or footer when needed.","Do not amend, force-push, hard-reset, or bypass hooks without explicit operator approval."],"constraints":{"max_files":50,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Create a focused Conventional Commits-style git commit from the real diff while avoiding secrets and unrelated changes.","asset_id":"sha256:8f0acd8af3d0a94d6f50e7fe20d96ff39dc982022b851b204eddce47e177f467"}
|
|
3
|
+
{"type":"Gene","schema_version":"1.12.1","id":"gene_seed_poll_bugbot_review","category":"optimize","signals_match":["poll_bugbot","bugbot_review","wait_for_ci_review","pr_review_gate","cursor_bugbot","pr_opened"],"preconditions":["An open GitHub PR exists and Cursor Bugbot is expected to review it."],"strategy":["Poll the Cursor Bugbot check by name until it reaches a terminal conclusion or the operator timeout is reached.","Treat SUCCESS as mergeable only when required checks are green and there are no unresolved Bugbot comments.","Treat NEUTRAL as not passed; fetch and summarize Cursor Bugbot inline comments for the operator.","On FAILURE or ACTION_REQUIRED, surface the findings and do not merge automatically.","Only squash-merge when the operator explicitly asked for merge and the review gate is clean."],"constraints":{"max_files":1,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Wait for Cursor Bugbot on a GitHub PR and gate merge decisions on the actual check conclusion and comments.","asset_id":"sha256:87737bf9d2702667c31e336fc79096f4da7f12a84baae25e827ad62b4a08d332"}
|
|
4
|
+
{"type":"Gene","schema_version":"1.12.1","id":"gene_seed_gateway_timeout_recovery","category":"repair","signals_match":["gateway_timeout","upstream_timeout","http_524","http_504","request_timed_out","retry_on_timeout"],"preconditions":["A tool call, fetch, subagent request, or long command returned a gateway-class timeout."],"strategy":["Treat gateway timeout as transient or size-driven until one bounded recovery attempt proves otherwise.","Retry the exact same operation once, then stop retrying the monolithic call.","If the retry also times out, split the work by file, directory, endpoint, record, section, or time window.","Run independent slices in parallel when safe and merge the results.","Apply the same decomposition recursively only to the slice that still times out."],"constraints":{"max_files":1,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Recover from gateway or upstream timeouts by retrying once, then decomposing the work instead of looping the same large call.","asset_id":"sha256:fe6820477e47a8eb848ecfaa33cdb4242d9af77b78fac21fa1d716244984615c"}
|
|
5
|
+
{"type":"Gene","schema_version":"1.12.1","id":"gene_seed_github_webhook_listener","category":"innovate","signals_match":["github_webhook_listener","bugbot_webhook","passive_pr_notifications","notify_when_bugbot_finishes","webhook_tunnel","github_pr_inbox"],"preconditions":["A developer machine can run a local listener and an outbound tunnel for GitHub webhook delivery."],"strategy":["Use an HMAC-validated local listener for GitHub webhook payloads and deduplicate delivery ids.","Expose the listener through an outbound-only tunnel rather than an inbound public port.","Write vetted notifications into a local inbox with restrictive permissions; never execute webhook payloads.","On session start, re-fetch PR state through GitHub before surfacing any inbox item.","Rotate webhook secrets periodically and immediately after suspected exposure."],"constraints":{"max_files":20,"forbidden_paths":[".git","node_modules"]},"validation":["node --version"],"summary":"Deploy a local HMAC-validated GitHub webhook listener that turns PR and Bugbot events into safe operator notifications.","asset_id":"sha256:a64e8d0a91cebd54290451527987230572d8a5bfe90b6ca73cd3368fd3d1f689"}
|
|
@@ -2,7 +2,7 @@ import { aggregateLearningHistory } from '../assetstore/learningHistory.js';
|
|
|
2
2
|
import { reuseSentiment } from '../ops/reuseOutcomes.js';
|
|
3
3
|
import { tagOverlapScore } from '../signals/expand.js';
|
|
4
4
|
import { bannedGenesFromFailures } from './bans.js';
|
|
5
|
-
import {
|
|
5
|
+
import { geneGenerationSource } from './geneIntake.js';
|
|
6
6
|
function asStrings(v) {
|
|
7
7
|
return Array.isArray(v) ? v.filter((x) => typeof x === 'string') : [];
|
|
8
8
|
}
|
|
@@ -18,10 +18,10 @@ function candidateIds(geneId, assetId) {
|
|
|
18
18
|
function isBannedCandidate(banned, geneId, assetId) {
|
|
19
19
|
return candidateIds(geneId, assetId).some((id) => banned.has(id));
|
|
20
20
|
}
|
|
21
|
-
function passesInjectedCandidateGates(candidate, opts) {
|
|
21
|
+
function passesInjectedCandidateGates(candidate, opts, provenance) {
|
|
22
22
|
const assetId = candidate.assetId;
|
|
23
23
|
if (opts.provenance && !opts.includeUntrusted) {
|
|
24
|
-
if (!assetId ||
|
|
24
|
+
if (!assetId || provenance?.get(assetId)?.trusted !== true)
|
|
25
25
|
return false;
|
|
26
26
|
}
|
|
27
27
|
if (opts.review) {
|
|
@@ -79,13 +79,14 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
|
|
|
79
79
|
const genes = await store.list('Gene', limit);
|
|
80
80
|
const banned = await computeBans(store, signals, limit);
|
|
81
81
|
const sigSet = new Set(signals);
|
|
82
|
+
const provenance = opts.provenance?.snapshot();
|
|
82
83
|
const out = [];
|
|
83
84
|
const distilledFallback = [];
|
|
84
85
|
const antiWarnings = [];
|
|
85
86
|
for (const g of genes) {
|
|
86
87
|
// Trust-first (#30): untrusted (e.g. hub-ingested) genes are excluded from the candidate pool by default;
|
|
87
88
|
// they enter only with includeUntrusted or after an explicit promotion. Provenance is keyed by asset_id.
|
|
88
|
-
if (opts.provenance && !opts.includeUntrusted &&
|
|
89
|
+
if (opts.provenance && !opts.includeUntrusted && provenance?.get(String(g.asset_id))?.trusted === false)
|
|
89
90
|
continue;
|
|
90
91
|
// Review-first (#89/#91): auto-distilled drafts land quarantined until a human approves; rejected drafts stay
|
|
91
92
|
// out too. No record → eligible (cycle/migrate genes). Symmetric to the provenance trust-first filter above.
|
|
@@ -104,18 +105,20 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
|
|
|
104
105
|
const literal = signalsMatch.some((m) => sigSet.has(m));
|
|
105
106
|
const relevant = literal || tagOverlapScore(signals, { signalsMatch, geneId, category, summary }) > 0;
|
|
106
107
|
if (!relevant) {
|
|
107
|
-
// #97: a trusted, approved, non-banned distilled gene that doesn't match the live signals is not
|
|
108
|
-
// candidate, but it IS eligible as a last-resort fallback (selection uses it only when nothing clears
|
|
109
|
-
// floor).
|
|
110
|
-
//
|
|
108
|
+
// #97: a trusted, approved, non-banned distilled (or evolved) gene that doesn't match the live signals is not
|
|
109
|
+
// a normal candidate, but it IS eligible as a last-resort fallback (selection uses it only when nothing clears
|
|
110
|
+
// the floor). Eligibility here is gated by trust/review/ban, not by skill provenance. Provenance is read from
|
|
111
|
+
// generation_meta (V1 #302); a legacy gene without it falls back to the `gene_distilled_` id namespace.
|
|
111
112
|
// Lightweight: no learning-history aggregation — it is picked first-match, not ranked by health.
|
|
112
|
-
|
|
113
|
+
const gsrc = geneGenerationSource(g, geneId);
|
|
114
|
+
if (gsrc === 'distilled' || gsrc === 'evolved') {
|
|
113
115
|
distilledFallback.push({
|
|
114
116
|
geneId,
|
|
115
117
|
assetId,
|
|
116
118
|
signalsMatch,
|
|
117
119
|
view: emptyLearningView(geneId),
|
|
118
120
|
reuseCount: 0,
|
|
121
|
+
generationSource: gsrc,
|
|
119
122
|
...(category ? { category } : {}),
|
|
120
123
|
...(summary ? { summary } : {}),
|
|
121
124
|
});
|
|
@@ -145,7 +148,7 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
|
|
|
145
148
|
if (opts.hubCandidates && opts.hubCandidates.length > 0) {
|
|
146
149
|
const localIds = new Set(out.map((c) => c.geneId));
|
|
147
150
|
for (const h of opts.hubCandidates) {
|
|
148
|
-
if (!passesInjectedCandidateGates(h, opts))
|
|
151
|
+
if (!passesInjectedCandidateGates(h, opts, provenance))
|
|
149
152
|
continue;
|
|
150
153
|
if (localIds.has(h.geneId))
|
|
151
154
|
continue; // a trusted local gene already covers this id
|
|
@@ -160,7 +163,10 @@ export async function assembleSelectionPool(store, signals, opts = {}) {
|
|
|
160
163
|
}
|
|
161
164
|
}
|
|
162
165
|
for (const a of await store.list('AntiGene', limit)) {
|
|
163
|
-
|
|
166
|
+
// AntiGene is negative memory that changes an autonomous prompt. Unlike legacy/cycle-authored Genes, it must
|
|
167
|
+
// never inherit ReviewLedger's backward-compatible "no record = approved" default: no ledger, no record,
|
|
168
|
+
// quarantined, and rejected all fail closed. Only an explicit human approval can enable warning injection.
|
|
169
|
+
if (!opts.review?.isExplicitlyApproved(String(a.asset_id)))
|
|
164
170
|
continue;
|
|
165
171
|
const trigger = asStrings(a['trigger']);
|
|
166
172
|
const avoid = asStrings(a['avoid']);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ConversationCapabilityHit } from './conversationSniffer.js';
|
|
1
2
|
/** The "five-question shape" of a capability proposal (ported verbatim from v1). */
|
|
2
3
|
export interface FiveQuestionShape {
|
|
3
4
|
title: string;
|
|
@@ -30,6 +31,7 @@ export interface ExtractCandidatesInput {
|
|
|
30
31
|
toolCalls?: readonly string[];
|
|
31
32
|
signals?: readonly string[];
|
|
32
33
|
failedCapsules?: readonly FailedCapsuleLike[];
|
|
34
|
+
conversationHits?: readonly ConversationCapabilityHit[];
|
|
33
35
|
}
|
|
34
36
|
/**
|
|
35
37
|
* Mine capability candidates from three sources (ported v1): repeated tool calls (>=3), active opportunity
|
|
@@ -80,6 +80,11 @@ export function extractCapabilityCandidates(inp) {
|
|
|
80
80
|
const title = `Repeated tool usage: ${tool}`;
|
|
81
81
|
out.push(candidate('transcript', stableHash(title), title, signals, `Observed ${count} occurrences of tool call ${tool}.`, signalTags));
|
|
82
82
|
}
|
|
83
|
+
// A2. Conversation sniffer hits: a transcript explicitly proved a reusable capability.
|
|
84
|
+
for (const hit of inp.conversationHits ?? []) {
|
|
85
|
+
const hitSignals = [...new Set([...(hit.signals ?? []), ...signals].map((s) => String(s)).filter(Boolean))];
|
|
86
|
+
out.push(candidate('transcript', stableHash('conversation:' + hit.id), hit.title || 'Verified reusable conversation capability', hitSignals, hit.evidence.length > 0 ? hit.evidence.slice(0, 2).join('; ') : hit.summary, [...new Set([...signalTags, ...expandSignals(hitSignals)])]));
|
|
87
|
+
}
|
|
83
88
|
// B. Signal-mapped candidates: predefined defensive/opportunity signals.
|
|
84
89
|
for (const sc of SIGNAL_CANDIDATES) {
|
|
85
90
|
if (!signals.some((s) => s === sc.signal || s.startsWith(sc.signal + ':')))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SignalSourceTurn } from '../signals/extractor.js';
|
|
2
|
+
export interface ConversationSnifferTurn extends SignalSourceTurn {
|
|
3
|
+
role?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ConversationCapabilityHit {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
summary: string;
|
|
9
|
+
signals: string[];
|
|
10
|
+
evidence: string[];
|
|
11
|
+
score: number;
|
|
12
|
+
reasons: string[];
|
|
13
|
+
toolCalls: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface ConversationSnifferOptions {
|
|
16
|
+
maxHits?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function sniffConversationCapabilities(turns: readonly ConversationSnifferTurn[], opts?: ConversationSnifferOptions): ConversationCapabilityHit[];
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { redactString } from '../hub/sanitize.js';
|
|
3
|
+
const REUSABLE_RE = /\b(reusable|repeatable|workflow|playbook|runbook|capability|procedure|pattern|recipe|documented|future runs?|next time|can reuse|reuse this)\b|可复用|复用|能力|流程|工作流/i;
|
|
4
|
+
const PROOF_RE = /\b(validated|verified|passed|green|success(?:ful|fully)?|succeeded|works?|completed|published|uploaded|recorded:true|exit code:?\s*0|all tests passed)\b|验证|通过|成功|已发布|可用/i;
|
|
5
|
+
const FAILURE_RE = /\b(failed|failure|error|exception|traceback|exit code:?\s*[1-9]|not working|unable to)\b|失败|错误|报错/i;
|
|
6
|
+
const EXIT_ZERO_RE = /\bexit code:?\s*0\b/i;
|
|
7
|
+
const EXIT_NON_ZERO_RE = /\bexit code:?\s*[1-9]\d*\b/i;
|
|
8
|
+
const DOMAIN_TOKENS = [
|
|
9
|
+
[/\b(lark|feishu|飞书)\b/i, 'lark'],
|
|
10
|
+
[/\b(github|gh|pull request|bugbot)\b/i, 'github'],
|
|
11
|
+
[/\b(playwright|screenshot|visual)\b/i, 'playwright'],
|
|
12
|
+
[/\b(vitest|jest|test)\b/i, 'test'],
|
|
13
|
+
[/\b(pnpm|npm|yarn)\b/i, 'package-manager'],
|
|
14
|
+
[/\b(hub|publish|marketplace)\b/i, 'hub-publish'],
|
|
15
|
+
[/\b(release|checksum|sha256)\b/i, 'release'],
|
|
16
|
+
[/\b(api|http|endpoint)\b/i, 'api'],
|
|
17
|
+
[/\b(document|docs?|markdown)\b/i, 'docs'],
|
|
18
|
+
];
|
|
19
|
+
function cleanText(value, max = 220) {
|
|
20
|
+
const text = redactString(String(value ?? '').replace(/\s+/g, ' ').trim());
|
|
21
|
+
return text.length > max ? `${text.slice(0, max - 3)}...` : text;
|
|
22
|
+
}
|
|
23
|
+
function turnText(turn) {
|
|
24
|
+
return cleanText([turn.text, turn.toolResult, turn.errorMessage].filter(Boolean).join('\n'), 500);
|
|
25
|
+
}
|
|
26
|
+
function stableId(parts) {
|
|
27
|
+
return `conversation_capability_${createHash('sha1').update(JSON.stringify(parts), 'utf8').digest('hex').slice(0, 12)}`;
|
|
28
|
+
}
|
|
29
|
+
function normalizeSignal(value) {
|
|
30
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').slice(0, 48);
|
|
31
|
+
}
|
|
32
|
+
function collectToolCalls(turns) {
|
|
33
|
+
const seen = new Set();
|
|
34
|
+
for (const turn of turns) {
|
|
35
|
+
const tool = cleanText(turn.toolName ?? '', 80);
|
|
36
|
+
if (tool)
|
|
37
|
+
seen.add(tool);
|
|
38
|
+
}
|
|
39
|
+
return [...seen].slice(0, 8);
|
|
40
|
+
}
|
|
41
|
+
function domainSignals(text, toolCalls) {
|
|
42
|
+
const out = new Set();
|
|
43
|
+
for (const [re, signal] of DOMAIN_TOKENS) {
|
|
44
|
+
if (re.test(text))
|
|
45
|
+
out.add(signal);
|
|
46
|
+
}
|
|
47
|
+
for (const tool of toolCalls) {
|
|
48
|
+
const normalized = normalizeSignal(tool);
|
|
49
|
+
if (normalized)
|
|
50
|
+
out.add(normalized);
|
|
51
|
+
}
|
|
52
|
+
return [...out].slice(0, 6);
|
|
53
|
+
}
|
|
54
|
+
function isToolTurn(turn) {
|
|
55
|
+
return turn.role === 'tool' || Boolean(turn.toolName);
|
|
56
|
+
}
|
|
57
|
+
function classifyToolOutcome(turn) {
|
|
58
|
+
const text = turnText(turn);
|
|
59
|
+
if (EXIT_NON_ZERO_RE.test(text))
|
|
60
|
+
return 'failure';
|
|
61
|
+
if (EXIT_ZERO_RE.test(text))
|
|
62
|
+
return 'success';
|
|
63
|
+
if (PROOF_RE.test(text) && !FAILURE_RE.test(text))
|
|
64
|
+
return 'success';
|
|
65
|
+
if (FAILURE_RE.test(text))
|
|
66
|
+
return 'failure';
|
|
67
|
+
return 'unknown';
|
|
68
|
+
}
|
|
69
|
+
function hasTerminalSuccessfulToolRun(turns) {
|
|
70
|
+
let lastDecisive = 'unknown';
|
|
71
|
+
for (const turn of turns) {
|
|
72
|
+
if (!isToolTurn(turn))
|
|
73
|
+
continue;
|
|
74
|
+
const outcome = classifyToolOutcome(turn);
|
|
75
|
+
if (outcome !== 'unknown')
|
|
76
|
+
lastDecisive = outcome;
|
|
77
|
+
}
|
|
78
|
+
return lastDecisive === 'success';
|
|
79
|
+
}
|
|
80
|
+
function pickEvidence(turns, matcher, max = 3) {
|
|
81
|
+
const evidence = [];
|
|
82
|
+
for (const turn of turns) {
|
|
83
|
+
const text = turnText(turn);
|
|
84
|
+
if (!text || !matcher.test(text))
|
|
85
|
+
continue;
|
|
86
|
+
evidence.push(cleanText(text, 180));
|
|
87
|
+
if (evidence.length >= max)
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
return evidence;
|
|
91
|
+
}
|
|
92
|
+
function pickSummary(turns, reusableEvidence, proofEvidence) {
|
|
93
|
+
const assistant = turns
|
|
94
|
+
.filter((turn) => turn.role === 'assistant' && !turn.isMeta)
|
|
95
|
+
.map(turnText)
|
|
96
|
+
.find((text) => text.length >= 40 && (REUSABLE_RE.test(text) || PROOF_RE.test(text)));
|
|
97
|
+
return cleanText(assistant ?? reusableEvidence[0] ?? proofEvidence[0] ?? 'Verified reusable conversation capability.', 180);
|
|
98
|
+
}
|
|
99
|
+
export function sniffConversationCapabilities(turns, opts = {}) {
|
|
100
|
+
const usableTurns = turns.filter((turn) => !turn.isMeta);
|
|
101
|
+
if (usableTurns.length === 0)
|
|
102
|
+
return [];
|
|
103
|
+
const allText = usableTurns.map(turnText).filter(Boolean).join('\n');
|
|
104
|
+
if (!allText.trim())
|
|
105
|
+
return [];
|
|
106
|
+
const reusableEvidence = pickEvidence(usableTurns, REUSABLE_RE);
|
|
107
|
+
const proofEvidence = pickEvidence(usableTurns, PROOF_RE);
|
|
108
|
+
if (reusableEvidence.length === 0 || proofEvidence.length === 0)
|
|
109
|
+
return [];
|
|
110
|
+
const failureOnly = FAILURE_RE.test(allText) && !PROOF_RE.test(allText);
|
|
111
|
+
if (failureOnly)
|
|
112
|
+
return [];
|
|
113
|
+
if (!hasTerminalSuccessfulToolRun(usableTurns))
|
|
114
|
+
return [];
|
|
115
|
+
const toolCalls = collectToolCalls(usableTurns);
|
|
116
|
+
const domain = domainSignals(allText, toolCalls);
|
|
117
|
+
const signals = [...new Set(['reusable_capability', 'verified_workflow', ...domain])].slice(0, 8);
|
|
118
|
+
const score = 4 + Math.min(2, reusableEvidence.length) + Math.min(2, proofEvidence.length) + Math.min(2, toolCalls.length);
|
|
119
|
+
const summary = pickSummary(usableTurns, reusableEvidence, proofEvidence);
|
|
120
|
+
const titleSignal = domain[0] ?? 'verified workflow';
|
|
121
|
+
const hit = {
|
|
122
|
+
id: stableId({ summary, signals, evidence: [...reusableEvidence, ...proofEvidence].slice(0, 4) }),
|
|
123
|
+
title: `Verified reusable capability: ${titleSignal}`,
|
|
124
|
+
summary,
|
|
125
|
+
signals,
|
|
126
|
+
evidence: [...reusableEvidence, ...proofEvidence].slice(0, 4),
|
|
127
|
+
score,
|
|
128
|
+
reasons: ['reusable_marker', 'proof_marker', ...(toolCalls.length > 0 ? ['tool_evidence'] : [])],
|
|
129
|
+
toolCalls,
|
|
130
|
+
};
|
|
131
|
+
return [hit].slice(0, opts.maxHits ?? 1);
|
|
132
|
+
}
|
package/dist/algo/cycleEngine.js
CHANGED
|
@@ -183,6 +183,8 @@ export class CycleEngine {
|
|
|
183
183
|
// Locality must recognise V2's real gene prefix `gene_distilled_` (isDistilledGeneId), not just the
|
|
184
184
|
// V1-only `gene_auto_` / content-hash `sha256:` forms — otherwise this gate stays shut for every real V2
|
|
185
185
|
// gene and classifyCycleFailure never runs (mirror of isLocalGeneratedGene in cycleFailureClassifier.ts).
|
|
186
|
+
// NB the prefix is now a NAMESPACE marker, not a provenance tag (the authoritative source is generation_meta,
|
|
187
|
+
// V1 #302); this call site only has a gene id, so it falls back to the prefix.
|
|
186
188
|
const localCurrentGene = current?.geneId !== undefined && (isDistilledGeneId(current.geneId) || /^sha256:/.test(current.geneId) || /^gene_auto_/.test(current.geneId));
|
|
187
189
|
// Run classification when we have ANY host context (an exec transcript or an injected failureContext) OR a
|
|
188
190
|
// local-gene blast to judge; otherwise stay default-open (unclassified, no payload).
|
|
@@ -12,13 +12,17 @@
|
|
|
12
12
|
import { isDistilledGeneId } from './geneIntake.js';
|
|
13
13
|
// "[NO SESSION LOGS FOUND]" et al. — the host gave evolver nothing to evolve from.
|
|
14
14
|
const HOST_NO_TRANSCRIPT_RE = /\[no session logs? found\]|no session log|no transcript/i;
|
|
15
|
+
// Interactive runner gates can stop execution before the provider is invoked. They still belong to the
|
|
16
|
+
// host_provider_error compatibility bucket because the local gene never had a chance to run.
|
|
17
|
+
const HOST_RUNNER_ERR_RE = /Workspace Trust Required/i;
|
|
15
18
|
// Host LLM-provider errors. None of these strings are emitted by evolver core (v1 #279 verified the same;
|
|
16
19
|
// keeping the regex byte-for-byte equivalent), so their presence means the host Agent's provider call failed.
|
|
17
|
-
const HOST_PROVIDER_ERR_RE = /\bLLM ERROR\b|\bMaxTokens\b|field MaxTokens|max_tokens[^\n]{0,24}invalid|insufficient_quota|invalid_api_key|\brate limit(?:ed| exceeded)?\b|quota exceeded|context length exceeded|maximum context length/i;
|
|
20
|
+
const HOST_PROVIDER_ERR_RE = /\bLLM ERROR\b|\bMaxTokens\b|field MaxTokens|max_tokens[^\n]{0,24}invalid|insufficient_quota|invalid_api_key|\brate limit(?:ed| exceeded)?\b|quota exceeded|context length exceeded|maximum context length|disabled Claude subscription access|Use an Anthropic API key|not authenticated|authentication required|login required|not logged in/i;
|
|
18
21
|
// Locality prefixes a "locally-generated" gene id can carry:
|
|
19
|
-
// - `gene_distilled_` — the
|
|
20
|
-
// surfaced via isDistilledGeneId).
|
|
21
|
-
//
|
|
22
|
+
// - `gene_distilled_` — the namespace V2 mints for every intaken gene (geneIntake.DISTILLED_ID_PREFIX,
|
|
23
|
+
// surfaced via isDistilledGeneId). NB this is a NAMESPACE marker now, not a provenance tag: the authoritative
|
|
24
|
+
// source is generation_meta (V1 #302, read via geneGenerationSource), but this call site only has a gene id, so
|
|
25
|
+
// it falls back to the prefix. Without this the whole `local_gene_no_blast` bucket never fired for real V2 genes.
|
|
22
26
|
// - `gene_auto_` — V1-only legacy form (V2 never produces it); kept so V1-shaped inputs still match.
|
|
23
27
|
// - `sha256:` — a content asset_id (not a logical gene id); kept for back-compat / loose callers.
|
|
24
28
|
function isLocalGeneratedGene(geneId) {
|
|
@@ -60,7 +64,11 @@ export function classifyCycleFailure(opts) {
|
|
|
60
64
|
if (hasSessionLog && (!log.trim() || HOST_NO_TRANSCRIPT_RE.test(log))) {
|
|
61
65
|
return { failureClass: 'host_no_transcript', reason: 'no session transcript to evolve from' };
|
|
62
66
|
}
|
|
63
|
-
// (b) Host LLM-provider error surfaced in the transcript.
|
|
67
|
+
// (b) Host runner preflight or LLM-provider error surfaced in the transcript.
|
|
68
|
+
const runner = hasSessionLog ? log.match(HOST_RUNNER_ERR_RE) : null;
|
|
69
|
+
if (runner) {
|
|
70
|
+
return { failureClass: 'host_provider_error', reason: `host runner error: ${String(runner[0]).slice(0, 60)}` };
|
|
71
|
+
}
|
|
64
72
|
const prov = hasSessionLog ? log.match(HOST_PROVIDER_ERR_RE) : null;
|
|
65
73
|
if (prov) {
|
|
66
74
|
return { failureClass: 'host_provider_error', reason: `host LLM-provider error: ${String(prov[0]).slice(0, 60)}` };
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import { type Gene } from '../wire/index.js';
|
|
1
|
+
import { type Gene, type GenerationSource } from '../wire/index.js';
|
|
2
2
|
/**
|
|
3
|
-
* True when a gene id is
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* True when a gene id is in the `gene_distilled_` namespace. NB this prefix is now a NAMESPACE marker, NOT a
|
|
4
|
+
* provenance tag: v1 used it to mean "skill-derived" (auto-evolved genes were `gene_auto_`), but v2's intakeGene
|
|
5
|
+
* tags EVERY intaken gene this way regardless of origin. The authoritative provenance is {@link geneGenerationSource};
|
|
6
|
+
* this prefix check remains as a back-compat fallback for legacy pooled genes that predate generation_meta, and for
|
|
7
|
+
* call sites that only have a gene id (not the full record).
|
|
6
8
|
*/
|
|
7
9
|
export declare function isDistilledGeneId(id: string | undefined | null): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* The authoritative gene-generation source: read `generation_meta.source` from a gene-shaped record (V1 #302). Falls
|
|
12
|
+
* back to the id-prefix namespace when the record carries no generation_meta (legacy pooled genes predate the field),
|
|
13
|
+
* returning 'distilled' for a `gene_distilled_` id and 'manual' otherwise — so the fallback never claims a higher
|
|
14
|
+
* provenance tier than the prefix can vouch for. `null` means "undetermined" (no record, no recognizable id).
|
|
15
|
+
*/
|
|
16
|
+
export declare function geneGenerationSource(gene: Record<string, unknown> | null | undefined, geneId?: string | null): GenerationSource | null;
|
|
8
17
|
export interface GeneCandidate {
|
|
9
18
|
id?: string;
|
|
10
19
|
category?: string;
|
|
@@ -19,6 +28,7 @@ export interface GeneCandidate {
|
|
|
19
28
|
validation?: readonly string[];
|
|
20
29
|
routing_hint?: unknown;
|
|
21
30
|
tool_policy?: unknown;
|
|
31
|
+
generation_meta?: unknown;
|
|
22
32
|
}
|
|
23
33
|
/** Minimal shape of an existing gene needed for dedup. */
|
|
24
34
|
export interface ExistingGeneRef {
|
package/dist/algo/geneIntake.js
CHANGED
|
@@ -5,20 +5,44 @@
|
|
|
5
5
|
// signals fully overlap an existing one (dedup, so the pool does not grow redundant). The gene's CONTENT
|
|
6
6
|
// is generated upstream (the agent runtime / a distillation prompt — generative, out of core scope); this
|
|
7
7
|
// slice is the structural gate. Pairs with capabilityCandidates (which proposes what to distill).
|
|
8
|
-
import { computeAssetId, validateWire, SCHEMA_VERSION, normalizeRoutingHint, normalizeToolPolicy, stripGeneHints, } from '../wire/index.js';
|
|
8
|
+
import { computeAssetId, validateWire, SCHEMA_VERSION, normalizeRoutingHint, normalizeToolPolicy, normalizeGenerationMeta, stripGeneHints, } from '../wire/index.js';
|
|
9
9
|
const VALID_CATEGORIES = ['repair', 'optimize', 'innovate', 'explore'];
|
|
10
10
|
const DEFAULT_FORBIDDEN_PATHS = ['.git', 'node_modules'];
|
|
11
11
|
const DEFAULT_MAX_FILES = 12;
|
|
12
12
|
/** Id prefix every distilled (skill-derived) gene carries. Recognised via {@link isDistilledGeneId}. */
|
|
13
13
|
const DISTILLED_ID_PREFIX = 'gene_distilled_';
|
|
14
14
|
/**
|
|
15
|
-
* True when a gene id is
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* True when a gene id is in the `gene_distilled_` namespace. NB this prefix is now a NAMESPACE marker, NOT a
|
|
16
|
+
* provenance tag: v1 used it to mean "skill-derived" (auto-evolved genes were `gene_auto_`), but v2's intakeGene
|
|
17
|
+
* tags EVERY intaken gene this way regardless of origin. The authoritative provenance is {@link geneGenerationSource};
|
|
18
|
+
* this prefix check remains as a back-compat fallback for legacy pooled genes that predate generation_meta, and for
|
|
19
|
+
* call sites that only have a gene id (not the full record).
|
|
18
20
|
*/
|
|
19
21
|
export function isDistilledGeneId(id) {
|
|
20
22
|
return typeof id === 'string' && id.startsWith(DISTILLED_ID_PREFIX);
|
|
21
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* The authoritative gene-generation source: read `generation_meta.source` from a gene-shaped record (V1 #302). Falls
|
|
26
|
+
* back to the id-prefix namespace when the record carries no generation_meta (legacy pooled genes predate the field),
|
|
27
|
+
* returning 'distilled' for a `gene_distilled_` id and 'manual' otherwise — so the fallback never claims a higher
|
|
28
|
+
* provenance tier than the prefix can vouch for. `null` means "undetermined" (no record, no recognizable id).
|
|
29
|
+
*/
|
|
30
|
+
export function geneGenerationSource(gene, geneId) {
|
|
31
|
+
const meta = gene?.['generation_meta'];
|
|
32
|
+
if (meta && typeof meta === 'object') {
|
|
33
|
+
const src = meta['source'];
|
|
34
|
+
if (typeof src === 'string' && (src === 'evolved' || src === 'distilled' || src === 'manual'))
|
|
35
|
+
return src;
|
|
36
|
+
}
|
|
37
|
+
// Legacy fallback: a `gene_distilled_` id is at best a distilled gene (v2 minted it for ALL intake sources, so it
|
|
38
|
+
// cannot be trusted as "evolved"); any other id is treated as manual (human-authored / externally seeded).
|
|
39
|
+
const id = geneId ?? (typeof gene?.['id'] === 'string' ? String(gene['id']) : null);
|
|
40
|
+
if (id && isDistilledGeneId(id))
|
|
41
|
+
return 'distilled';
|
|
42
|
+
if (id)
|
|
43
|
+
return 'manual';
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
22
46
|
function fnv1a(s) {
|
|
23
47
|
let h = 2166136261;
|
|
24
48
|
for (let i = 0; i < s.length; i++) {
|
|
@@ -65,6 +89,9 @@ export function intakeGene(candidate, existing = []) {
|
|
|
65
89
|
// absent when malformed/empty. The router / tool-gate reads absent === "no opinion".
|
|
66
90
|
const routingHint = normalizeRoutingHint(candidate.routing_hint);
|
|
67
91
|
const toolPolicy = normalizeToolPolicy(candidate.tool_policy);
|
|
92
|
+
// v2-delta provenance + quality metadata (v1 #302): normalized so a candidate carrying it lands canonical,
|
|
93
|
+
// dropped to absent when malformed/no-recognized-source.
|
|
94
|
+
const generationMeta = normalizeGenerationMeta(candidate.generation_meta);
|
|
68
95
|
const gene = {
|
|
69
96
|
type: 'Gene',
|
|
70
97
|
schema_version: SCHEMA_VERSION,
|
|
@@ -81,16 +108,17 @@ export function intakeGene(candidate, existing = []) {
|
|
|
81
108
|
...(candidate.preconditions ? { preconditions: [...candidate.preconditions] } : {}),
|
|
82
109
|
...(routingHint ? { routing_hint: routingHint } : {}),
|
|
83
110
|
...(toolPolicy ? { tool_policy: toolPolicy } : {}),
|
|
111
|
+
...(generationMeta ? { generation_meta: generationMeta } : {}),
|
|
84
112
|
asset_id: '',
|
|
85
113
|
};
|
|
86
|
-
// asset_id folds in the hints (intake's own canonical shape — gep-sdk canonicalize hashes every
|
|
87
|
-
// NB: this is NOT byte-equal to what v1's createGene would hash for the same logical gene — v1 always emits
|
|
114
|
+
// asset_id folds in the hints + generation_meta (intake's own canonical shape — gep-sdk canonicalize hashes every
|
|
115
|
+
// own key). NB: this is NOT byte-equal to what v1's createGene would hash for the same logical gene — v1 always emits
|
|
88
116
|
// routing_hint/tool_policy:null plus empty epigenetic_marks/learning_history/anti_patterns/preconditions that
|
|
89
117
|
// v2 intake omits, so the canonical shapes differ. Self-consistent here; not a cross-impl parity guarantee.
|
|
90
118
|
gene.asset_id = computeAssetId(gene) ?? '';
|
|
91
|
-
// Structural gate validates the gep-sdk-known CORE. routing_hint / tool_policy are v2-delta
|
|
92
|
-
// gene-schema bump), so strip them before the check — else additionalProperties:false rejects the
|
|
93
|
-
// intake. The full hinted gene stays schema-invalid until the SDK catches up — surfaced by the advisory
|
|
119
|
+
// Structural gate validates the gep-sdk-known CORE. routing_hint / tool_policy / generation_meta are v2-delta
|
|
120
|
+
// (pending a gene-schema bump), so strip them before the check — else additionalProperties:false rejects the
|
|
121
|
+
// gene at intake. The full hinted gene stays schema-invalid until the SDK catches up — surfaced by the advisory
|
|
94
122
|
// validateWire preview (evolver_gep_build) and enforced by the hub on receipt, NOT by a local egress gate
|
|
95
123
|
// (the publish/egress path is sanitize-only). Same v2-delta contract Capsule's proof_of_work had pre-1.11.0.
|
|
96
124
|
const v = validateWire(stripGeneHints(gene));
|
|
@@ -4,6 +4,7 @@ import { type GeneHealth } from './geneHealth.js';
|
|
|
4
4
|
import type { GeneLearningView } from '../assetstore/learningHistory.js';
|
|
5
5
|
import type { AssetRecord } from '../assetstore/provider.js';
|
|
6
6
|
import { type ExplorationInput } from './exploration.js';
|
|
7
|
+
import type { GenerationSource } from '../wire/index.js';
|
|
7
8
|
/** 一个候选 gene 的选择期素材. */
|
|
8
9
|
export interface GeneCandidateInput {
|
|
9
10
|
geneId: string;
|
|
@@ -43,6 +44,11 @@ export interface GeneCandidateInput {
|
|
|
43
44
|
* effect, so the factor is dormant until a caller injects the signal (default-off).
|
|
44
45
|
*/
|
|
45
46
|
reuseAdjust?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Authoritative provenance tag from Gene.generation_meta.source. The legacy `gene_distilled_` prefix remains a
|
|
49
|
+
* fallback only for old candidates that do not carry this field.
|
|
50
|
+
*/
|
|
51
|
+
generationSource?: GenerationSource;
|
|
46
52
|
}
|
|
47
53
|
export interface SelectionInput {
|
|
48
54
|
signals: readonly string[];
|
|
@@ -88,6 +88,14 @@ function matchesScoredIdentity(a, b) {
|
|
|
88
88
|
function withoutCandidateIdentity(scored, c) {
|
|
89
89
|
return scored.filter((s) => !matchesCandidateIdentity(s, c));
|
|
90
90
|
}
|
|
91
|
+
function isReusableGenerationSource(source) {
|
|
92
|
+
return source === 'distilled' || source === 'evolved';
|
|
93
|
+
}
|
|
94
|
+
function isReusableFallbackCandidate(c) {
|
|
95
|
+
if (c.generationSource !== undefined)
|
|
96
|
+
return isReusableGenerationSource(c.generationSource);
|
|
97
|
+
return isDistilledGeneId(c.geneId);
|
|
98
|
+
}
|
|
91
99
|
function forcedSelection(input, scored, scoreForcedFallback, floor) {
|
|
92
100
|
const forcedGeneId = input.forcedGeneId?.trim();
|
|
93
101
|
if (!forcedGeneId)
|
|
@@ -116,7 +124,7 @@ function forcedSelection(input, scored, scoreForcedFallback, floor) {
|
|
|
116
124
|
forcedFallback.reasons.push('forced gene rejected: epigenetic suppression');
|
|
117
125
|
return { scoredForChoice: scoredCandidatesOnly, forceRejected: true };
|
|
118
126
|
}
|
|
119
|
-
if (!
|
|
127
|
+
if (!isReusableFallbackCandidate(fallback)) {
|
|
120
128
|
forcedFallback.reasons.push('forced gene rejected: suppressed fallback');
|
|
121
129
|
return { scoredForChoice: scoredCandidatesOnly, forceRejected: true };
|
|
122
130
|
}
|
|
@@ -141,15 +149,15 @@ export const engineHealthSelection = {
|
|
|
141
149
|
let selectedAssetId = forced.selectedAssetId ?? chosen?.assetId;
|
|
142
150
|
if (!selectedGeneId) {
|
|
143
151
|
// Issue #97 (ported from v1 selector.js): no candidate matched any live signal (every gene scored <= 0). Rather
|
|
144
|
-
// than fall straight through to a blind innovate, reuse a *distilled* gene if one is available.
|
|
145
|
-
//
|
|
146
|
-
// evolved
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
152
|
+
// than fall straight through to a blind innovate, reuse a *distilled* (or evolved) gene if one is available.
|
|
153
|
+
// The fallback pool is built by candidateAssembly, which now filters by generation_meta.source ∈ {distilled,
|
|
154
|
+
// evolved} (V1 #302), falling back to the `gene_distilled_` id namespace for legacy genes. The id-prefix check
|
|
155
|
+
// below is a defense-in-depth filter for callers that bypass candidateAssembly; the authoritative provenance
|
|
156
|
+
// lives on generation_meta (see geneGenerationSource). The safety rests on the upstream gates (trust #30 /
|
|
157
|
+
// review #89-91 / signal-ban / inert-ban #195) plus the epigenetic skip below, not on a skill-provenance
|
|
158
|
+
// low-blast-radius assumption. We skip any distilled fallback epigenetically suppressed in this env; v2's
|
|
159
|
+
// event-log-derived epigeneticPenalty is the analog of v1's asset-mark hard suppression (related band, not the
|
|
160
|
+
// identical predicate). No usable distilled fallback → stay null so the caller still innovates (the contract).
|
|
153
161
|
//
|
|
154
162
|
// v1 #97 parity: the distilled fallback is a *no-signal-match* last resort — it must fire only when NOTHING
|
|
155
163
|
// scored above zero, NOT merely when nothing cleared a positive `floor`. Otherwise a real but weak signal match
|
|
@@ -157,7 +165,7 @@ export const engineHealthSelection = {
|
|
|
157
165
|
// keeps such a match. When candidates scored > 0 but below floor, defer to V2's floor policy (innovate).
|
|
158
166
|
const noSignalMatch = !forced.forceRejected && forced.scoredForChoice.every((s) => s.score <= 0);
|
|
159
167
|
const fb = noSignalMatch
|
|
160
|
-
? (input.distilledFallback ?? []).find((c) =>
|
|
168
|
+
? (input.distilledFallback ?? []).find((c) => isReusableFallbackCandidate(c) && (c.epigeneticPenalty ?? 0) === 0)
|
|
161
169
|
: undefined;
|
|
162
170
|
if (fb) {
|
|
163
171
|
const fbScored = scoreCandidate(input.signals, fb);
|
package/dist/algo/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './exploration.js';
|
|
|
3
3
|
export * from './epigenetics.js';
|
|
4
4
|
export * from './confidence.js';
|
|
5
5
|
export * from './capabilityCandidates.js';
|
|
6
|
+
export * from './conversationSniffer.js';
|
|
6
7
|
export * from './geneIntake.js';
|
|
7
8
|
export * from './candidateAssembly.js';
|
|
8
9
|
export * from './genePromotion.js';
|
package/dist/algo/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export * from './exploration.js';
|
|
|
3
3
|
export * from './epigenetics.js';
|
|
4
4
|
export * from './confidence.js';
|
|
5
5
|
export * from './capabilityCandidates.js';
|
|
6
|
+
export * from './conversationSniffer.js';
|
|
6
7
|
export * from './geneIntake.js';
|
|
7
8
|
export * from './candidateAssembly.js';
|
|
8
9
|
export * from './genePromotion.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AssetKind } from './provider.js';
|
|
2
|
+
export type AssetSyncSource = 'hub';
|
|
3
|
+
export type AssetSyncScope = 'purchased' | 'published';
|
|
4
|
+
export interface AssetSyncRecord {
|
|
5
|
+
assetId: string;
|
|
6
|
+
type: Extract<AssetKind, 'Gene' | 'Capsule'>;
|
|
7
|
+
source: AssetSyncSource;
|
|
8
|
+
scope: AssetSyncScope;
|
|
9
|
+
syncedAt: string;
|
|
10
|
+
remoteAssetId: string;
|
|
11
|
+
logicalId?: string;
|
|
12
|
+
status?: string;
|
|
13
|
+
forced?: true;
|
|
14
|
+
collisionWithAssetId?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare class AssetSyncLedger {
|
|
17
|
+
private readonly now;
|
|
18
|
+
private readonly path;
|
|
19
|
+
private readonly index;
|
|
20
|
+
private loaded;
|
|
21
|
+
constructor(baseDir: string, now?: () => number);
|
|
22
|
+
append(rec: Omit<AssetSyncRecord, 'syncedAt'> & {
|
|
23
|
+
syncedAt?: string;
|
|
24
|
+
}): AssetSyncRecord;
|
|
25
|
+
get(assetId: string): AssetSyncRecord | null;
|
|
26
|
+
list(): AssetSyncRecord[];
|
|
27
|
+
private load;
|
|
28
|
+
}
|