@evomap/evolver-core 2.0.0-beta.17 → 2.0.0-beta.19
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/dist/algo/candidateAssembly.js +21 -2
- package/dist/algo/cycleEngine.d.ts +12 -0
- package/dist/algo/cycleEngine.js +36 -4
- package/dist/algo/geneHealth.d.ts +2 -2
- package/dist/algo/geneHealth.js +5 -4
- package/dist/algo/geneSelection.d.ts +1 -1
- package/dist/algo/orchestrator.js +9 -2
- package/dist/assetstore/assetSidecarRecords.js +4 -0
- package/dist/assetstore/assetStoreHealth.js +41 -24
- package/dist/assetstore/assetStoreStorage.d.ts +1 -1
- package/dist/assetstore/assetStoreStorage.js +16 -7
- package/dist/assetstore/localJsonl.d.ts +2 -1
- package/dist/assetstore/localJsonl.js +54 -10
- package/dist/assetstore/provenance.d.ts +24 -0
- package/dist/assetstore/provenance.js +219 -12
- package/dist/assetstore/provider.d.ts +20 -1
- package/dist/assetstore/provider.js +34 -1
- package/dist/bootstrap/index.d.ts +2 -1
- package/dist/bootstrap/index.js +2 -1
- package/dist/bootstrap/v1EnvCompat.d.ts +110 -0
- package/dist/bootstrap/v1EnvCompat.js +256 -0
- package/dist/events/public.d.ts +1 -1
- package/dist/events/public.js +1 -1
- package/dist/events/reports.d.ts +2 -0
- package/dist/events/reports.js +4 -0
- package/dist/exec/autoExec.d.ts +18 -1
- package/dist/exec/autoExec.js +24 -9
- package/dist/exec/autonomousCycle.d.ts +19 -4
- package/dist/exec/autonomousCycle.js +63 -13
- package/dist/exec/claudeBridge.d.ts +25 -7
- package/dist/exec/claudeBridge.js +264 -29
- package/dist/exec/prompt.js +5 -1
- package/dist/exec/runnerRegistry.d.ts +68 -26
- package/dist/exec/runnerRegistry.js +307 -72
- package/dist/exec/selfPr.js +1 -7
- package/dist/feedback/envelope.d.ts +61 -0
- package/dist/feedback/envelope.js +168 -0
- package/dist/feedback/index.d.ts +1 -0
- package/dist/feedback/index.js +1 -0
- package/dist/hub/assetCallLog.d.ts +35 -1
- package/dist/hub/assetCallLog.js +124 -1
- package/dist/hub/bindings.d.ts +8 -1
- package/dist/hub/bindings.js +17 -6
- package/dist/hub/capability.d.ts +11 -1
- package/dist/hub/fake.d.ts +2 -2
- package/dist/hub/fake.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/mailbox/dispatch.d.ts +1 -1
- package/dist/mailbox/dispatch.js +22 -6
- package/dist/mailbox/envelope.d.ts +7 -1
- package/dist/mailbox/envelope.js +9 -2
- package/dist/mailbox/ipcServer.d.ts +10 -2
- package/dist/mailbox/ipcServer.js +163 -13
- package/dist/mailbox/store.d.ts +38 -2
- package/dist/mailbox/store.js +416 -27
- package/dist/signals/curriculum.d.ts +55 -0
- package/dist/signals/curriculum.js +202 -0
- package/dist/signals/expand.js +17 -6
- package/dist/signals/index.d.ts +2 -1
- package/dist/signals/index.js +2 -1
- package/dist/strategy/constraintAblation.js +115 -369
- package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
- package/dist/strategy/constraintAblationPredicates.js +339 -0
- package/dist/trace/index.d.ts +2 -1
- package/dist/trace/index.js +2 -1
- package/dist/trace/proxyTurns.d.ts +31 -0
- package/dist/trace/proxyTurns.js +137 -0
- package/dist/verify/validation.d.ts +11 -1
- package/dist/verify/validation.js +31 -0
- package/package.json +4 -1
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
export const PRIORITY_AXES = Object.freeze([
|
|
2
|
+
'task_success',
|
|
3
|
+
'user_preference',
|
|
4
|
+
'quality',
|
|
5
|
+
'safety',
|
|
6
|
+
'cost',
|
|
7
|
+
'latency',
|
|
8
|
+
'other',
|
|
9
|
+
]);
|
|
10
|
+
export const LABELS = Object.freeze(['positive', 'negative', 'mixed', 'neutral']);
|
|
11
|
+
export const ATTENTION_LEVELS = Object.freeze(['full', 'limited', 'skimmed', 'unknown']);
|
|
12
|
+
export const EVIDENCE_KINDS = Object.freeze([
|
|
13
|
+
'evolution_event',
|
|
14
|
+
'evolution_outcome',
|
|
15
|
+
'user_override',
|
|
16
|
+
'review',
|
|
17
|
+
'turn',
|
|
18
|
+
'external',
|
|
19
|
+
]);
|
|
20
|
+
export function clamp01(value) {
|
|
21
|
+
const n = Number(value);
|
|
22
|
+
if (!Number.isFinite(n))
|
|
23
|
+
return 0.5;
|
|
24
|
+
if (n < 0)
|
|
25
|
+
return 0;
|
|
26
|
+
if (n > 1)
|
|
27
|
+
return 1;
|
|
28
|
+
return n;
|
|
29
|
+
}
|
|
30
|
+
export function labelFromScalar(value) {
|
|
31
|
+
const scalar = clamp01(value);
|
|
32
|
+
if (scalar >= 0.6)
|
|
33
|
+
return 'positive';
|
|
34
|
+
if (scalar <= 0.4)
|
|
35
|
+
return 'negative';
|
|
36
|
+
return 'mixed';
|
|
37
|
+
}
|
|
38
|
+
export function normalizeAttention(input) {
|
|
39
|
+
const record = asRecord(input);
|
|
40
|
+
if (!record)
|
|
41
|
+
return { level: 'unknown' };
|
|
42
|
+
const attention = {
|
|
43
|
+
level: enumValue(record['level'], ATTENTION_LEVELS, 'unknown'),
|
|
44
|
+
};
|
|
45
|
+
const observedItems = nonNegativeInteger(record['observed_items']);
|
|
46
|
+
const elapsedMs = nonNegativeInteger(record['elapsed_ms']);
|
|
47
|
+
if (observedItems !== null)
|
|
48
|
+
attention.observed_items = observedItems;
|
|
49
|
+
if (elapsedMs !== null)
|
|
50
|
+
attention.elapsed_ms = elapsedMs;
|
|
51
|
+
return attention;
|
|
52
|
+
}
|
|
53
|
+
export function evidenceRef(kind, id, options = {}) {
|
|
54
|
+
const ref = {
|
|
55
|
+
kind: enumValue(kind, EVIDENCE_KINDS, 'external'),
|
|
56
|
+
id: String(id ?? '').trim() || 'unknown',
|
|
57
|
+
};
|
|
58
|
+
if (typeof options.summary === 'string' && options.summary.trim()) {
|
|
59
|
+
ref.summary = options.summary.trim();
|
|
60
|
+
}
|
|
61
|
+
return ref;
|
|
62
|
+
}
|
|
63
|
+
export function normalizeEvidenceRef(input) {
|
|
64
|
+
const record = asRecord(input);
|
|
65
|
+
if (!record)
|
|
66
|
+
return evidenceRef('external', 'unknown');
|
|
67
|
+
return evidenceRef(record['kind'], record['id'], { summary: record['summary'] });
|
|
68
|
+
}
|
|
69
|
+
export function envelopeUncertainty(scalar, attentionLevel, indecision, conflict) {
|
|
70
|
+
const normalizedScalar = clamp01(scalar);
|
|
71
|
+
const ambiguity = 1 - Math.abs(normalizedScalar - 0.5) * 2;
|
|
72
|
+
const attentionPenalty = {
|
|
73
|
+
full: 0,
|
|
74
|
+
limited: 0.15,
|
|
75
|
+
skimmed: 0.30,
|
|
76
|
+
unknown: 0.20,
|
|
77
|
+
};
|
|
78
|
+
return clamp01(0.10
|
|
79
|
+
+ 0.30 * ambiguity
|
|
80
|
+
+ attentionPenalty[attentionLevel]
|
|
81
|
+
+ (indecision ? 0.25 : 0)
|
|
82
|
+
+ (conflict ? 0.35 : 0));
|
|
83
|
+
}
|
|
84
|
+
export function fromScalarFeedback(options = {}) {
|
|
85
|
+
const input = options ?? {};
|
|
86
|
+
const scalar = clamp01(input.scalar);
|
|
87
|
+
const label = labelFromScalar(scalar);
|
|
88
|
+
const indecision = Boolean(input.indecision) || label === 'mixed';
|
|
89
|
+
const conflict = Boolean(input.conflict);
|
|
90
|
+
const evaluatorAttention = normalizeAttention(input.evaluator_attention ?? input.evaluatorAttention);
|
|
91
|
+
const priorityAxis = enumValue(input.priority_axis ?? input.priorityAxis, PRIORITY_AXES, 'task_success');
|
|
92
|
+
const evidence = input.evidence_ref ?? input.evidenceRef ?? evidenceRef('external', 'unknown');
|
|
93
|
+
return {
|
|
94
|
+
priority_axis: priorityAxis,
|
|
95
|
+
label,
|
|
96
|
+
scalar,
|
|
97
|
+
indecision,
|
|
98
|
+
conflict,
|
|
99
|
+
evaluator_attention: evaluatorAttention,
|
|
100
|
+
evidence_ref: normalizeEvidenceRef(evidence),
|
|
101
|
+
uncertainty: envelopeUncertainty(scalar, evaluatorAttention.level, indecision, conflict),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export function fromOutcomeScalar(outcome, options = {}) {
|
|
105
|
+
const record = asRecord(outcome);
|
|
106
|
+
if (!record)
|
|
107
|
+
return null;
|
|
108
|
+
const scalar = record['user_override'] ?? record['score'];
|
|
109
|
+
if (scalar === null || scalar === undefined)
|
|
110
|
+
return null;
|
|
111
|
+
return fromScalarFeedback({ ...options, scalar });
|
|
112
|
+
}
|
|
113
|
+
export function withConflict(envelope) {
|
|
114
|
+
const evaluatorAttention = normalizeAttention(envelope.evaluator_attention);
|
|
115
|
+
return {
|
|
116
|
+
...envelope,
|
|
117
|
+
conflict: true,
|
|
118
|
+
evaluator_attention: evaluatorAttention,
|
|
119
|
+
uncertainty: envelopeUncertainty(envelope.scalar, evaluatorAttention.level, envelope.indecision, true),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export function withIndecision(envelope) {
|
|
123
|
+
const evaluatorAttention = normalizeAttention(envelope.evaluator_attention);
|
|
124
|
+
return {
|
|
125
|
+
...envelope,
|
|
126
|
+
indecision: true,
|
|
127
|
+
evaluator_attention: evaluatorAttention,
|
|
128
|
+
uncertainty: envelopeUncertainty(envelope.scalar, evaluatorAttention.level, true, envelope.conflict),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export function aggregateFeedbackEnvelopes(envelopes) {
|
|
132
|
+
const list = Array.isArray(envelopes) ? envelopes.filter(Boolean) : [];
|
|
133
|
+
if (list.length === 0)
|
|
134
|
+
return { dominant_label: null, uncertainty: 1, sample_count: 0 };
|
|
135
|
+
const sampleCount = list.length;
|
|
136
|
+
const meanUncertainty = list.reduce((sum, envelope) => sum + clamp01(envelope.uncertainty), 0) / sampleCount;
|
|
137
|
+
const hasPositive = list.some((envelope) => envelope.label === 'positive');
|
|
138
|
+
const hasNegative = list.some((envelope) => envelope.label === 'negative');
|
|
139
|
+
const hasConflict = list.some((envelope) => envelope.conflict) || (hasPositive && hasNegative);
|
|
140
|
+
const hasIndecision = list.some((envelope) => envelope.indecision || envelope.label === 'mixed' || envelope.label === 'neutral');
|
|
141
|
+
const hasLowAttention = list.some((envelope) => normalizeAttention(envelope.evaluator_attention).level !== 'full');
|
|
142
|
+
const uncertainty = clamp01(meanUncertainty
|
|
143
|
+
+ (hasConflict ? 0.25 : 0)
|
|
144
|
+
+ (hasIndecision ? 0.10 : 0)
|
|
145
|
+
+ (hasLowAttention ? 0.10 : 0));
|
|
146
|
+
let dominantLabel = null;
|
|
147
|
+
if (!hasConflict && !hasLowAttention && uncertainty < 0.5) {
|
|
148
|
+
if (hasPositive)
|
|
149
|
+
dominantLabel = 'positive';
|
|
150
|
+
else if (hasNegative)
|
|
151
|
+
dominantLabel = 'negative';
|
|
152
|
+
}
|
|
153
|
+
return { dominant_label: dominantLabel, uncertainty, sample_count: sampleCount };
|
|
154
|
+
}
|
|
155
|
+
function enumValue(value, allowed, fallback) {
|
|
156
|
+
return typeof value === 'string' && allowed.includes(value)
|
|
157
|
+
? value
|
|
158
|
+
: fallback;
|
|
159
|
+
}
|
|
160
|
+
function nonNegativeInteger(value) {
|
|
161
|
+
const n = Number(value);
|
|
162
|
+
return Number.isFinite(n) ? Math.max(0, Math.floor(n)) : null;
|
|
163
|
+
}
|
|
164
|
+
function asRecord(value) {
|
|
165
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
166
|
+
? value
|
|
167
|
+
: null;
|
|
168
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './envelope.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './envelope.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export type AssetCallAction = 'hub_search_hit' | 'hub_search_miss' | 'asset_reuse' | 'asset_reference' | 'asset_publish' | 'asset_publish_skip' | 'hub_review_submitted' | 'hub_review_rejected' | 'hub_review_failed';
|
|
1
|
+
export type AssetCallAction = 'hub_search_hit' | 'hub_search_miss' | 'asset_reuse' | 'asset_reference' | 'asset_publish' | 'asset_publish_skip' | 'asset_inject' | 'asset_inject_shadow' | 'hub_review_submitted' | 'hub_review_rejected' | 'hub_review_failed';
|
|
2
|
+
export type TokensSavedBasis = 'measured' | 'cost_index' | 'estimated_blast_radius' | 'estimated_default';
|
|
2
3
|
export interface AssetCallEntry {
|
|
3
4
|
run_id?: string | null;
|
|
4
5
|
action: AssetCallAction;
|
|
@@ -10,6 +11,9 @@ export interface AssetCallEntry {
|
|
|
10
11
|
mode?: 'direct' | 'reference';
|
|
11
12
|
signals?: readonly string[];
|
|
12
13
|
reason?: string;
|
|
14
|
+
tokens_saved?: number | null;
|
|
15
|
+
tokens_saved_basis?: TokensSavedBasis | null;
|
|
16
|
+
tokens_spent?: number | null;
|
|
13
17
|
extra?: Record<string, unknown>;
|
|
14
18
|
}
|
|
15
19
|
export interface AssetCallRecord extends AssetCallEntry {
|
|
@@ -30,6 +34,32 @@ export interface CallLogSummary {
|
|
|
30
34
|
by_action: Record<string, number>;
|
|
31
35
|
entries: AssetCallRecord[];
|
|
32
36
|
}
|
|
37
|
+
export interface AssetReuseAttribution {
|
|
38
|
+
asset_id: string;
|
|
39
|
+
source_node_id: string | null;
|
|
40
|
+
chain_id: string | null;
|
|
41
|
+
reuse: number;
|
|
42
|
+
reference: number;
|
|
43
|
+
tokens_saved: number;
|
|
44
|
+
}
|
|
45
|
+
export interface ReuseAttributionSummary {
|
|
46
|
+
total_reuse: number;
|
|
47
|
+
total_reference: number;
|
|
48
|
+
total_tokens_saved: number;
|
|
49
|
+
by_asset: AssetReuseAttribution[];
|
|
50
|
+
}
|
|
51
|
+
export interface ReuseSavingsMetric {
|
|
52
|
+
tokens_saved: number;
|
|
53
|
+
tokens_saved_basis: TokensSavedBasis;
|
|
54
|
+
}
|
|
55
|
+
/** Measured derivation cost carried by an asset (or by a Hub wrapper's payload). */
|
|
56
|
+
export declare function assetDerivationTokenCost(asset: unknown): number | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Attribute savings without inventing a measured value: asset telemetry wins, then this node's publish-cost
|
|
59
|
+
* index, then the savings-core blast/default estimator. Reference reuse applies the same fractional discount
|
|
60
|
+
* to measured and estimated costs.
|
|
61
|
+
*/
|
|
62
|
+
export declare function reuseSavingsForAsset(asset: unknown, mode: 'direct' | 'reference', indexedTokenCost?: unknown): ReuseSavingsMetric;
|
|
33
63
|
export declare class AssetCallLog {
|
|
34
64
|
private readonly path;
|
|
35
65
|
private readonly now;
|
|
@@ -40,4 +70,8 @@ export declare class AssetCallLog {
|
|
|
40
70
|
read(opts?: ReadOpts): AssetCallRecord[];
|
|
41
71
|
/** Totals + per-action counts (for CLI / observability). */
|
|
42
72
|
summarize(opts?: ReadOpts): CallLogSummary;
|
|
73
|
+
/** Local-only attribution rollup over reuse/reference audit rows. */
|
|
74
|
+
reuseAttributionSummary(opts?: ReadOpts): ReuseAttributionSummary;
|
|
75
|
+
/** Later valid publish rows win; malformed/non-positive costs never erase a prior measurement. */
|
|
76
|
+
assetCostIndex(opts?: ReadOpts): Record<string, number>;
|
|
43
77
|
}
|
package/dist/hub/assetCallLog.js
CHANGED
|
@@ -5,6 +5,69 @@
|
|
|
5
5
|
// or fails an evolution. The log path is injected so it's testable without a real home dir.
|
|
6
6
|
import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
7
7
|
import { dirname } from 'node:path';
|
|
8
|
+
import { REUSE_ESTIMATOR, reuseEstimate } from '../ops/savingsCore.js';
|
|
9
|
+
function objectRecord(value) {
|
|
10
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
11
|
+
? value
|
|
12
|
+
: undefined;
|
|
13
|
+
}
|
|
14
|
+
function nonEmptyString(value) {
|
|
15
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
function positiveNumber(value, coerceLegacy = false) {
|
|
18
|
+
const number = typeof value === 'number' ? value : coerceLegacy ? Number(value) : Number.NaN;
|
|
19
|
+
return Number.isFinite(number) && number > 0 ? number : undefined;
|
|
20
|
+
}
|
|
21
|
+
function nestedAssetRecord(asset) {
|
|
22
|
+
const direct = objectRecord(asset);
|
|
23
|
+
return { ...(direct ? { direct } : {}), ...(objectRecord(direct?.['payload']) ? { payload: objectRecord(direct?.['payload']) } : {}) };
|
|
24
|
+
}
|
|
25
|
+
/** Measured derivation cost carried by an asset (or by a Hub wrapper's payload). */
|
|
26
|
+
export function assetDerivationTokenCost(asset) {
|
|
27
|
+
const records = nestedAssetRecord(asset);
|
|
28
|
+
for (const record of [records.direct, records.payload]) {
|
|
29
|
+
const derivation = objectRecord(record?.['derivation_tokens']);
|
|
30
|
+
const total = positiveNumber(derivation?.['total_tokens']);
|
|
31
|
+
if (total !== undefined)
|
|
32
|
+
return total;
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
function assetBlastRadiusLines(asset) {
|
|
37
|
+
const records = nestedAssetRecord(asset);
|
|
38
|
+
for (const record of [records.direct, records.payload]) {
|
|
39
|
+
const blast = objectRecord(record?.['blast_radius']);
|
|
40
|
+
const lines = positiveNumber(blast?.['lines']);
|
|
41
|
+
if (lines !== undefined)
|
|
42
|
+
return lines;
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Attribute savings without inventing a measured value: asset telemetry wins, then this node's publish-cost
|
|
48
|
+
* index, then the savings-core blast/default estimator. Reference reuse applies the same fractional discount
|
|
49
|
+
* to measured and estimated costs.
|
|
50
|
+
*/
|
|
51
|
+
export function reuseSavingsForAsset(asset, mode, indexedTokenCost) {
|
|
52
|
+
const measured = assetDerivationTokenCost(asset);
|
|
53
|
+
const indexed = measured === undefined ? positiveNumber(indexedTokenCost) : undefined;
|
|
54
|
+
const cost = measured ?? indexed;
|
|
55
|
+
if (cost !== undefined) {
|
|
56
|
+
return {
|
|
57
|
+
tokens_saved: Math.round(mode === 'reference' ? cost * REUSE_ESTIMATOR.reference_saving_fraction : cost),
|
|
58
|
+
tokens_saved_basis: measured !== undefined ? 'measured' : 'cost_index',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const estimated = reuseEstimate(assetBlastRadiusLines(asset), mode);
|
|
62
|
+
return { tokens_saved: estimated.tokens_saved, tokens_saved_basis: estimated.basis };
|
|
63
|
+
}
|
|
64
|
+
function isAssetCallRecord(value) {
|
|
65
|
+
const record = objectRecord(value);
|
|
66
|
+
return record !== undefined
|
|
67
|
+
&& typeof record['timestamp'] === 'string'
|
|
68
|
+
&& typeof record['action'] === 'string'
|
|
69
|
+
&& record['action'].length > 0;
|
|
70
|
+
}
|
|
8
71
|
export class AssetCallLog {
|
|
9
72
|
path;
|
|
10
73
|
now;
|
|
@@ -33,7 +96,9 @@ export class AssetCallLog {
|
|
|
33
96
|
if (!line)
|
|
34
97
|
continue;
|
|
35
98
|
try {
|
|
36
|
-
|
|
99
|
+
const parsed = JSON.parse(line);
|
|
100
|
+
if (isAssetCallRecord(parsed))
|
|
101
|
+
entries.push(parsed);
|
|
37
102
|
}
|
|
38
103
|
catch { /* skip corrupt */ }
|
|
39
104
|
}
|
|
@@ -69,4 +134,62 @@ export class AssetCallLog {
|
|
|
69
134
|
}
|
|
70
135
|
return { total_entries: entries.length, unique_assets: assets.size, unique_runs: runs.size, by_action: byAction, entries };
|
|
71
136
|
}
|
|
137
|
+
/** Local-only attribution rollup over reuse/reference audit rows. */
|
|
138
|
+
reuseAttributionSummary(opts = {}) {
|
|
139
|
+
const entries = this.read(opts).filter((entry) => entry.action === 'asset_reuse' || entry.action === 'asset_reference');
|
|
140
|
+
const byAsset = new Map();
|
|
141
|
+
let totalTokensSaved = 0;
|
|
142
|
+
let totalReuse = 0;
|
|
143
|
+
let totalReference = 0;
|
|
144
|
+
for (const entry of entries) {
|
|
145
|
+
const id = nonEmptyString(entry.asset_id) ?? '(unknown)';
|
|
146
|
+
let aggregate = byAsset.get(id);
|
|
147
|
+
if (!aggregate) {
|
|
148
|
+
aggregate = {
|
|
149
|
+
asset_id: id,
|
|
150
|
+
source_node_id: nonEmptyString(entry.source_node_id) ?? null,
|
|
151
|
+
chain_id: nonEmptyString(entry.chain_id) ?? null,
|
|
152
|
+
reuse: 0,
|
|
153
|
+
reference: 0,
|
|
154
|
+
tokens_saved: 0,
|
|
155
|
+
};
|
|
156
|
+
byAsset.set(id, aggregate);
|
|
157
|
+
}
|
|
158
|
+
if (entry.action === 'asset_reuse') {
|
|
159
|
+
aggregate.reuse += 1;
|
|
160
|
+
totalReuse += 1;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
aggregate.reference += 1;
|
|
164
|
+
totalReference += 1;
|
|
165
|
+
}
|
|
166
|
+
const tokensSaved = positiveNumber(entry['tokens_saved'], true);
|
|
167
|
+
if (tokensSaved !== undefined) {
|
|
168
|
+
aggregate.tokens_saved += tokensSaved;
|
|
169
|
+
totalTokensSaved += tokensSaved;
|
|
170
|
+
}
|
|
171
|
+
aggregate.source_node_id ??= nonEmptyString(entry.source_node_id) ?? null;
|
|
172
|
+
aggregate.chain_id ??= nonEmptyString(entry.chain_id) ?? null;
|
|
173
|
+
}
|
|
174
|
+
const byAssetRows = [...byAsset.values()].sort((left, right) => (right.reuse + right.reference) - (left.reuse + left.reference));
|
|
175
|
+
return {
|
|
176
|
+
total_reuse: totalReuse,
|
|
177
|
+
total_reference: totalReference,
|
|
178
|
+
total_tokens_saved: totalTokensSaved,
|
|
179
|
+
by_asset: byAssetRows,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/** Later valid publish rows win; malformed/non-positive costs never erase a prior measurement. */
|
|
183
|
+
assetCostIndex(opts = {}) {
|
|
184
|
+
const costs = new Map();
|
|
185
|
+
for (const entry of this.read(opts)) {
|
|
186
|
+
if (entry.action !== 'asset_publish')
|
|
187
|
+
continue;
|
|
188
|
+
const assetId = nonEmptyString(entry.asset_id);
|
|
189
|
+
const tokensSpent = positiveNumber(entry['tokens_spent'], true);
|
|
190
|
+
if (assetId && tokensSpent !== undefined)
|
|
191
|
+
costs.set(assetId, tokensSpent);
|
|
192
|
+
}
|
|
193
|
+
return Object.fromEntries(costs);
|
|
194
|
+
}
|
|
72
195
|
}
|
package/dist/hub/bindings.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HubCapability, HubBindings } from './capability.js';
|
|
1
|
+
import type { HubCapability, HubBindings, PublishReceipt } from './capability.js';
|
|
2
2
|
import { type LeakCheckMode } from './sanitize.js';
|
|
3
3
|
/** publish 回执非 accepted → 抛此错; SyncEngine 据 terminal 决定是否重试. */
|
|
4
4
|
export declare class PublishRejectedError extends Error {
|
|
@@ -19,6 +19,13 @@ export interface HubBindingsOptions {
|
|
|
19
19
|
mode?: LeakCheckMode;
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Internal publish-handler result. `submittedAssetIds` identifies the exact sanitized
|
|
24
|
+
* content sent to the Hub, even when the Hub's accepted receipt omits asset ids.
|
|
25
|
+
*/
|
|
26
|
+
export interface PublishDispatchResult extends PublishReceipt {
|
|
27
|
+
submittedAssetIds?: string[];
|
|
28
|
+
}
|
|
22
29
|
/**
|
|
23
30
|
* 把 HubCapability 接到 core 两 seam(M6-1):
|
|
24
31
|
* - asProxyHandler: Dispatcher.handlers.proxy — 按 envelope.type 路由到 hub. 抛错→store.fail()重试;
|
package/dist/hub/bindings.js
CHANGED
|
@@ -37,14 +37,18 @@ export function makeHubBindings(cap, options = {}) {
|
|
|
37
37
|
const sanEnabled = sanCfg.enabled ?? true;
|
|
38
38
|
const sanEnv = sanCfg.env ?? (typeof process !== 'undefined' ? process.env : {});
|
|
39
39
|
/** Single publish chokepoint: sanitize (on by default) → strict + leak found → refuse (terminal, not retryable) → cap.publish the redacted bundle. */
|
|
40
|
-
const publishSanitized = (bundle) => {
|
|
40
|
+
const publishSanitized = async (bundle, publishOptions) => {
|
|
41
41
|
if (!sanEnabled)
|
|
42
|
-
return cap.publish(bundle);
|
|
42
|
+
return publishOptions ? cap.publish(bundle, publishOptions) : cap.publish(bundle);
|
|
43
43
|
const r = sanitizeBundle(bundle, { env: sanEnv, ...(sanCfg.mode ? { mode: sanCfg.mode } : {}) });
|
|
44
44
|
if (r.blocked) {
|
|
45
45
|
throw new PublishRejectedError('leak_blocked', true, `sensitive data detected before publish, refused (not retryable): ${summarizeLeaks(r.leaks)}`);
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
const receipt = publishOptions
|
|
48
|
+
? await cap.publish(r.bundle, publishOptions)
|
|
49
|
+
: await cap.publish(r.bundle);
|
|
50
|
+
const submittedAssetIds = contentAssetIds(r.bundle);
|
|
51
|
+
return submittedAssetIds ? { ...receipt, submittedAssetIds } : receipt;
|
|
48
52
|
};
|
|
49
53
|
return {
|
|
50
54
|
asProxyHandler() {
|
|
@@ -54,9 +58,10 @@ export function makeHubBindings(cap, options = {}) {
|
|
|
54
58
|
// payload 可为 bundle {assets:[...]} 或单资产; 统一成 bundle 数组发.
|
|
55
59
|
const p = e.payload;
|
|
56
60
|
const bundle = Array.isArray(p.assets) ? p.assets : [p];
|
|
57
|
-
const r = await publishSanitized(bundle);
|
|
58
|
-
if (r.status !== 'accepted')
|
|
59
|
-
throw new PublishRejectedError(r.status, r.terminal ?? true, r.reason);
|
|
61
|
+
const r = await publishSanitized(bundle, { idempotencyKey: e.idempotencyKey });
|
|
62
|
+
if (r.status !== 'accepted') {
|
|
63
|
+
throw new PublishRejectedError(r.rejection?.code ?? r.status, r.terminal ?? true, r.reason, r.rejection?.retryAfterMs);
|
|
64
|
+
}
|
|
60
65
|
return r;
|
|
61
66
|
}
|
|
62
67
|
case 'task_claim': {
|
|
@@ -98,6 +103,12 @@ export function makeHubBindings(cap, options = {}) {
|
|
|
98
103
|
},
|
|
99
104
|
};
|
|
100
105
|
}
|
|
106
|
+
function contentAssetIds(bundle) {
|
|
107
|
+
const ids = bundle.map((asset) => asset.asset_id);
|
|
108
|
+
return ids.every((id) => typeof id === 'string' && /^sha256:[a-f0-9]{64}$/i.test(id))
|
|
109
|
+
? ids
|
|
110
|
+
: undefined;
|
|
111
|
+
}
|
|
101
112
|
function firstString(...values) {
|
|
102
113
|
for (const value of values) {
|
|
103
114
|
if (typeof value !== 'string')
|
package/dist/hub/capability.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ export interface PublishReceipt {
|
|
|
16
16
|
assetIds?: string[];
|
|
17
17
|
/** 终态标记: true=不可重试(reject/quarantine/402), SyncEngine 不得 incrementRetry(money-safety risk). */
|
|
18
18
|
terminal?: boolean;
|
|
19
|
+
/** Adapter-owned structured rejection metadata; callers must not infer policy from free-form `reason`. */
|
|
20
|
+
rejection?: {
|
|
21
|
+
code: 'invalid_request' | 'credit_shortage' | 'node_unauthorized' | 'not_found' | 'duplicate' | 'invalid_payload' | 'cooldown' | 'hub_rejected';
|
|
22
|
+
retryAfterMs?: number;
|
|
23
|
+
};
|
|
19
24
|
/** M8-1: 公版经济侧只读回执(core 不解释经济语义, 仅透传供观测/UI). private hub 多为空. */
|
|
20
25
|
economic?: {
|
|
21
26
|
creditShortage?: boolean;
|
|
@@ -25,6 +30,10 @@ export interface PublishReceipt {
|
|
|
25
30
|
gdiScore?: number;
|
|
26
31
|
};
|
|
27
32
|
}
|
|
33
|
+
export interface PublishOptions {
|
|
34
|
+
/** Stable, non-blank per logical publish so adapters can make transport retries idempotent. */
|
|
35
|
+
idempotencyKey?: string;
|
|
36
|
+
}
|
|
28
37
|
/** 资产查询. 复用 assetstore SearchQuery, 不发明第二套查询 DSL. */
|
|
29
38
|
export type HubQuery = SearchQuery;
|
|
30
39
|
/** task 子系统事件(订阅流元素). 公版含经济上下文(bounty/credit), 私有仅 workflow. */
|
|
@@ -274,6 +283,7 @@ export interface HttpRequestLike {
|
|
|
274
283
|
method: string;
|
|
275
284
|
path: string;
|
|
276
285
|
body?: string;
|
|
286
|
+
signal?: AbortSignal;
|
|
277
287
|
}
|
|
278
288
|
/**
|
|
279
289
|
* Pluggable auth. Example implementations: public OAuth device token / Ed25519 keypair / enterprise SSO (OIDC/LDAP).
|
|
@@ -295,7 +305,7 @@ export interface HubCapability {
|
|
|
295
305
|
* 异步发布. 公版 /a2a/publish 收 **bundle**[Gene,Capsule,(EvolutionEvent)](一个 cycle 的产物一起发, +GDI);
|
|
296
306
|
* 传单资产=[asset]。core 已 normalizeForPut(算/校验 asset_id), hub 做经济/治理/质量 gate。
|
|
297
307
|
*/
|
|
298
|
-
publish(bundle: AssetRecord[]): Promise<PublishReceipt>;
|
|
308
|
+
publish(bundle: AssetRecord[], options?: PublishOptions): Promise<PublishReceipt>;
|
|
299
309
|
/** 拉取资产. hub 侧排序/scope(公版 GDI, 私有 RBAC); core 不重排. */
|
|
300
310
|
fetch(query: HubQuery): Promise<AssetRecord[]>;
|
|
301
311
|
/** 搜索资产. 与 fetch 同 shape, 差异在 hub 侧召回策略. */
|
package/dist/hub/fake.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HubCapability, PublishReceipt, AgentEvent, TaskEvent, AuthProvider, HubQuery, AssetRecord, TaskCompleteContext } from './capability.js';
|
|
1
|
+
import type { HubCapability, PublishReceipt, AgentEvent, TaskEvent, AuthProvider, HubQuery, AssetRecord, TaskCompleteContext, PublishOptions } from './capability.js';
|
|
2
2
|
export interface FakeHubOptions {
|
|
3
3
|
/** 脚本化 publish gate: 返回 reject/quarantine 以测 PublishReceipt 终态路径. */
|
|
4
4
|
publishGate?: (asset: AssetRecord) => Pick<PublishReceipt, 'status' | 'reason' | 'terminal'>;
|
|
@@ -28,7 +28,7 @@ export declare class FakeHubCapability implements HubCapability {
|
|
|
28
28
|
constructor(opts?: FakeHubOptions);
|
|
29
29
|
/** 测试注入: 排一条 inbound 事件供 poll 拉. */
|
|
30
30
|
seedInbound(e: AgentEvent): void;
|
|
31
|
-
publish(bundle: AssetRecord[]): Promise<PublishReceipt>;
|
|
31
|
+
publish(bundle: AssetRecord[], _options?: PublishOptions): Promise<PublishReceipt>;
|
|
32
32
|
fetch(query: HubQuery): Promise<AssetRecord[]>;
|
|
33
33
|
search(query: HubQuery): Promise<AssetRecord[]>;
|
|
34
34
|
task: {
|
package/dist/hub/fake.js
CHANGED
|
@@ -26,7 +26,7 @@ export class FakeHubCapability {
|
|
|
26
26
|
}
|
|
27
27
|
/** 测试注入: 排一条 inbound 事件供 poll 拉. */
|
|
28
28
|
seedInbound(e) { this.inbox.push(e); }
|
|
29
|
-
async publish(bundle) {
|
|
29
|
+
async publish(bundle, _options) {
|
|
30
30
|
const assetIds = bundle.map((a) => computeAssetId(a));
|
|
31
31
|
const gate = this.opts.publishGate?.(bundle[0] ?? {}) ?? { status: 'accepted' };
|
|
32
32
|
if (gate.status === 'accepted')
|
package/dist/index.d.ts
CHANGED
|
@@ -25,4 +25,6 @@ export * as shadow from './shadow/index.js';
|
|
|
25
25
|
export * as ops from './ops/index.js';
|
|
26
26
|
export * as util from './util/index.js';
|
|
27
27
|
export * as trace from './trace/index.js';
|
|
28
|
-
export * as issueReporter from './issueReporter/index.js';
|
|
28
|
+
export * as issueReporter from './issueReporter/index.js';
|
|
29
|
+
export * as feedback from './feedback/index.js';
|
|
30
|
+
export { isValidationCommandAllowed } from './verify/validation.js';
|
package/dist/index.js
CHANGED
|
@@ -25,4 +25,7 @@ export * as shadow from './shadow/index.js';
|
|
|
25
25
|
export * as ops from './ops/index.js';
|
|
26
26
|
export * as util from './util/index.js';
|
|
27
27
|
export * as trace from './trace/index.js';
|
|
28
|
-
export * as issueReporter from './issueReporter/index.js';
|
|
28
|
+
export * as issueReporter from './issueReporter/index.js';
|
|
29
|
+
export * as feedback from './feedback/index.js';
|
|
30
|
+
// Re-export commonly used validation functions at top level for convenience
|
|
31
|
+
export { isValidationCommandAllowed } from './verify/validation.js';
|
package/dist/mailbox/dispatch.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mailboxClaimOwner } from './store.js';
|
|
1
2
|
/**
|
|
2
3
|
* 选择性 Material 判定(M2-7). 默认遵目录 feedsMaterial; 两类在 dispatch 决定:
|
|
3
4
|
* - asset_publish_result: 仅 rejected 进(失败=养料 #40); 通过无需.
|
|
@@ -25,10 +26,16 @@ export class Dispatcher {
|
|
|
25
26
|
}
|
|
26
27
|
async dispatchOne(e) {
|
|
27
28
|
const now = this.deps.now();
|
|
29
|
+
const claimOwner = mailboxClaimOwner(e);
|
|
30
|
+
if (!claimOwner) {
|
|
31
|
+
return { id: e.id, handler: e.handler, handled: false, fedMaterial: false, note: 'claim-owner-missing' };
|
|
32
|
+
}
|
|
28
33
|
// 副作用幂等: 业务键(非默认=id)命中 → 跳过不二次执行, 直接 complete (money-safety A7/A13)
|
|
29
34
|
const sideEffecting = e.handler !== 'agent' && e.idempotencyKey !== e.id;
|
|
30
35
|
if (sideEffecting && this.deps.store.isProcessed(e.idempotencyKey)) {
|
|
31
|
-
this.deps.store.
|
|
36
|
+
if (!this.deps.store.completeClaimed(e.id, now, claimOwner)) {
|
|
37
|
+
return { id: e.id, handler: e.handler, handled: false, fedMaterial: false, note: 'claim-lost' };
|
|
38
|
+
}
|
|
32
39
|
return { id: e.id, handler: e.handler, handled: true, fedMaterial: false, note: 'idempotent-skip' };
|
|
33
40
|
}
|
|
34
41
|
try {
|
|
@@ -38,14 +45,23 @@ export class Dispatcher {
|
|
|
38
45
|
await this.deps.onMaterial(e);
|
|
39
46
|
fedMaterial = true;
|
|
40
47
|
}
|
|
41
|
-
|
|
42
|
-
this.deps.store.
|
|
43
|
-
|
|
48
|
+
const completed = sideEffecting
|
|
49
|
+
? this.deps.store.completeClaimedAndMarkProcessed(e.id, e.idempotencyKey, result ?? null, now, claimOwner)
|
|
50
|
+
: this.deps.store.completeClaimed(e.id, now, claimOwner);
|
|
51
|
+
if (!completed) {
|
|
52
|
+
return { id: e.id, handler: e.handler, handled: false, fedMaterial, note: 'claim-lost' };
|
|
53
|
+
}
|
|
44
54
|
return { id: e.id, handler: e.handler, handled: true, fedMaterial };
|
|
45
55
|
}
|
|
46
56
|
catch (err) {
|
|
47
|
-
this.deps.store.
|
|
48
|
-
return {
|
|
57
|
+
const failed = this.deps.store.failClaimed(e.id, err instanceof Error ? err.message : String(err), now, claimOwner);
|
|
58
|
+
return {
|
|
59
|
+
id: e.id,
|
|
60
|
+
handler: e.handler,
|
|
61
|
+
handled: false,
|
|
62
|
+
fedMaterial: false,
|
|
63
|
+
note: failed ? 'failed' : 'claim-lost',
|
|
64
|
+
};
|
|
49
65
|
}
|
|
50
66
|
}
|
|
51
67
|
/** 拉一批并分派. 默认 daemon 侧 core+proxy; agent 由 runtime 经 IPC 拉取. */
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { specForType, type Direction, type Handler } from './catalog.js';
|
|
2
2
|
export type Status = 'pending' | 'in_flight' | 'done' | 'failed' | 'expired';
|
|
3
|
-
export
|
|
3
|
+
export type Priority = 'high' | 'normal' | 'low';
|
|
4
|
+
export declare const PRIORITIES: readonly ["high", "normal", "low"];
|
|
5
|
+
export declare const ENVELOPE_SCHEMA_VERSION = "1.1.0";
|
|
4
6
|
export interface Envelope {
|
|
5
7
|
id: string;
|
|
6
8
|
type: string;
|
|
@@ -15,6 +17,7 @@ export interface Envelope {
|
|
|
15
17
|
sourceAgent: string;
|
|
16
18
|
targetAgent: string;
|
|
17
19
|
runtimeNamespace: string;
|
|
20
|
+
priority: Priority;
|
|
18
21
|
attempts: number;
|
|
19
22
|
nextRetryAt: number | null;
|
|
20
23
|
ttlAt: number | null;
|
|
@@ -22,6 +25,7 @@ export interface Envelope {
|
|
|
22
25
|
updatedAt: number;
|
|
23
26
|
schemaVersion: string;
|
|
24
27
|
feedsMaterial: boolean;
|
|
28
|
+
lastError: string | null;
|
|
25
29
|
}
|
|
26
30
|
/** 固定 envelope 字段集 (schema-snapshot 锁; 增删字段必须改此处). */
|
|
27
31
|
export declare const ENVELOPE_FIELDS: readonly (keyof Envelope)[];
|
|
@@ -37,7 +41,9 @@ export interface CreateEnvelopeInput {
|
|
|
37
41
|
sourceAgent?: string;
|
|
38
42
|
targetAgent?: string;
|
|
39
43
|
runtimeNamespace?: string;
|
|
44
|
+
priority?: unknown;
|
|
40
45
|
now?: number;
|
|
41
46
|
}
|
|
47
|
+
export declare function normalizePriority(value: unknown): Priority;
|
|
42
48
|
export declare function createEnvelope(input: CreateEnvelopeInput): Envelope;
|
|
43
49
|
export { specForType };
|