@evomap/evolver-core 2.0.0-beta.19 → 2.0.0-beta.22
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 -5
- package/dist/algo/bans.d.ts +10 -1
- package/dist/algo/bans.js +56 -6
- package/dist/algo/candidateAssembly.d.ts +25 -2
- package/dist/algo/candidateAssembly.js +143 -20
- package/dist/algo/capabilityCandidates.js +10 -0
- package/dist/algo/cycleEngine.d.ts +40 -5
- package/dist/algo/cycleEngine.js +277 -37
- package/dist/algo/evolutionEvent.d.ts +26 -0
- package/dist/algo/evolutionEvent.js +32 -0
- package/dist/algo/exploration.d.ts +7 -0
- package/dist/algo/exploration.js +16 -3
- package/dist/algo/geneHealth.d.ts +34 -1
- package/dist/algo/geneHealth.js +42 -0
- package/dist/algo/geneIntake.d.ts +46 -1
- package/dist/algo/geneIntake.js +121 -10
- package/dist/algo/geneSelection.d.ts +115 -6
- package/dist/algo/geneSelection.js +459 -32
- package/dist/algo/index.d.ts +4 -1
- package/dist/algo/index.js +4 -1
- package/dist/algo/kautoProjection.d.ts +41 -0
- package/dist/algo/kautoProjection.js +95 -0
- package/dist/algo/kautoValidator.d.ts +68 -0
- package/dist/algo/kautoValidator.js +256 -0
- package/dist/algo/orchestrator.d.ts +14 -1
- package/dist/algo/orchestrator.js +9 -2
- package/dist/algo/solidify.d.ts +11 -2
- package/dist/algo/solidify.js +37 -7
- package/dist/algo/ucb1.d.ts +53 -0
- package/dist/algo/ucb1.js +156 -0
- package/dist/assetrepair/hubRejection.d.ts +12 -0
- package/dist/assetrepair/hubRejection.js +109 -0
- package/dist/assetrepair/index.d.ts +2 -0
- package/dist/assetrepair/index.js +2 -0
- package/dist/assetrepair/repair.d.ts +33 -0
- package/dist/assetrepair/repair.js +155 -0
- package/dist/assetstore/assetSidecarRecords.js +5 -1
- package/dist/assetstore/assetStoreHealth.d.ts +2 -3
- package/dist/assetstore/assetStoreHealth.js +4 -10
- package/dist/assetstore/foreignJsonlSource.d.ts +48 -0
- package/dist/assetstore/foreignJsonlSource.js +150 -0
- package/dist/assetstore/index.d.ts +3 -0
- package/dist/assetstore/index.js +3 -0
- package/dist/assetstore/learningHistory.js +3 -3
- package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
- package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
- package/dist/assetstore/localJsonl.d.ts +8 -0
- package/dist/assetstore/localJsonl.js +128 -3
- package/dist/assetstore/provenance.d.ts +38 -3
- package/dist/assetstore/provenance.js +103 -45
- package/dist/assetstore/provider.d.ts +4 -0
- package/dist/assetstore/reviewFilter.d.ts +19 -1
- package/dist/assetstore/reviewFilter.js +36 -0
- package/dist/assetstore/unionReadStore.d.ts +25 -0
- package/dist/assetstore/unionReadStore.js +119 -0
- package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
- package/dist/benchmark/antiGeneBenchmark.js +4 -3
- package/dist/benchmark/antiGeneRollout.d.ts +2 -0
- package/dist/benchmark/antiGeneRollout.js +4 -3
- package/dist/benchmark/index.d.ts +2 -1
- package/dist/benchmark/index.js +2 -1
- package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
- package/dist/benchmark/selectionFlatAbstention.js +481 -0
- package/dist/bootstrap/envFingerprint.d.ts +9 -0
- package/dist/bootstrap/envFingerprint.js +5 -0
- package/dist/bootstrap/index.d.ts +2 -1
- package/dist/bootstrap/index.js +2 -1
- package/dist/bootstrap/lifecycleBootstrap.d.ts +111 -0
- package/dist/bootstrap/lifecycleBootstrap.js +433 -0
- package/dist/bootstrap/v1EnvCompat.d.ts +3 -0
- package/dist/bootstrap/v1EnvCompat.js +48 -1
- package/dist/events/eventArchive.d.ts +2 -0
- package/dist/events/eventArchive.js +13 -3
- package/dist/events/eventSchema.d.ts +7 -7
- package/dist/events/eventStore.d.ts +2 -0
- package/dist/events/eventStore.js +5 -1
- package/dist/events/ingest.d.ts +2 -1
- package/dist/events/ingest.js +6 -0
- package/dist/exec/autoExec.d.ts +30 -3
- package/dist/exec/autoExec.js +294 -29
- package/dist/exec/autonomousCycle.d.ts +7 -0
- package/dist/exec/autonomousCycle.js +2 -0
- package/dist/exec/claudeBridge.d.ts +28 -6
- package/dist/exec/claudeBridge.js +411 -258
- package/dist/exec/executionBinding.d.ts +414 -0
- package/dist/exec/executionBinding.js +588 -0
- package/dist/exec/index.d.ts +1 -0
- package/dist/exec/index.js +1 -0
- package/dist/exec/proofOfWork.d.ts +1 -1
- package/dist/exec/proofOfWork.js +2 -2
- package/dist/exec/runnerRegistry.d.ts +5 -0
- package/dist/exec/runnerRegistry.js +54 -14
- package/dist/hub/capability.d.ts +40 -1
- package/dist/hub/conversationDistiller.d.ts +19 -0
- package/dist/hub/conversationDistiller.js +115 -37
- package/dist/hub/fake.d.ts +1 -0
- package/dist/hub/fake.js +1 -0
- package/dist/hub/index.d.ts +1 -0
- package/dist/hub/index.js +1 -0
- package/dist/hub/recipeCompose.d.ts +27 -0
- package/dist/hub/recipeCompose.js +90 -0
- package/dist/hub/sanitize.js +119 -5
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mailbox/ipcServer.d.ts +2 -0
- package/dist/mailbox/ipcServer.js +20 -0
- package/dist/mailbox/store.d.ts +52 -2
- package/dist/mailbox/store.js +546 -81
- package/dist/modelCompatibility.d.ts +164 -0
- package/dist/modelCompatibility.js +309 -0
- package/dist/observers/valueDigestObserver.d.ts +9 -0
- package/dist/observers/valueDigestObserver.js +35 -2
- package/dist/ops/cleanup.js +1 -1
- package/dist/ops/evolutionGraphProjection.d.ts +20 -0
- package/dist/ops/evolutionGraphProjection.js +315 -0
- package/dist/ops/index.d.ts +2 -1
- package/dist/ops/index.js +2 -1
- package/dist/ops/selfUpdate.d.ts +8 -0
- package/dist/ops/selfUpdate.js +24 -8
- package/dist/ops/valueOutreach.d.ts +3 -1
- package/dist/ops/valueOutreach.js +5 -1
- package/dist/personality/schema.d.ts +18 -18
- package/dist/schema/evolutionGraph.d.ts +784 -0
- package/dist/schema/evolutionGraph.js +187 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/proofOfWork.d.ts +125 -6
- package/dist/schema/proofOfWork.js +102 -4
- package/dist/shadow/shadowHub.js +1 -0
- package/dist/signals/expand.d.ts +15 -1
- package/dist/signals/expand.js +148 -0
- package/dist/signals/index.d.ts +3 -1
- package/dist/signals/index.js +3 -1
- package/dist/signals/scopeVocabulary.d.ts +75 -0
- package/dist/signals/scopeVocabulary.js +91 -0
- package/dist/signals/taskDomain.d.ts +22 -0
- package/dist/signals/taskDomain.js +43 -0
- package/dist/trace/learningTrace.d.ts +7 -0
- package/dist/trace/learningTrace.js +22 -0
- package/dist/trace/trajectory.d.ts +8 -0
- package/dist/trace/trajectory.js +14 -2
- package/dist/util/fileLock.d.ts +92 -5
- package/dist/util/fileLock.js +809 -70
- package/dist/util/index.d.ts +2 -1
- package/dist/util/index.js +1 -1
- package/dist/verify/index.d.ts +2 -1
- package/dist/verify/index.js +1 -1
- package/dist/verify/sandboxRunner.d.ts +2 -0
- package/dist/verify/sandboxRunner.js +154 -10
- package/dist/verify/sandboxedValidation.d.ts +7 -2
- package/dist/verify/sandboxedValidation.js +102 -24
- package/dist/verify/validation.d.ts +24 -3
- package/dist/verify/validation.js +141 -24
- package/dist/wire/geneHints.d.ts +88 -16
- package/dist/wire/geneHints.js +124 -15
- package/dist/wire/index.d.ts +8 -3
- package/dist/wire/index.js +2 -2
- package/dist/wire/schemaGate.d.ts +21 -0
- package/dist/wire/schemaGate.js +119 -10
- package/package.json +5 -4
|
@@ -592,6 +592,10 @@ export function spawnCapture(cmd, args, opts) {
|
|
|
592
592
|
});
|
|
593
593
|
});
|
|
594
594
|
});
|
|
595
|
+
// Defined input is delivered and closed by stdin.end(opts.input) above. With no input, still close the pipe so
|
|
596
|
+
// EOF-driven CLIs cannot hang.
|
|
597
|
+
if (opts.input === undefined)
|
|
598
|
+
child.stdin?.end();
|
|
595
599
|
});
|
|
596
600
|
}
|
|
597
601
|
/** Map the shared process result into the failure taxonomy used by plain-text runners. */
|
|
@@ -774,6 +778,45 @@ function geminiMessage(value) {
|
|
|
774
778
|
function geminiWarnings(value) {
|
|
775
779
|
return Array.isArray(value) ? value.map(geminiMessage).filter(Boolean) : [];
|
|
776
780
|
}
|
|
781
|
+
/** Accept only native-session-safe ids so Learning Ops never joins on garbage envelope fields. */
|
|
782
|
+
function geminiSessionId(value) {
|
|
783
|
+
if (typeof value !== 'string')
|
|
784
|
+
return undefined;
|
|
785
|
+
const sessionId = value.trim();
|
|
786
|
+
return NATIVE_SESSION_ID_PATTERN.test(sessionId) ? sessionId : undefined;
|
|
787
|
+
}
|
|
788
|
+
function withGeminiSessionId(result, envelope) {
|
|
789
|
+
const sessionId = geminiSessionId(envelope.session_id);
|
|
790
|
+
return sessionId !== undefined ? { ...result, sessionId } : result;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Gemini sometimes prints non-JSON diagnostics before the structured envelope.
|
|
794
|
+
* Prefer pure JSON; otherwise accept the last top-level JSON object in stdout.
|
|
795
|
+
*/
|
|
796
|
+
function parseGeminiStdout(stdout) {
|
|
797
|
+
const trimmed = stdout.trim();
|
|
798
|
+
if (!trimmed)
|
|
799
|
+
return null;
|
|
800
|
+
try {
|
|
801
|
+
const parsed = JSON.parse(trimmed);
|
|
802
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
803
|
+
return parsed;
|
|
804
|
+
}
|
|
805
|
+
catch {
|
|
806
|
+
// fall through to last-object recovery
|
|
807
|
+
}
|
|
808
|
+
for (let index = trimmed.lastIndexOf('{'); index >= 0; index = trimmed.lastIndexOf('{', index - 1)) {
|
|
809
|
+
try {
|
|
810
|
+
const parsed = JSON.parse(trimmed.slice(index));
|
|
811
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
812
|
+
return parsed;
|
|
813
|
+
}
|
|
814
|
+
catch {
|
|
815
|
+
// keep scanning earlier braces
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
777
820
|
/** Interpret one bounded Gemini subprocess result. Structured output and diagnostics require complete capture. */
|
|
778
821
|
export function classifyGeminiRunnerResult(result, timeoutMs) {
|
|
779
822
|
if (result.termination === 'timeout') {
|
|
@@ -791,14 +834,8 @@ export function classifyGeminiRunnerResult(result, timeoutMs) {
|
|
|
791
834
|
exitCode: result.code,
|
|
792
835
|
};
|
|
793
836
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
const parsed = JSON.parse(result.stdout);
|
|
797
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
798
|
-
throw new Error('JSON object required');
|
|
799
|
-
envelope = parsed;
|
|
800
|
-
}
|
|
801
|
-
catch {
|
|
837
|
+
const envelope = parseGeminiStdout(result.stdout);
|
|
838
|
+
if (!envelope) {
|
|
802
839
|
const error = result.stderr || (result.code === 0 ? 'gemini returned invalid JSON output' : `gemini exited with code ${String(result.code)}`);
|
|
803
840
|
return { ok: false, output: result.stdout, error, failureKind: result.code === 0 ? 'invalid_output' : 'non_zero_exit', exitCode: result.code };
|
|
804
841
|
}
|
|
@@ -808,19 +845,19 @@ export function classifyGeminiRunnerResult(result, timeoutMs) {
|
|
|
808
845
|
? GEMINI_TERMINATION_WARNINGS.find(({ pattern }) => warnings.some((warning) => pattern.test(warning)))?.error
|
|
809
846
|
: undefined;
|
|
810
847
|
if (terminationError) {
|
|
811
|
-
return { ok: false, output: geminiMessage(envelope.response), error: terminationError, failureKind: 'runtime_error', exitCode: result.code };
|
|
848
|
+
return withGeminiSessionId({ ok: false, output: geminiMessage(envelope.response), error: terminationError, failureKind: 'runtime_error', exitCode: result.code }, envelope);
|
|
812
849
|
}
|
|
813
850
|
const denial = [structuredError, ...warnings, result.stderr].find((message) => GEMINI_PERMISSION_DENIAL_RE.test(message));
|
|
814
851
|
if (denial) {
|
|
815
|
-
return { ok: false, output: geminiMessage(envelope.response), error: denial, failureKind: 'permission_denied', exitCode: result.code };
|
|
852
|
+
return withGeminiSessionId({ ok: false, output: geminiMessage(envelope.response), error: denial, failureKind: 'permission_denied', exitCode: result.code }, envelope);
|
|
816
853
|
}
|
|
817
854
|
if (result.code !== 0) {
|
|
818
|
-
return { ok: false, output: geminiMessage(envelope.response), error: structuredError || result.stderr || `gemini exited with code ${String(result.code)}`, failureKind: 'non_zero_exit', exitCode: result.code };
|
|
855
|
+
return withGeminiSessionId({ ok: false, output: geminiMessage(envelope.response), error: structuredError || result.stderr || `gemini exited with code ${String(result.code)}`, failureKind: 'non_zero_exit', exitCode: result.code }, envelope);
|
|
819
856
|
}
|
|
820
857
|
if (structuredError) {
|
|
821
|
-
return { ok: false, output: geminiMessage(envelope.response), error: structuredError, failureKind: 'runtime_error', exitCode: result.code };
|
|
858
|
+
return withGeminiSessionId({ ok: false, output: geminiMessage(envelope.response), error: structuredError, failureKind: 'runtime_error', exitCode: result.code }, envelope);
|
|
822
859
|
}
|
|
823
|
-
return { ok: true, output: geminiMessage(envelope.response), exitCode: result.code };
|
|
860
|
+
return withGeminiSessionId({ ok: true, output: geminiMessage(envelope.response), exitCode: result.code }, envelope);
|
|
824
861
|
}
|
|
825
862
|
/** Build verified Gemini CLI argv. The prompt is appended separately as one argv element with shell:false. */
|
|
826
863
|
export function geminiRunnerArgs(opts = {}) {
|
|
@@ -1014,7 +1051,10 @@ export function makeCursorHeadlessRunner(opts = {}, platform = process.platform)
|
|
|
1014
1051
|
};
|
|
1015
1052
|
}
|
|
1016
1053
|
const RUNNER_SPECS = {
|
|
1017
|
-
|
|
1054
|
+
// AWS_ prefix is allowlisted so the claude CLI can authenticate against Amazon Bedrock (CLAUDE_CODE_USE_BEDROCK=1
|
|
1055
|
+
// + AWS_REGION/AWS_PROFILE or AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY). This is a legitimate runner-auth channel,
|
|
1056
|
+
// symmetric to CURSOR_/OPENAI_ for the other runners; no other runner inherits it.
|
|
1057
|
+
claude: { name: 'claude', makeRunner: makeClaudeHeadlessRunner, envAllow: { prefixes: ['ANTHROPIC_', 'CLAUDE_', 'AWS_'] } },
|
|
1018
1058
|
codex: { name: 'codex', makeRunner: makeCodexHeadlessRunner, envAllow: { prefixes: ['OPENAI_', 'CODEX_'] } },
|
|
1019
1059
|
// cursor keeps only its OWN auth env (CURSOR_); like every runner it never inherits another's vendor key.
|
|
1020
1060
|
cursor: { name: 'cursor', makeRunner: makeCursorHeadlessRunner, envAllow: { prefixes: ['CURSOR_'] } },
|
package/dist/hub/capability.d.ts
CHANGED
|
@@ -238,12 +238,27 @@ export interface RecipeExpressRequest {
|
|
|
238
238
|
export interface RecipeExpressionReceipt extends RecipeReceipt {
|
|
239
239
|
organismId?: string;
|
|
240
240
|
}
|
|
241
|
+
/** Search/list published recipes. Empty `q` is valid; adapters may fall through to list. */
|
|
242
|
+
export interface RecipeSearchRequest {
|
|
243
|
+
q?: string;
|
|
244
|
+
limit?: number;
|
|
245
|
+
cursor?: string;
|
|
246
|
+
sort?: string;
|
|
247
|
+
}
|
|
248
|
+
export interface RecipeSearchReceipt {
|
|
249
|
+
recipes: unknown[];
|
|
250
|
+
nextCursor?: string;
|
|
251
|
+
hasMore?: boolean;
|
|
252
|
+
raw: unknown;
|
|
253
|
+
}
|
|
241
254
|
/** Optional Hub recipe/DNA capability. Core stays wire-agnostic; adapters own REST shape. */
|
|
242
255
|
export interface RecipeCapability {
|
|
243
256
|
create(request: RecipeCreateRequest): Promise<RecipeReceipt>;
|
|
244
257
|
publish(recipeId: string, options?: RecipePublishOptions): Promise<RecipeReceipt>;
|
|
245
258
|
get(recipeId: string): Promise<RecipeFetchReceipt>;
|
|
246
259
|
express(recipeId: string, request?: RecipeExpressRequest): Promise<RecipeExpressionReceipt>;
|
|
260
|
+
search(request?: RecipeSearchRequest): Promise<RecipeSearchReceipt>;
|
|
261
|
+
list(request?: RecipeSearchRequest): Promise<RecipeSearchReceipt>;
|
|
247
262
|
}
|
|
248
263
|
export type HubCapabilityName = 'publish' | 'fetch' | 'search' | 'task' | 'mailbox' | 'auth' | 'audit' | 'air_gap' | 'tenant_isolation' | 'marketplace' | 'economy' | 'questions' | 'recipes' | 'agent_directory' | 'learning_assets';
|
|
249
264
|
/** hub 能力清单(可选). core 据此动态适配 UI/行为, "按需耦合". */
|
|
@@ -300,6 +315,30 @@ export interface AuthProvider {
|
|
|
300
315
|
/** 撤销凭证(机器丢失/泄露). */
|
|
301
316
|
revoke(credentialId: string): Promise<void>;
|
|
302
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* By-id fetch is content-addressed by default. Integrity-sensitive callers may
|
|
320
|
+
* explicitly opt in to receiving a schema-valid delivery whose declared
|
|
321
|
+
* asset_id matches the request while its current content hash does not.
|
|
322
|
+
*/
|
|
323
|
+
export interface FetchAssetByIdOptions {
|
|
324
|
+
allowUnverifiedExactIdentity?: boolean;
|
|
325
|
+
}
|
|
326
|
+
/** Why a by-id fetch produced no asset. `absent` is the ONLY one that means the hub does not have it. */
|
|
327
|
+
export type AssetDeliveryRejection = 'identity_mismatch' | 'revoked' | 'ambiguous' | 'unverified_not_allowed';
|
|
328
|
+
/**
|
|
329
|
+
* The outcome of a by-id fetch. `verified:false` means the delivery is bound to the requested id but its body
|
|
330
|
+
* hashes to something else — usable only under quarantine, never as canonical content.
|
|
331
|
+
*/
|
|
332
|
+
export type AssetDeliveryOutcome = {
|
|
333
|
+
status: 'delivered';
|
|
334
|
+
asset: AssetRecord;
|
|
335
|
+
verified: boolean;
|
|
336
|
+
} | {
|
|
337
|
+
status: 'absent';
|
|
338
|
+
} | {
|
|
339
|
+
status: 'rejected';
|
|
340
|
+
reason: AssetDeliveryRejection;
|
|
341
|
+
};
|
|
303
342
|
export interface HubCapability {
|
|
304
343
|
/**
|
|
305
344
|
* 异步发布. 公版 /a2a/publish 收 **bundle**[Gene,Capsule,(EvolutionEvent)](一个 cycle 的产物一起发, +GDI);
|
|
@@ -311,7 +350,7 @@ export interface HubCapability {
|
|
|
311
350
|
/** 搜索资产. 与 fetch 同 shape, 差异在 hub 侧召回策略. */
|
|
312
351
|
search(query: HubQuery): Promise<AssetRecord[]>;
|
|
313
352
|
/** 可选: 按 content-addressed asset_id 拉单个 full asset. private adapter/proxy 用它保证只 fetch 一个 winner. */
|
|
314
|
-
fetchAssetById?(assetId: string): Promise<AssetRecord | null>;
|
|
353
|
+
fetchAssetById?(assetId: string, options?: FetchAssetByIdOptions): Promise<AssetRecord | null>;
|
|
315
354
|
/** 可选: 报告某个资产被复用后的结果. PHub/enterprise adapter 实现; 不支持时 runtime 应明确降级. */
|
|
316
355
|
recordReuseResult?(report: ReuseResultReport): Promise<ReuseResultReceipt>;
|
|
317
356
|
/** 可选: 列出 hub 端 runtime learning assets. Missing means the adapter does not support this MVP surface. */
|
|
@@ -43,6 +43,7 @@ interface NormalizedExecution {
|
|
|
43
43
|
files: number;
|
|
44
44
|
lines: number;
|
|
45
45
|
};
|
|
46
|
+
untrustedStatusClaim: boolean;
|
|
46
47
|
}
|
|
47
48
|
interface NormalizedConversation {
|
|
48
49
|
text: string;
|
|
@@ -65,6 +66,22 @@ export interface QualityGate {
|
|
|
65
66
|
export interface ConversationDistillOptions {
|
|
66
67
|
persist?: boolean;
|
|
67
68
|
store?: AssetStoreProvider;
|
|
69
|
+
/**
|
|
70
|
+
* 仅由受信任的宿主验证器注入。HTTP/MCP 请求体中的 execution 不能替代此证据。
|
|
71
|
+
*/
|
|
72
|
+
verifiedExecution?: ConversationDistillVerifiedExecution;
|
|
73
|
+
}
|
|
74
|
+
export interface ConversationDistillVerifiedExecution {
|
|
75
|
+
trace: ReadonlyArray<{
|
|
76
|
+
command: string;
|
|
77
|
+
exit: number;
|
|
78
|
+
summary?: string;
|
|
79
|
+
}>;
|
|
80
|
+
validation?: readonly string[];
|
|
81
|
+
blast_radius?: {
|
|
82
|
+
files?: number;
|
|
83
|
+
lines?: number;
|
|
84
|
+
};
|
|
68
85
|
}
|
|
69
86
|
export type ConversationDistillResult = {
|
|
70
87
|
ok: false;
|
|
@@ -80,6 +97,8 @@ export type ConversationDistillResult = {
|
|
|
80
97
|
signals: string[];
|
|
81
98
|
gene: AssetRecord;
|
|
82
99
|
capsule: AssetRecord;
|
|
100
|
+
/** 仅当质量闸门通过且草稿可以进入发布流程时为 true。 */
|
|
101
|
+
publishable: boolean;
|
|
83
102
|
};
|
|
84
103
|
export declare function inferSignals(text: string, providedSignals?: unknown): string[];
|
|
85
104
|
export declare function evaluateGate(input: ConversationDistillInput, normalized: NormalizedConversation): QualityGate;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { intakeGene } from '../algo/geneIntake.js';
|
|
3
|
+
import { redactDeep, redactString } from './sanitize.js';
|
|
4
|
+
import { SCHEMA_VERSION, computeAssetId, stripGeneHints, validateWireDeep } from '../wire/index.js';
|
|
4
5
|
const DEFAULT_SIGNALS = [
|
|
5
6
|
'conversation_distillation',
|
|
6
7
|
'reusable_capability',
|
|
@@ -16,6 +17,7 @@ const SIGNAL_RULES = [
|
|
|
16
17
|
{ signal: 'plugin_integration', re: /\b(plugin|codex|claude|cursor|antigravity|workbuddy|hook|notify)\b|插件|钩子/i },
|
|
17
18
|
{ signal: 'test_verified', re: /\b(test|build|verify|passed|green)\b|测试|验证|通过/i },
|
|
18
19
|
];
|
|
20
|
+
const BUNDLE_PERSISTENCE_UNSUPPORTED = 'bundle_persistence_unsupported';
|
|
19
21
|
function asRecord(v) {
|
|
20
22
|
return v && typeof v === 'object' && !Array.isArray(v) ? v : {};
|
|
21
23
|
}
|
|
@@ -74,37 +76,63 @@ function normalizePublishBlastRadius(value, artifactCount) {
|
|
|
74
76
|
};
|
|
75
77
|
}
|
|
76
78
|
function capsuleContent(normalized) {
|
|
77
|
-
return
|
|
79
|
+
return redactDeep({
|
|
78
80
|
platform: normalized.platform,
|
|
79
81
|
source_thread: normalized.source_thread || null,
|
|
80
82
|
artifacts: normalized.artifacts,
|
|
81
83
|
excerpt: normalized.text.slice(0, 1200),
|
|
82
|
-
})
|
|
84
|
+
});
|
|
83
85
|
}
|
|
84
86
|
function normalizeExecution(input) {
|
|
85
87
|
const execution = asRecord(input.execution);
|
|
86
88
|
const validation = normalizeList(input.validation ?? input.verification ?? execution['validation'], 8, 180);
|
|
89
|
+
const untrustedStatusClaim = execution['status'] !== undefined || execution['ok'] !== undefined;
|
|
90
|
+
let malformedTrace = false;
|
|
87
91
|
const trace = asArray(execution['trace']).map((item) => {
|
|
88
|
-
if (typeof item === 'string')
|
|
89
|
-
|
|
92
|
+
if (typeof item === 'string') {
|
|
93
|
+
malformedTrace = true;
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
90
96
|
const row = asRecord(item);
|
|
91
|
-
if (Object.keys(row).length === 0)
|
|
97
|
+
if (Object.keys(row).length === 0) {
|
|
98
|
+
malformedTrace = true;
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const command = trimText(row['command'] ?? row['cmd'] ?? row['name'] ?? '', 180);
|
|
102
|
+
if (!command) {
|
|
103
|
+
malformedTrace = true;
|
|
92
104
|
return null;
|
|
105
|
+
}
|
|
106
|
+
const rawExit = row['exit'];
|
|
107
|
+
const exit = Number.isInteger(rawExit)
|
|
108
|
+
? Number(rawExit)
|
|
109
|
+
: (typeof row['ok'] === 'boolean' ? (row['ok'] ? 0 : 1) : undefined);
|
|
110
|
+
if (exit === undefined) {
|
|
111
|
+
malformedTrace = true;
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
93
114
|
return {
|
|
94
|
-
command
|
|
95
|
-
exit
|
|
115
|
+
command,
|
|
116
|
+
exit,
|
|
96
117
|
summary: trimText(row['summary'] ?? row['output'] ?? '', 240),
|
|
97
118
|
};
|
|
98
119
|
}).filter((row) => row !== null);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
120
|
+
// 声明的验证命令只是计划,并不能证明命令已经执行。
|
|
121
|
+
// 保留它作为质量信号,但不能为未执行的声明伪造 exit-0 轨迹或成功结果。
|
|
122
|
+
// 只有显式结构化的退出码才可作为证据。调用方声明的 status/ok 字段只是元数据,
|
|
123
|
+
// 不能据此把资产晋级到发布流程。
|
|
124
|
+
const normalizedValidation = new Set(validation);
|
|
125
|
+
const covered = [...normalizedValidation].every((command) => trace.some((row) => row.command === command));
|
|
126
|
+
const ok = trace.length > 0
|
|
127
|
+
&& !malformedTrace
|
|
128
|
+
&& trace.every((t) => t.exit === 0)
|
|
129
|
+
&& covered;
|
|
103
130
|
return {
|
|
104
131
|
status: ok ? 'success' : 'failed',
|
|
105
132
|
trace,
|
|
106
133
|
validation,
|
|
107
134
|
blast_radius: normalizeBlastRadius(execution['blast_radius'] ?? input.blast_radius),
|
|
135
|
+
untrustedStatusClaim,
|
|
108
136
|
};
|
|
109
137
|
}
|
|
110
138
|
function buildStrategy(input) {
|
|
@@ -145,7 +173,9 @@ export function evaluateGate(input, normalized) {
|
|
|
145
173
|
reasons.push('explicit_distill_signal');
|
|
146
174
|
}
|
|
147
175
|
const rawThreshold = Number(input.min_score ?? input.minScore ?? 5);
|
|
148
|
-
|
|
176
|
+
// 调用方可以要求更严格的阈值,但不能把准入策略降为零,
|
|
177
|
+
// 也不能把未经确认的草稿变成可发布资产。
|
|
178
|
+
const threshold = Number.isFinite(rawThreshold) ? Math.max(5, rawThreshold) : 5;
|
|
149
179
|
if (score < threshold)
|
|
150
180
|
return { ok: false, score, threshold, reasons, reason: 'insufficient_reusable_signal' };
|
|
151
181
|
return { ok: true, score, threshold, reasons };
|
|
@@ -178,12 +208,17 @@ export function normalizeConversationInput(input) {
|
|
|
178
208
|
export async function distillConversation(input, opts = {}) {
|
|
179
209
|
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
180
210
|
return { ok: false, status: 'skipped', reason: 'input_object_required' };
|
|
181
|
-
const
|
|
211
|
+
const effectiveInput = opts.verifiedExecution
|
|
212
|
+
? {
|
|
213
|
+
...input,
|
|
214
|
+
execution: opts.verifiedExecution,
|
|
215
|
+
...(opts.verifiedExecution.validation ? { validation: opts.verifiedExecution.validation } : {}),
|
|
216
|
+
}
|
|
217
|
+
: input;
|
|
218
|
+
const normalized = normalizeConversationInput(effectiveInput);
|
|
182
219
|
if (!normalized.summary || normalized.summary.length < 20)
|
|
183
220
|
return { ok: false, status: 'skipped', reason: 'summary_required' };
|
|
184
221
|
const gate = evaluateGate(input, normalized);
|
|
185
|
-
if (!gate.ok)
|
|
186
|
-
return { ok: false, status: 'skipped', reason: gate.reason ?? 'insufficient_reusable_signal', quality: gate, signals: normalized.signals };
|
|
187
222
|
const slug = slugify(input.name ?? input.title ?? normalized.summary);
|
|
188
223
|
const fingerprint = hashInput({
|
|
189
224
|
summary: normalized.summary,
|
|
@@ -192,9 +227,13 @@ export async function distillConversation(input, opts = {}) {
|
|
|
192
227
|
artifacts: normalized.artifacts,
|
|
193
228
|
});
|
|
194
229
|
const category = inferCategory(normalized.signals, normalized.text);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
230
|
+
// 只有质量门禁与受信任宿主证据同时通过时,Capsule 才允许广播。
|
|
231
|
+
const publishable = gate.ok && normalized.execution.status === 'success' && opts.verifiedExecution !== undefined;
|
|
232
|
+
const declaredValidation = normalized.execution.validation;
|
|
233
|
+
// GEP Gene schema 要求非空 validation。调用方没有计划时保留旧版 Node 身份检查,
|
|
234
|
+
// 但它只用于形成可审阅草稿,绝不能产生 verifier 坐标或发布资格。
|
|
235
|
+
const validation = declaredValidation.length > 0 ? declaredValidation : ['node --version'];
|
|
236
|
+
const intake = intakeGene({
|
|
198
237
|
id: `gene_conversation_${slug}_${fingerprint}`,
|
|
199
238
|
summary: normalized.summary,
|
|
200
239
|
category,
|
|
@@ -204,18 +243,28 @@ export async function distillConversation(input, opts = {}) {
|
|
|
204
243
|
'The conversation includes enough evidence to reconstruct when and how to use it.',
|
|
205
244
|
],
|
|
206
245
|
strategy: normalized.strategy,
|
|
207
|
-
validation
|
|
246
|
+
validation,
|
|
208
247
|
constraints: { max_files: 20, forbidden_paths: ['.git', 'node_modules', '.env'] },
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
248
|
+
generation_meta: { source: 'distilled' },
|
|
249
|
+
model_name: normalized.model,
|
|
250
|
+
});
|
|
251
|
+
if (!intake.ok || !intake.gene) {
|
|
252
|
+
return {
|
|
253
|
+
ok: false,
|
|
254
|
+
status: 'skipped',
|
|
255
|
+
reason: 'gene_intake_failed',
|
|
213
256
|
quality: gate,
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
};
|
|
218
|
-
|
|
257
|
+
signals: normalized.signals,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
const gene = { ...intake.gene };
|
|
261
|
+
// intakeGene 会从任何非空 validation 计划推导 verifier_profile。旧版身份检查并非
|
|
262
|
+
// 行为验证证据,因此必须移除该坐标并重新绑定内容寻址 ID,保持严格 K_auto fail-closed。
|
|
263
|
+
if (declaredValidation.length === 0) {
|
|
264
|
+
delete gene.verifier_profile;
|
|
265
|
+
gene.asset_id = '';
|
|
266
|
+
gene.asset_id = computeAssetId(gene) ?? '';
|
|
267
|
+
}
|
|
219
268
|
const capsule = {
|
|
220
269
|
type: 'Capsule',
|
|
221
270
|
schema_version: SCHEMA_VERSION,
|
|
@@ -226,13 +275,13 @@ export async function distillConversation(input, opts = {}) {
|
|
|
226
275
|
confidence: Math.min(0.95, 0.5 + gate.score / 20),
|
|
227
276
|
blast_radius: normalizePublishBlastRadius(normalized.execution.blast_radius, normalized.artifacts.length),
|
|
228
277
|
outcome: { status: normalized.execution.status, score: normalized.execution.status === 'success' ? 0.82 : 0.35 },
|
|
229
|
-
source_type: '
|
|
278
|
+
source_type: 'generated',
|
|
230
279
|
strategy: normalized.strategy,
|
|
231
280
|
execution_trace: normalized.execution.trace,
|
|
232
|
-
a2a: { eligible_to_broadcast:
|
|
281
|
+
a2a: { eligible_to_broadcast: publishable },
|
|
233
282
|
content: capsuleContent(normalized),
|
|
234
|
-
diff:
|
|
235
|
-
reused_asset_id:
|
|
283
|
+
diff: null,
|
|
284
|
+
reused_asset_id: null,
|
|
236
285
|
env_fingerprint: {
|
|
237
286
|
platform: normalized.platform,
|
|
238
287
|
source_thread: normalized.source_thread || null,
|
|
@@ -247,14 +296,43 @@ export async function distillConversation(input, opts = {}) {
|
|
|
247
296
|
asset_id: '',
|
|
248
297
|
};
|
|
249
298
|
capsule.asset_id = computeAssetId(capsule) ?? '';
|
|
250
|
-
|
|
299
|
+
// 质量准入可以保持宽松,但草稿仍必须是有效的 wire 记录。
|
|
300
|
+
// 在持久化或排队前捕获生产者与 schema 的漂移。
|
|
301
|
+
if (!validateWireDeep(stripGeneHints(gene)).ok || !validateWireDeep(capsule).ok) {
|
|
302
|
+
return {
|
|
303
|
+
ok: false,
|
|
304
|
+
status: 'skipped',
|
|
305
|
+
reason: 'wire_schema_invalid',
|
|
306
|
+
quality: gate,
|
|
307
|
+
signals: normalized.signals,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
// 只有受信任宿主验证器提供的成功 execution 才能解锁发布。
|
|
311
|
+
// 本地调用方显式要求 persist 时,可以保留没有成功证据的失败草稿供人工复核,
|
|
312
|
+
// 但请求体伪造的 exit-0 轨迹不能借此写入看似成功的资产。
|
|
313
|
+
const persistRequested = (opts.persist ?? input.persist) === true;
|
|
314
|
+
const failedDraftPersistence = !opts.verifiedExecution
|
|
315
|
+
&& normalized.execution.status === 'failed'
|
|
316
|
+
&& !normalized.execution.untrustedStatusClaim;
|
|
317
|
+
const persist = persistRequested && (publishable || (gate.ok && failedDraftPersistence));
|
|
251
318
|
if (persist && opts.store) {
|
|
252
|
-
|
|
253
|
-
|
|
319
|
+
// Gene/Capsule 是一个可发布单元。只提供单条写入的 provider 无法恢复
|
|
320
|
+
// 两次写入之间的崩溃窗口;不要用兼容回退制造孤儿 Gene。
|
|
321
|
+
if (typeof opts.store.putBundle !== 'function') {
|
|
322
|
+
return {
|
|
323
|
+
ok: false,
|
|
324
|
+
status: 'skipped',
|
|
325
|
+
reason: BUNDLE_PERSISTENCE_UNSUPPORTED,
|
|
326
|
+
quality: gate,
|
|
327
|
+
signals: normalized.signals,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
await opts.store.putBundle([gene, capsule]);
|
|
254
331
|
}
|
|
255
332
|
return {
|
|
256
333
|
ok: true,
|
|
257
334
|
status: persist && opts.store ? 'stored' : 'draft',
|
|
335
|
+
publishable,
|
|
258
336
|
distill_id: fingerprint,
|
|
259
337
|
quality: gate,
|
|
260
338
|
signals: normalized.signals,
|
package/dist/hub/fake.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export declare class FakeHubCapability implements HubCapability {
|
|
|
24
24
|
}>;
|
|
25
25
|
readonly auth: AuthProvider;
|
|
26
26
|
agentDirectory?: import('./agentDirectory.js').AgentDirectoryCapability;
|
|
27
|
+
recipes?: import('./capability.js').RecipeCapability;
|
|
27
28
|
nextPollAfterMs: number | undefined;
|
|
28
29
|
constructor(opts?: FakeHubOptions);
|
|
29
30
|
/** 测试注入: 排一条 inbound 事件供 poll 拉. */
|
package/dist/hub/fake.js
CHANGED
package/dist/hub/index.d.ts
CHANGED
package/dist/hub/index.js
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AssetRecord, HubCapability } from './capability.js';
|
|
2
|
+
export declare const RECIPE_COMPOSE_FLAG = "compose_recipe";
|
|
3
|
+
export interface RecipeComposeInput {
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
assets?: readonly unknown[];
|
|
7
|
+
source?: unknown;
|
|
8
|
+
[k: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface RecipeComposeResult {
|
|
11
|
+
attempted: boolean;
|
|
12
|
+
ok: boolean;
|
|
13
|
+
reason?: string;
|
|
14
|
+
recipeId?: string;
|
|
15
|
+
status?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function recipeComposeRequested(payload: RecipeComposeInput): boolean;
|
|
18
|
+
export declare function recipeAssetsFromPayload(payload: RecipeComposeInput): {
|
|
19
|
+
gene?: AssetRecord;
|
|
20
|
+
capsule?: AssetRecord;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* After a Gene/Capsule bundle is accepted by Hub, compose a Recipe as the
|
|
24
|
+
* preferred public artifact. Skill Store publish is not the default outbound.
|
|
25
|
+
* Missing recipe capability or missing Gene is a no-op, never a publish failure.
|
|
26
|
+
*/
|
|
27
|
+
export declare function composeRecipeAfterAssetPublish(hub: Pick<HubCapability, 'recipes'>, payload: RecipeComposeInput): Promise<RecipeComposeResult>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export const RECIPE_COMPOSE_FLAG = 'compose_recipe';
|
|
2
|
+
function asRecord(value) {
|
|
3
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
4
|
+
? value
|
|
5
|
+
: null;
|
|
6
|
+
}
|
|
7
|
+
function assetType(asset) {
|
|
8
|
+
const typed = asset['type'] ?? asset['assetType'];
|
|
9
|
+
return typeof typed === 'string' ? typed : '';
|
|
10
|
+
}
|
|
11
|
+
function assetId(asset) {
|
|
12
|
+
const id = asset['asset_id'] ?? asset['assetId'];
|
|
13
|
+
return typeof id === 'string' ? id : '';
|
|
14
|
+
}
|
|
15
|
+
function assetSummary(asset) {
|
|
16
|
+
const summary = asset['summary'] ?? asset['shortTitle'] ?? asset['nlSummary'];
|
|
17
|
+
return typeof summary === 'string' ? summary : '';
|
|
18
|
+
}
|
|
19
|
+
export function recipeComposeRequested(payload) {
|
|
20
|
+
if (payload[RECIPE_COMPOSE_FLAG] === false)
|
|
21
|
+
return false;
|
|
22
|
+
if (payload['publish_recipe'] === false)
|
|
23
|
+
return false;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
function shouldCompose(payload) {
|
|
27
|
+
return recipeComposeRequested(payload);
|
|
28
|
+
}
|
|
29
|
+
export function recipeAssetsFromPayload(payload) {
|
|
30
|
+
const assets = Array.isArray(payload.assets) ? payload.assets : [];
|
|
31
|
+
const records = assets.filter((item) => !!asRecord(item));
|
|
32
|
+
return {
|
|
33
|
+
gene: records.find((asset) => assetType(asset) === 'Gene'),
|
|
34
|
+
capsule: records.find((asset) => assetType(asset) === 'Capsule'),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* After a Gene/Capsule bundle is accepted by Hub, compose a Recipe as the
|
|
39
|
+
* preferred public artifact. Skill Store publish is not the default outbound.
|
|
40
|
+
* Missing recipe capability or missing Gene is a no-op, never a publish failure.
|
|
41
|
+
*/
|
|
42
|
+
export async function composeRecipeAfterAssetPublish(hub, payload) {
|
|
43
|
+
if (!shouldCompose(payload))
|
|
44
|
+
return { attempted: false, ok: true, reason: 'not_requested' };
|
|
45
|
+
const recipes = hub.recipes;
|
|
46
|
+
if (!recipes)
|
|
47
|
+
return { attempted: false, ok: true, reason: 'recipes_unsupported' };
|
|
48
|
+
const { gene, capsule } = recipeAssetsFromPayload(payload);
|
|
49
|
+
if (!gene)
|
|
50
|
+
return { attempted: false, ok: true, reason: 'gene_missing' };
|
|
51
|
+
const geneId = assetId(gene);
|
|
52
|
+
if (!geneId)
|
|
53
|
+
return { attempted: false, ok: true, reason: 'gene_missing' };
|
|
54
|
+
const title = String(payload.title || assetSummary(gene) || 'Reusable recipe').trim().slice(0, 200);
|
|
55
|
+
if (title.length < 3)
|
|
56
|
+
return { attempted: false, ok: true, reason: 'title_too_short' };
|
|
57
|
+
const description = String(payload.description || assetSummary(gene) || title).trim().slice(0, 2000);
|
|
58
|
+
const steps = [
|
|
59
|
+
{ assetId: geneId, assetType: 'Gene', position: 0 },
|
|
60
|
+
...(capsule && assetId(capsule)
|
|
61
|
+
? [{ assetId: assetId(capsule), assetType: 'Capsule', position: 1 }]
|
|
62
|
+
: []),
|
|
63
|
+
];
|
|
64
|
+
try {
|
|
65
|
+
const created = await recipes.create({
|
|
66
|
+
title,
|
|
67
|
+
description,
|
|
68
|
+
steps,
|
|
69
|
+
pricePerExecution: 5,
|
|
70
|
+
currency: 'Credit',
|
|
71
|
+
});
|
|
72
|
+
const recipeId = created.recipeId;
|
|
73
|
+
if (!recipeId)
|
|
74
|
+
return { attempted: true, ok: false, reason: 'recipe_id_missing' };
|
|
75
|
+
const published = await recipes.publish(recipeId);
|
|
76
|
+
return {
|
|
77
|
+
attempted: true,
|
|
78
|
+
ok: true,
|
|
79
|
+
recipeId,
|
|
80
|
+
status: published.status ?? created.status ?? 'published',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return {
|
|
85
|
+
attempted: true,
|
|
86
|
+
ok: false,
|
|
87
|
+
reason: error instanceof Error ? error.message : 'recipe_compose_failed',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|