@evomap/evolver-core 2.0.0-beta.2 → 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 +171 -26
- package/dist/algo/capabilityCandidates.js +10 -0
- package/dist/algo/conversationSniffer.js +25 -8
- package/dist/algo/cycleEngine.d.ts +63 -5
- package/dist/algo/cycleEngine.js +320 -42
- package/dist/algo/cycleFailureClassifier.d.ts +1 -1
- 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 +36 -3
- package/dist/algo/geneHealth.js +47 -4
- package/dist/algo/geneIntake.d.ts +46 -1
- package/dist/algo/geneIntake.js +121 -10
- package/dist/algo/geneSelection.d.ts +126 -6
- package/dist/algo/geneSelection.js +479 -36
- package/dist/algo/index.d.ts +6 -1
- package/dist/algo/index.js +6 -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/memoryGraph.d.ts +62 -0
- package/dist/algo/memoryGraph.js +86 -0
- package/dist/algo/orchestrator.d.ts +17 -1
- package/dist/algo/orchestrator.js +30 -4
- package/dist/algo/publishEligibility.d.ts +34 -0
- package/dist/algo/publishEligibility.js +52 -0
- 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.d.ts +28 -0
- package/dist/assetstore/assetSidecarRecords.js +384 -0
- package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
- package/dist/assetstore/assetSidecarRecovery.js +288 -0
- package/dist/assetstore/assetStoreHealth.d.ts +81 -0
- package/dist/assetstore/assetStoreHealth.js +319 -0
- package/dist/assetstore/assetStoreLayout.d.ts +2 -0
- package/dist/assetstore/assetStoreLayout.js +6 -0
- package/dist/assetstore/assetStoreStorage.d.ts +42 -0
- package/dist/assetstore/assetStoreStorage.js +336 -0
- package/dist/assetstore/assetSyncLedger.d.ts +91 -1
- package/dist/assetstore/assetSyncLedger.js +718 -59
- package/dist/assetstore/foreignJsonlSource.d.ts +48 -0
- package/dist/assetstore/foreignJsonlSource.js +150 -0
- package/dist/assetstore/index.d.ts +5 -0
- package/dist/assetstore/index.js +5 -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 +12 -1
- package/dist/assetstore/localJsonl.js +251 -42
- package/dist/assetstore/provenance.d.ts +93 -4
- package/dist/assetstore/provenance.js +368 -84
- package/dist/assetstore/provider.d.ts +63 -0
- package/dist/assetstore/provider.js +97 -6
- package/dist/assetstore/reviewFilter.d.ts +19 -1
- package/dist/assetstore/reviewFilter.js +39 -1
- package/dist/assetstore/reviewLedger.d.ts +8 -2
- package/dist/assetstore/reviewLedger.js +71 -45
- 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 +3 -1
- package/dist/benchmark/index.js +3 -1
- package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
- package/dist/benchmark/selectionFlatAbstention.js +481 -0
- package/dist/benchmark/triggerShift.d.ts +62 -0
- package/dist/benchmark/triggerShift.js +106 -0
- package/dist/bootstrap/envFingerprint.d.ts +9 -0
- package/dist/bootstrap/envFingerprint.js +5 -0
- package/dist/bootstrap/index.d.ts +3 -1
- package/dist/bootstrap/index.js +3 -1
- package/dist/bootstrap/lifecycleBootstrap.d.ts +111 -0
- package/dist/bootstrap/lifecycleBootstrap.js +433 -0
- package/dist/bootstrap/v1EnvCompat.d.ts +113 -0
- package/dist/bootstrap/v1EnvCompat.js +303 -0
- 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 +15 -0
- package/dist/events/paths.d.ts +12 -10
- package/dist/events/paths.js +24 -20
- package/dist/events/public.d.ts +2 -2
- package/dist/events/public.js +2 -2
- package/dist/events/reports.d.ts +2 -0
- package/dist/events/reports.js +4 -0
- package/dist/exec/autoExec.d.ts +68 -4
- package/dist/exec/autoExec.js +392 -33
- package/dist/exec/autonomousCycle.d.ts +31 -4
- package/dist/exec/autonomousCycle.js +71 -13
- package/dist/exec/claudeBridge.d.ts +80 -15
- package/dist/exec/claudeBridge.js +845 -76
- 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/openPrRegistry.d.ts +8 -2
- package/dist/exec/openPrRegistry.js +32 -22
- package/dist/exec/prompt.js +14 -1
- package/dist/exec/proofOfWork.d.ts +1 -1
- package/dist/exec/proofOfWork.js +2 -2
- package/dist/exec/runnerRegistry.d.ts +153 -36
- package/dist/exec/runnerRegistry.js +848 -65
- 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/hooks/hooks.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 +29 -8
- package/dist/hub/capability.d.ts +130 -4
- package/dist/hub/conversationDistiller.d.ts +19 -0
- package/dist/hub/conversationDistiller.js +115 -37
- package/dist/hub/fake.d.ts +3 -2
- package/dist/hub/fake.js +2 -1
- package/dist/hub/index.d.ts +1 -0
- package/dist/hub/index.js +1 -0
- package/dist/hub/questionGenerator.d.ts +5 -1
- package/dist/hub/questionGenerator.js +8 -6
- package/dist/hub/recipeCompose.d.ts +27 -0
- package/dist/hub/recipeCompose.js +90 -0
- package/dist/hub/sanitize.js +122 -7
- package/dist/index.d.ts +6 -1
- package/dist/index.js +7 -1
- package/dist/issueReporter/index.d.ts +156 -0
- package/dist/issueReporter/index.js +1688 -0
- 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 +12 -2
- package/dist/mailbox/ipcServer.js +183 -13
- package/dist/mailbox/store.d.ts +89 -3
- package/dist/mailbox/store.js +895 -41
- 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/savingsCore.js +1 -2
- package/dist/ops/selfUpdate.d.ts +18 -1
- package/dist/ops/selfUpdate.js +88 -23
- package/dist/ops/valueOutreach.d.ts +3 -1
- package/dist/ops/valueOutreach.js +5 -1
- package/dist/personality/schema.d.ts +24 -24
- 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/schema/signal.d.ts +3 -3
- package/dist/schema/signal.js +1 -1
- package/dist/shadow/shadowHub.js +1 -0
- package/dist/signals/curriculum.d.ts +55 -0
- package/dist/signals/curriculum.js +202 -0
- package/dist/signals/cycleHistoryFromEvents.js +17 -8
- package/dist/signals/expand.d.ts +15 -1
- package/dist/signals/expand.js +169 -1
- package/dist/signals/extractor.d.ts +2 -2
- package/dist/signals/extractor.js +31 -6
- package/dist/signals/index.d.ts +4 -1
- package/dist/signals/index.js +4 -1
- package/dist/signals/metaSignals.d.ts +4 -0
- package/dist/signals/metaSignals.js +42 -0
- package/dist/signals/scopeVocabulary.d.ts +75 -0
- package/dist/signals/scopeVocabulary.js +91 -0
- package/dist/signals/signalGate.js +1 -1
- package/dist/signals/taskDomain.d.ts +22 -0
- package/dist/signals/taskDomain.js +43 -0
- package/dist/strategy/constraintAblation.d.ts +64 -0
- package/dist/strategy/constraintAblation.js +2820 -0
- package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
- package/dist/strategy/constraintAblationPredicates.js +339 -0
- package/dist/strategy/index.d.ts +2 -1
- package/dist/strategy/index.js +2 -1
- package/dist/trace/index.d.ts +3 -1
- package/dist/trace/index.js +3 -1
- package/dist/trace/learningTrace.d.ts +216 -0
- package/dist/trace/learningTrace.js +298 -0
- package/dist/trace/proxyTurns.d.ts +31 -0
- package/dist/trace/proxyTurns.js +137 -0
- package/dist/trace/trajectory.d.ts +8 -0
- package/dist/trace/trajectory.js +14 -2
- package/dist/util/fetchPort.d.ts +1 -0
- package/dist/util/fetchPort.js +11 -0
- package/dist/util/fileLock.d.ts +113 -7
- package/dist/util/fileLock.js +1035 -80
- package/dist/util/index.d.ts +3 -1
- package/dist/util/index.js +2 -1
- package/dist/verify/index.d.ts +2 -1
- package/dist/verify/index.js +1 -1
- package/dist/verify/sandboxRunner.d.ts +30 -0
- package/dist/verify/sandboxRunner.js +370 -27
- package/dist/verify/sandboxedValidation.d.ts +16 -2
- package/dist/verify/sandboxedValidation.js +200 -22
- package/dist/verify/validation.d.ts +35 -4
- package/dist/verify/validation.js +164 -16
- 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/dist/workflow/dsl.d.ts +24 -3
- package/dist/workflow/dsl.js +4 -0
- package/dist/workflow/engine.d.ts +5 -1
- package/dist/workflow/engine.js +3 -0
- package/dist/workflow/index.d.ts +3 -1
- package/dist/workflow/index.js +3 -1
- package/dist/workflow/runtime.d.ts +110 -0
- package/dist/workflow/runtime.js +1298 -0
- package/dist/workflow/stateStore.d.ts +172 -0
- package/dist/workflow/stateStore.js +1044 -0
- package/package.json +12 -4
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
// Agent runtime trace instrumentation (Learning Ops slice 1).
|
|
2
|
+
// Aligns with evomap-hub Learning Ops data plane contract (schemaVersion `trace_event.v0` /
|
|
3
|
+
// `learning_packet.v0`): the runtime emits ordered TraceEvents over the agent-run lifecycle and
|
|
4
|
+
// assembles a LearningPacket DRAFT locally. Hub delivery goes through the LearningPacketSink port —
|
|
5
|
+
// core never hardcodes a hub endpoint (file/console/memory sinks let a run trace without hub access).
|
|
6
|
+
import { appendFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { dirname } from 'node:path';
|
|
8
|
+
export const TRACE_EVENT_SCHEMA = 'trace_event.v0';
|
|
9
|
+
export const LEARNING_PACKET_SCHEMA = 'learning_packet.v0';
|
|
10
|
+
/** Lifecycle vocabulary for slice 1. Kept flat + closed so hub-side eventType stays queryable. */
|
|
11
|
+
export const TRACE_EVENT_TYPES = [
|
|
12
|
+
'run.started',
|
|
13
|
+
'run.completed',
|
|
14
|
+
'model.called',
|
|
15
|
+
'tool.called',
|
|
16
|
+
'tool.failed',
|
|
17
|
+
'retry.attempted',
|
|
18
|
+
'reflection.recorded',
|
|
19
|
+
'intervention.received',
|
|
20
|
+
];
|
|
21
|
+
export class InMemoryTraceSink {
|
|
22
|
+
events = [];
|
|
23
|
+
emit(event) { this.events.push(event); }
|
|
24
|
+
}
|
|
25
|
+
/** JSONL append sink for local inspection / offline replay. */
|
|
26
|
+
export class FileTraceSink {
|
|
27
|
+
path;
|
|
28
|
+
constructor(path) {
|
|
29
|
+
this.path = path;
|
|
30
|
+
}
|
|
31
|
+
emit(event) {
|
|
32
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
33
|
+
appendFileSync(this.path, `${JSON.stringify(event)}\n`, 'utf8');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class ConsoleTraceSink {
|
|
37
|
+
log;
|
|
38
|
+
constructor(log = (line) => console.error(line)) {
|
|
39
|
+
this.log = log;
|
|
40
|
+
}
|
|
41
|
+
emit(event) { this.log(`[trace] ${event.sequence} ${event.eventType} ${JSON.stringify(event.payload)}`); }
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Per-run trace recorder: the single hook surface the runtime calls at lifecycle points.
|
|
45
|
+
* Sequence numbers are assigned here (monotonic per run), so downstream ordering never depends on
|
|
46
|
+
* sink latency or clock resolution.
|
|
47
|
+
*/
|
|
48
|
+
export class AgentRunTraceRecorder {
|
|
49
|
+
opts;
|
|
50
|
+
sequence = 0;
|
|
51
|
+
recorded = [];
|
|
52
|
+
constructor(opts) {
|
|
53
|
+
this.opts = opts;
|
|
54
|
+
}
|
|
55
|
+
get events() { return this.recorded; }
|
|
56
|
+
get sessionId() { return this.opts.sessionId; }
|
|
57
|
+
/**
|
|
58
|
+
* Late-bind a session id discovered after run start (e.g. unique proxy llm_turn session).
|
|
59
|
+
* Backfills already-recorded events so the whole trajectory carries the join key.
|
|
60
|
+
* Fail closed on empty/whitespace values and conflicting rebinds.
|
|
61
|
+
*/
|
|
62
|
+
bindSessionId(sessionId) {
|
|
63
|
+
const next = sessionId.trim();
|
|
64
|
+
if (next.length === 0) {
|
|
65
|
+
throw new Error('sessionId must be a non-empty string');
|
|
66
|
+
}
|
|
67
|
+
const current = this.opts.sessionId;
|
|
68
|
+
if (current !== undefined) {
|
|
69
|
+
if (current === next)
|
|
70
|
+
return;
|
|
71
|
+
throw new Error(`sessionId already bound to ${current}; refusing to rebind to ${next}`);
|
|
72
|
+
}
|
|
73
|
+
this.opts.sessionId = next;
|
|
74
|
+
for (const event of this.recorded) {
|
|
75
|
+
event.sessionId = next;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
runStarted(input = {}) {
|
|
79
|
+
return this.record('run.started', {
|
|
80
|
+
...(input.taskSummary !== undefined ? { taskSummary: input.taskSummary } : {}),
|
|
81
|
+
...(input.signals !== undefined ? { signals: [...input.signals] } : {}),
|
|
82
|
+
...(input.geneId !== undefined ? { geneId: input.geneId } : {}),
|
|
83
|
+
}, input.metadata);
|
|
84
|
+
}
|
|
85
|
+
modelCalled(input = {}) {
|
|
86
|
+
return this.record('model.called', {
|
|
87
|
+
...(input.provider !== undefined ? { provider: input.provider } : {}),
|
|
88
|
+
...(input.model !== undefined ? { model: input.model } : {}),
|
|
89
|
+
...(input.requestId !== undefined ? { requestId: input.requestId } : {}),
|
|
90
|
+
...(input.latencyMs !== undefined ? { latencyMs: input.latencyMs } : {}),
|
|
91
|
+
...(input.usage !== undefined ? { usage: input.usage } : {}),
|
|
92
|
+
...(input.stopReason !== undefined ? { stopReason: input.stopReason } : {}),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
toolCalled(input) {
|
|
96
|
+
return this.record('tool.called', {
|
|
97
|
+
toolName: input.toolName,
|
|
98
|
+
...(input.callId !== undefined ? { callId: input.callId } : {}),
|
|
99
|
+
...(input.durationMs !== undefined ? { durationMs: input.durationMs } : {}),
|
|
100
|
+
}, input.metadata);
|
|
101
|
+
}
|
|
102
|
+
toolFailed(input) {
|
|
103
|
+
return this.record('tool.failed', {
|
|
104
|
+
toolName: input.toolName,
|
|
105
|
+
...(input.callId !== undefined ? { callId: input.callId } : {}),
|
|
106
|
+
error: input.error,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
retryAttempted(input) {
|
|
110
|
+
return this.record('retry.attempted', {
|
|
111
|
+
attempt: input.attempt,
|
|
112
|
+
...(input.reason !== undefined ? { reason: input.reason } : {}),
|
|
113
|
+
...(input.target !== undefined ? { target: input.target } : {}),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
reflectionRecorded(input) {
|
|
117
|
+
return this.record('reflection.recorded', {
|
|
118
|
+
outcome: input.outcome,
|
|
119
|
+
...(input.action !== undefined ? { action: input.action } : {}),
|
|
120
|
+
...(input.summary !== undefined ? { summary: input.summary } : {}),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
interventionReceived(input) {
|
|
124
|
+
return this.record('intervention.received', {
|
|
125
|
+
kind: input.kind,
|
|
126
|
+
...(input.actorId !== undefined ? { actorId: input.actorId } : {}),
|
|
127
|
+
...(input.detail !== undefined ? { detail: input.detail } : {}),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
runCompleted(input) {
|
|
131
|
+
return this.record('run.completed', {
|
|
132
|
+
status: input.status,
|
|
133
|
+
...(input.score !== undefined ? { score: input.score } : {}),
|
|
134
|
+
...(input.reason !== undefined ? { reason: input.reason } : {}),
|
|
135
|
+
...(input.producedValue !== undefined ? { producedValue: input.producedValue } : {}),
|
|
136
|
+
...(input.failureKind !== undefined ? { failureKind: input.failureKind } : {}),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/** Fold one normalized llm_turn (trace/trajectory.ts) into model.called + tool.called/tool.failed events. */
|
|
140
|
+
recordLlmTurn(turn) {
|
|
141
|
+
const out = [this.modelCalled({
|
|
142
|
+
...(turn.provider !== null ? { provider: turn.provider } : {}),
|
|
143
|
+
...(turn.chosen_model !== null ? { model: turn.chosen_model } : {}),
|
|
144
|
+
...(turn.request_id !== null ? { requestId: turn.request_id } : {}),
|
|
145
|
+
...(turn.latency_ms !== null ? { latencyMs: turn.latency_ms } : {}),
|
|
146
|
+
...(turn.usage !== undefined ? { usage: { ...turn.usage } } : {}),
|
|
147
|
+
...(typeof turn.stop_reason === 'string' ? { stopReason: turn.stop_reason } : {}),
|
|
148
|
+
})];
|
|
149
|
+
if (Array.isArray(turn.tool_calls)) {
|
|
150
|
+
for (const call of turn.tool_calls) {
|
|
151
|
+
const rec = call;
|
|
152
|
+
const toolName = typeof rec.name === 'string' && rec.name.length > 0 ? rec.name : 'unknown_tool';
|
|
153
|
+
const callId = typeof rec.id === 'string' ? rec.id : undefined;
|
|
154
|
+
out.push(typeof rec.error === 'string' && rec.error.length > 0
|
|
155
|
+
? this.toolFailed({ toolName, ...(callId !== undefined ? { callId } : {}), error: rec.error })
|
|
156
|
+
: this.toolCalled({ toolName, ...(callId !== undefined ? { callId } : {}) }));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
record(eventType, payload, metadata) {
|
|
162
|
+
this.sequence += 1;
|
|
163
|
+
const at = (this.opts.now ?? Date.now)();
|
|
164
|
+
const event = {
|
|
165
|
+
schemaVersion: TRACE_EVENT_SCHEMA,
|
|
166
|
+
eventId: (this.opts.eventIdFactory ?? ((seq) => `${this.opts.runId}-${String(seq).padStart(4, '0')}`))(this.sequence),
|
|
167
|
+
eventType,
|
|
168
|
+
traceId: this.opts.runId,
|
|
169
|
+
...(this.opts.sessionId !== undefined ? { sessionId: this.opts.sessionId } : {}),
|
|
170
|
+
...(this.opts.taskId !== undefined ? { taskId: this.opts.taskId } : {}),
|
|
171
|
+
sequence: this.sequence,
|
|
172
|
+
occurredAt: new Date(at).toISOString(),
|
|
173
|
+
payload,
|
|
174
|
+
metadata: metadata ?? {},
|
|
175
|
+
};
|
|
176
|
+
this.recorded.push(event);
|
|
177
|
+
this.opts.sink?.emit(event);
|
|
178
|
+
return event;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/* ── Root-event bridge: existing cycle lifecycle → trace events, via the sanctioned ObserverBus ── */
|
|
182
|
+
const BRIDGED_EVENT_TYPES = [
|
|
183
|
+
'cycle.started', 'cycle.solidified', 'cycle.failed',
|
|
184
|
+
'reflection.recorded',
|
|
185
|
+
'actor.human.nudge', 'actor.human.intervene', 'actor.human.teach',
|
|
186
|
+
'actor.human.review.approve', 'actor.human.review.reject',
|
|
187
|
+
];
|
|
188
|
+
function asRecord(value) {
|
|
189
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Bypass-side bridge (never blocks the write path): maps the engine's existing root events onto the
|
|
193
|
+
* run's TraceEvent stream, so CycleEngine needs no code change for run start/completion, reflection,
|
|
194
|
+
* or human intervention coverage.
|
|
195
|
+
*/
|
|
196
|
+
export function learningTraceObserver(deps) {
|
|
197
|
+
const meta = {
|
|
198
|
+
name: 'learning_trace',
|
|
199
|
+
eventTypes: BRIDGED_EVENT_TYPES,
|
|
200
|
+
idempotent: true,
|
|
201
|
+
timeoutMs: deps.timeoutMs ?? 2_000,
|
|
202
|
+
};
|
|
203
|
+
return {
|
|
204
|
+
meta,
|
|
205
|
+
handle(event) {
|
|
206
|
+
const payload = asRecord(event.payload) ?? {};
|
|
207
|
+
if (event.type === 'cycle.started') {
|
|
208
|
+
deps.recorder.runStarted({ metadata: { cycleId: payload['cycleId'] ?? null } });
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (event.type === 'cycle.solidified' || event.type === 'cycle.failed') {
|
|
212
|
+
const outcome = asRecord(payload['outcome']);
|
|
213
|
+
const status = event.type === 'cycle.solidified' ? 'success' : 'failed';
|
|
214
|
+
const score = typeof outcome?.['score'] === 'number' ? outcome['score'] : undefined;
|
|
215
|
+
const reason = typeof payload['error'] === 'string'
|
|
216
|
+
? payload['error']
|
|
217
|
+
: typeof payload['reason'] === 'string' ? payload['reason'] : undefined;
|
|
218
|
+
deps.recorder.runCompleted({
|
|
219
|
+
status,
|
|
220
|
+
...(score !== undefined ? { score } : {}),
|
|
221
|
+
...(reason !== undefined ? { reason } : {}),
|
|
222
|
+
...(typeof payload['producedValue'] === 'boolean' ? { producedValue: payload['producedValue'] } : {}),
|
|
223
|
+
...(typeof payload['failureKind'] === 'string' ? { failureKind: payload['failureKind'] } : {}),
|
|
224
|
+
});
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (event.type === 'reflection.recorded') {
|
|
228
|
+
deps.recorder.reflectionRecorded({
|
|
229
|
+
outcome: typeof payload['outcome'] === 'string' ? payload['outcome'] : 'unknown',
|
|
230
|
+
...(typeof payload['action'] === 'string' ? { action: payload['action'] } : {}),
|
|
231
|
+
...(typeof payload['summary'] === 'string' ? { summary: payload['summary'] } : {}),
|
|
232
|
+
});
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// actor.human.*: host/human intervention audit trail.
|
|
236
|
+
deps.recorder.interventionReceived({
|
|
237
|
+
kind: event.type.replace(/^actor\.human\./, ''),
|
|
238
|
+
...(event.actor.id !== undefined ? { actorId: event.actor.id } : {}),
|
|
239
|
+
...(typeof payload['detail'] === 'string' ? { detail: payload['detail'] } : {}),
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
export function buildLearningPacketDraft(recorder, input) {
|
|
245
|
+
const events = [...recorder.events].sort((a, b) => a.sequence - b.sequence);
|
|
246
|
+
const first = events[0];
|
|
247
|
+
const completed = [...events].reverse().find((e) => e.eventType === 'run.completed');
|
|
248
|
+
const completedStatus = completed?.payload['status'];
|
|
249
|
+
const failureKind = completed?.payload['failureKind'];
|
|
250
|
+
const started = events.find((e) => e.eventType === 'run.started');
|
|
251
|
+
const startedSummary = started?.payload['taskSummary'];
|
|
252
|
+
return {
|
|
253
|
+
schemaVersion: LEARNING_PACKET_SCHEMA,
|
|
254
|
+
status: 'draft',
|
|
255
|
+
source: { repo: input.sourceRepo, run: first?.traceId ?? 'unknown-run', type: 'agent_run', id: first?.traceId ?? 'unknown-run' },
|
|
256
|
+
task: {
|
|
257
|
+
taskId: first?.taskId ?? null,
|
|
258
|
+
summary: input.taskSummary ?? (typeof startedSummary === 'string' ? startedSummary : null),
|
|
259
|
+
signals: [...(input.signals ?? [])],
|
|
260
|
+
},
|
|
261
|
+
context: {
|
|
262
|
+
sessionId: first?.sessionId ?? null,
|
|
263
|
+
traceId: first?.traceId ?? 'unknown-run',
|
|
264
|
+
environment: input.environment ?? {},
|
|
265
|
+
},
|
|
266
|
+
trajectory: events,
|
|
267
|
+
artifacts: { placeholder: true, items: [] },
|
|
268
|
+
evaluation: {
|
|
269
|
+
placeholder: input.verification === undefined,
|
|
270
|
+
outcomeStatus: completedStatus === 'success' ? 'success' : completedStatus === 'failed' ? 'failed' : 'unknown',
|
|
271
|
+
verifier: input.verification?.verifier ?? null,
|
|
272
|
+
...(input.verification !== undefined ? { verifierPassed: input.verification.passed } : {}),
|
|
273
|
+
...(input.verification?.score !== undefined ? { verifierScore: input.verification.score } : {}),
|
|
274
|
+
failureCategory: typeof failureKind === 'string' ? failureKind : null,
|
|
275
|
+
},
|
|
276
|
+
governance: { placeholder: true, redactionStatus: 'metadata_only', consentStatus: 'unknown', trainingEligible: false, retentionPolicy: 'standard' },
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
export class InMemoryLearningPacketSink {
|
|
280
|
+
drafts = [];
|
|
281
|
+
async submit(draft) {
|
|
282
|
+
this.drafts.push(draft);
|
|
283
|
+
return { accepted: true };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/** Writes one JSON file per run — the offline/no-hub path (and a manual-inspection artifact). */
|
|
287
|
+
export class FileLearningPacketSink {
|
|
288
|
+
dir;
|
|
289
|
+
constructor(dir) {
|
|
290
|
+
this.dir = dir;
|
|
291
|
+
}
|
|
292
|
+
async submit(draft) {
|
|
293
|
+
mkdirSync(this.dir, { recursive: true });
|
|
294
|
+
const path = `${this.dir}/${draft.source.run}.learning-packet.json`;
|
|
295
|
+
writeFileSync(path, `${JSON.stringify(draft, null, 2)}\n`, 'utf8');
|
|
296
|
+
return { accepted: true, reason: path };
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type TraceReadOptions } from './trajectoryExport.js';
|
|
2
|
+
import { type TraceTurnDraft } from './trajectory.js';
|
|
3
|
+
/** One agent run's wall-clock window (epoch ms, same host clock as the proxy's ts). */
|
|
4
|
+
export interface RunTurnWindow {
|
|
5
|
+
startMs: number;
|
|
6
|
+
endMs: number;
|
|
7
|
+
}
|
|
8
|
+
export interface SelectRunLlmTurnsOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Exact-match correlation key: when the caller knows the spawned agent's session id, only that session's
|
|
11
|
+
* turns are returned (the window heuristic is skipped — the id is authoritative).
|
|
12
|
+
*/
|
|
13
|
+
sessionId?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface CollectRunLlmTurnsOptions extends SelectRunLlmTurnsOptions {
|
|
16
|
+
/** Decryption material forwarded to readTraceRowsFromJsonl. allowPartial is always forced on. */
|
|
17
|
+
readOptions?: TraceReadOptions;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Pure selector (unit-testable without fs): pick the turns that belong to the run per the correlation
|
|
21
|
+
* contract above, sorted by ts ascending (stable — equal timestamps keep day-file append order, and the
|
|
22
|
+
* recorder's fold order becomes the sequence order).
|
|
23
|
+
*/
|
|
24
|
+
export declare function selectRunLlmTurns(turns: readonly TraceTurnDraft[], window: RunTurnWindow, opts?: SelectRunLlmTurnsOptions): TraceTurnDraft[];
|
|
25
|
+
/**
|
|
26
|
+
* Read the proxy trace day-files in `dir` and return this run's llm_turns (see the correlation contract
|
|
27
|
+
* above), ready to fold via recordLlmTurn. Reuses readTraceRowsFromJsonl (decryption + row parsing) and
|
|
28
|
+
* traceRecordToTurnDraft (normalization). Never throws: any failure — no proxy, missing dir, unreadable
|
|
29
|
+
* file, undecryptable rows — degrades to [].
|
|
30
|
+
*/
|
|
31
|
+
export declare function collectRunLlmTurns(dir: string, window: RunTurnWindow, opts?: CollectRunLlmTurnsOptions): TraceTurnDraft[];
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// Proxy llm_turn → run fold (Learning Ops slice 5): collect the per-request llm_turn records the LLM proxy
|
|
2
|
+
// captured DURING one agent run's wall-clock window, normalized as TraceTurnDrafts ready for
|
|
3
|
+
// AgentRunTraceRecorder.recordLlmTurn. This is what upgrades a run's trajectory from the bridge's single
|
|
4
|
+
// coarse model.called (the headless runner is a black box) to real per-request fidelity
|
|
5
|
+
// (provider/model/usage/latency/stop_reason + tool-call detail).
|
|
6
|
+
//
|
|
7
|
+
// Correlation contract (why time window + session-first-turn, not session_id alone): the headless runner does
|
|
8
|
+
// not report its session id back to the bridge (`claude -p --output-format text` is opaque), and llm_turn rows
|
|
9
|
+
// carry no cwd — so the run has no exact key to look up. What the run DOES own is its wall-clock window on the
|
|
10
|
+
// same host the proxy writes from (one shared clock, no skew). A turn belongs to the run iff:
|
|
11
|
+
// 1. its ts falls inside [startMs, endMs], AND
|
|
12
|
+
// 2. its session's FIRST observed turn also falls inside the window — a session spawned by this run cannot
|
|
13
|
+
// have traffic predating the run, while a concurrent interactive session (started earlier) is excluded by
|
|
14
|
+
// its pre-window history. Sessionless turns (session_id null) fall back to the window test alone.
|
|
15
|
+
// Callers that DO know the spawned agent's session id (e.g. a future runner passing --session-id) can pass
|
|
16
|
+
// `sessionId` for exact-match correlation instead of the heuristic.
|
|
17
|
+
//
|
|
18
|
+
// Residual risk, accepted + documented: an interactive session whose very first request starts inside the run
|
|
19
|
+
// window is indistinguishable from the run's own agent. On an unattended daemon host this is rare, and the
|
|
20
|
+
// fold is observability-only — it can bias a trace, never a verdict.
|
|
21
|
+
//
|
|
22
|
+
// Everything degrades silently to [] (missing dir, unreadable file, undecryptable envelope, bad ts): the
|
|
23
|
+
// learning trace must never fail or slow a task.
|
|
24
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
25
|
+
import { join } from 'node:path';
|
|
26
|
+
import { readTraceRowsFromJsonl } from './trajectoryExport.js';
|
|
27
|
+
import { traceRecordToTurnDraft } from './trajectory.js';
|
|
28
|
+
/** Any proxy day-file (`llm-trace-*.jsonl`). */
|
|
29
|
+
const TRACE_FILE_RE = /^llm-trace-.*\.jsonl$/i;
|
|
30
|
+
/** The canonical day-stamped name the proxy's JsonlTraceSink writes (`llm-trace-YYYYMMDD.jsonl`, UTC). */
|
|
31
|
+
const DAY_STAMPED_FILE_RE = /^llm-trace-(\d{8})\.jsonl$/i;
|
|
32
|
+
function utcDayStamp(ms) {
|
|
33
|
+
const d = new Date(ms);
|
|
34
|
+
const p = (n) => String(n).padStart(2, '0');
|
|
35
|
+
return `${d.getUTCFullYear()}${p(d.getUTCMonth() + 1)}${p(d.getUTCDate())}`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Keep day-stamped files that could contain the window's turns. Heuristic correlation must inspect all earlier
|
|
39
|
+
* day files to prove that an in-window session did not start before the run; exact session-id correlation only
|
|
40
|
+
* needs files that overlap the run window.
|
|
41
|
+
* Non-day-stamped `llm-trace-*.jsonl` names (custom sinks/tests) are kept conservatively — the ts window
|
|
42
|
+
* filter below is the authority; the filename filter only trims read volume.
|
|
43
|
+
*/
|
|
44
|
+
function fileCoversWindow(name, window, includeSessionHistory) {
|
|
45
|
+
const match = DAY_STAMPED_FILE_RE.exec(name);
|
|
46
|
+
if (!match)
|
|
47
|
+
return true;
|
|
48
|
+
const stamp = match[1];
|
|
49
|
+
return (includeSessionHistory || stamp >= utcDayStamp(window.startMs)) && stamp <= utcDayStamp(window.endMs);
|
|
50
|
+
}
|
|
51
|
+
function turnTsMs(turn) {
|
|
52
|
+
if (turn.ts === null)
|
|
53
|
+
return null;
|
|
54
|
+
const ms = Date.parse(turn.ts);
|
|
55
|
+
return Number.isFinite(ms) ? ms : null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Pure selector (unit-testable without fs): pick the turns that belong to the run per the correlation
|
|
59
|
+
* contract above, sorted by ts ascending (stable — equal timestamps keep day-file append order, and the
|
|
60
|
+
* recorder's fold order becomes the sequence order).
|
|
61
|
+
*/
|
|
62
|
+
export function selectRunLlmTurns(turns, window, opts = {}) {
|
|
63
|
+
const stamped = turns
|
|
64
|
+
.map((turn) => ({ turn, tsMs: turnTsMs(turn) }))
|
|
65
|
+
.filter((entry) => entry.tsMs !== null);
|
|
66
|
+
let selected;
|
|
67
|
+
if (opts.sessionId !== undefined) {
|
|
68
|
+
selected = stamped.filter(({ turn }) => turn.session_id === opts.sessionId);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// First observed turn per session across ALL provided turns (including pre-window rows from the same
|
|
72
|
+
// day files) — this is what tells an in-run spawned session apart from an older concurrent one.
|
|
73
|
+
const firstTsBySession = new Map();
|
|
74
|
+
for (const { turn, tsMs } of stamped) {
|
|
75
|
+
if (turn.session_id === null)
|
|
76
|
+
continue;
|
|
77
|
+
const prev = firstTsBySession.get(turn.session_id);
|
|
78
|
+
if (prev === undefined || tsMs < prev)
|
|
79
|
+
firstTsBySession.set(turn.session_id, tsMs);
|
|
80
|
+
}
|
|
81
|
+
selected = stamped.filter(({ turn, tsMs }) => {
|
|
82
|
+
if (tsMs < window.startMs || tsMs > window.endMs)
|
|
83
|
+
return false;
|
|
84
|
+
if (turn.session_id === null)
|
|
85
|
+
return true;
|
|
86
|
+
const firstTs = firstTsBySession.get(turn.session_id);
|
|
87
|
+
return firstTs !== undefined && firstTs >= window.startMs;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return selected
|
|
91
|
+
.map((entry, index) => ({ ...entry, index }))
|
|
92
|
+
.sort((a, b) => a.tsMs - b.tsMs || a.index - b.index)
|
|
93
|
+
.map(({ turn }) => turn);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Read the proxy trace day-files in `dir` and return this run's llm_turns (see the correlation contract
|
|
97
|
+
* above), ready to fold via recordLlmTurn. Reuses readTraceRowsFromJsonl (decryption + row parsing) and
|
|
98
|
+
* traceRecordToTurnDraft (normalization). Never throws: any failure — no proxy, missing dir, unreadable
|
|
99
|
+
* file, undecryptable rows — degrades to [].
|
|
100
|
+
*/
|
|
101
|
+
export function collectRunLlmTurns(dir, window, opts = {}) {
|
|
102
|
+
try {
|
|
103
|
+
if (!(window.endMs >= window.startMs))
|
|
104
|
+
return [];
|
|
105
|
+
const names = readdirSync(dir)
|
|
106
|
+
.filter((name) => TRACE_FILE_RE.test(name) && fileCoversWindow(name, window, opts.sessionId === undefined))
|
|
107
|
+
.sort();
|
|
108
|
+
const turns = [];
|
|
109
|
+
for (const name of names) {
|
|
110
|
+
const dayMatch = DAY_STAMPED_FILE_RE.exec(name);
|
|
111
|
+
const isHistoricalDay = opts.sessionId === undefined
|
|
112
|
+
&& dayMatch !== null
|
|
113
|
+
&& dayMatch[1] < utcDayStamp(window.startMs);
|
|
114
|
+
let text;
|
|
115
|
+
try {
|
|
116
|
+
text = readFileSync(join(dir, name), 'utf8');
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
// allowPartial forced on: an undecryptable envelope is a coverage gap, never a fold failure.
|
|
122
|
+
const { rows } = readTraceRowsFromJsonl(text, { ...(opts.readOptions ?? {}), allowPartial: true });
|
|
123
|
+
for (const row of rows) {
|
|
124
|
+
const turn = traceRecordToTurnDraft(row);
|
|
125
|
+
if (turn !== null) {
|
|
126
|
+
// Older day files establish that a session predates this run; they can never contribute candidate
|
|
127
|
+
// turns even if a malformed row carries an in-window timestamp.
|
|
128
|
+
turns.push(isHistoricalDay ? { ...turn, ts: new Date(window.startMs - 1).toISOString() } : turn);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return selectRunLlmTurns(turns, window, opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {});
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -78,5 +78,13 @@ export interface TraceTrajectoryDraft {
|
|
|
78
78
|
coverage: TraceCoverageItem[];
|
|
79
79
|
}
|
|
80
80
|
export declare function traceRecordToTurnDraft(record: NativeTraceRecord): TraceTurnDraft | null;
|
|
81
|
+
/**
|
|
82
|
+
* Exact-join helper for Learning Ops: return the unique non-empty session id across turns.
|
|
83
|
+
* Fail closed — 0 or >1 distinct ids yield null. Matches the correlation key contract used by
|
|
84
|
+
* Darwin (`cc::<session_id>` ↔ packet `traceEvents[].sessionId`).
|
|
85
|
+
*/
|
|
86
|
+
export declare function uniqueSessionId(turns: readonly {
|
|
87
|
+
session_id?: string | null;
|
|
88
|
+
}[]): string | null;
|
|
81
89
|
export declare function buildTraceTrajectoryDraft(records: readonly NativeTraceRecord[]): TraceTrajectoryDraft;
|
|
82
90
|
export declare function coverageForTurns(turns: readonly TraceTurnDraft[]): TraceCoverageItem[];
|
package/dist/trace/trajectory.js
CHANGED
|
@@ -95,12 +95,24 @@ export function traceRecordToTurnDraft(record) {
|
|
|
95
95
|
...(validation !== undefined ? { validation } : {}),
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Exact-join helper for Learning Ops: return the unique non-empty session id across turns.
|
|
100
|
+
* Fail closed — 0 or >1 distinct ids yield null. Matches the correlation key contract used by
|
|
101
|
+
* Darwin (`cc::<session_id>` ↔ packet `traceEvents[].sessionId`).
|
|
102
|
+
*/
|
|
103
|
+
export function uniqueSessionId(turns) {
|
|
104
|
+
const sessionIds = [
|
|
105
|
+
...new Set(turns
|
|
106
|
+
.map((turn) => turn.session_id)
|
|
107
|
+
.filter((value) => typeof value === 'string' && value.length > 0)),
|
|
108
|
+
];
|
|
109
|
+
return sessionIds.length === 1 ? sessionIds[0] : null;
|
|
110
|
+
}
|
|
98
111
|
export function buildTraceTrajectoryDraft(records) {
|
|
99
112
|
const turns = records.map(traceRecordToTurnDraft).filter((turn) => turn !== null);
|
|
100
|
-
const sessionIds = [...new Set(turns.map((turn) => turn.session_id).filter((value) => typeof value === 'string' && value.length > 0))];
|
|
101
113
|
return {
|
|
102
114
|
schema: 'evolver_trace_trajectory_draft.v1',
|
|
103
|
-
session_id:
|
|
115
|
+
session_id: uniqueSessionId(turns),
|
|
104
116
|
turns,
|
|
105
117
|
coverage: coverageForTurns(turns),
|
|
106
118
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isFetchForbiddenPort(port: number): boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// https://fetch.spec.whatwg.org/#port-blocking
|
|
2
|
+
const FETCH_FORBIDDEN_PORTS = new Set([
|
|
3
|
+
1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95,
|
|
4
|
+
101, 102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 137, 139, 143, 161, 179,
|
|
5
|
+
389, 427, 465, 512, 513, 514, 515, 526, 530, 531, 532, 540, 548, 554, 556, 563, 587, 601,
|
|
6
|
+
636, 989, 990, 993, 995, 1719, 1720, 1723, 2049, 3659, 4045, 4190, 5060, 5061, 6000,
|
|
7
|
+
6566, 6665, 6666, 6667, 6668, 6669, 6679, 6697, 10080,
|
|
8
|
+
]);
|
|
9
|
+
export function isFetchForbiddenPort(port) {
|
|
10
|
+
return Number.isInteger(port) && FETCH_FORBIDDEN_PORTS.has(port);
|
|
11
|
+
}
|
package/dist/util/fileLock.d.ts
CHANGED
|
@@ -1,24 +1,130 @@
|
|
|
1
1
|
export declare function syncSleep(ms: number): void;
|
|
2
|
+
export type FileLockProcessStartIdentity = {
|
|
3
|
+
readonly source: 'linux-proc';
|
|
4
|
+
readonly bootId: string;
|
|
5
|
+
readonly startTicks: string;
|
|
6
|
+
} | {
|
|
7
|
+
readonly source: 'windows-powershell';
|
|
8
|
+
readonly startTimeTicks: string;
|
|
9
|
+
} | {
|
|
10
|
+
readonly source: 'darwin-ps';
|
|
11
|
+
readonly startTime: string;
|
|
12
|
+
};
|
|
13
|
+
export interface FileLockOwnerRecord {
|
|
14
|
+
readonly pid: number;
|
|
15
|
+
readonly token: string;
|
|
16
|
+
readonly processStartIdentity: FileLockProcessStartIdentity;
|
|
17
|
+
}
|
|
18
|
+
export type TransferLockOwnershipReason = 'transferred' | 'not_owned' | 'ownership_changed' | 'mutation_busy' | 'guardian_exists' | 'guardian_missing' | 'target_process_dead' | 'target_process_pid_reused' | 'target_process_unverifiable' | 'path_changed';
|
|
19
|
+
export type TransferLockOwnershipResult = {
|
|
20
|
+
transferred: true;
|
|
21
|
+
reason: 'transferred';
|
|
22
|
+
guardian: FileLockOwnerRecord;
|
|
23
|
+
} | {
|
|
24
|
+
transferred: false;
|
|
25
|
+
reason: Exclude<TransferLockOwnershipReason, 'transferred'>;
|
|
26
|
+
};
|
|
27
|
+
export type AttachLockGuardianResult = {
|
|
28
|
+
attached: true;
|
|
29
|
+
reason: 'attached';
|
|
30
|
+
guardian: FileLockOwnerRecord;
|
|
31
|
+
} | {
|
|
32
|
+
attached: false;
|
|
33
|
+
reason: Exclude<TransferLockOwnershipReason, 'transferred'>;
|
|
34
|
+
};
|
|
35
|
+
export type RetainLockGuardianResult = {
|
|
36
|
+
retained: true;
|
|
37
|
+
reason: 'retained';
|
|
38
|
+
} | {
|
|
39
|
+
retained: false;
|
|
40
|
+
reason: 'not_owned' | 'ownership_changed' | 'mutation_busy' | 'guardian_missing' | 'path_changed';
|
|
41
|
+
};
|
|
42
|
+
export type ClearLockGuardianResult = {
|
|
43
|
+
cleared: true;
|
|
44
|
+
reason: 'cleared' | 'missing';
|
|
45
|
+
} | {
|
|
46
|
+
cleared: false;
|
|
47
|
+
reason: 'not_owned' | 'ownership_changed' | 'mutation_busy' | 'path_changed';
|
|
48
|
+
};
|
|
49
|
+
export type FileLockOwnerProcessStatus = 'current' | 'dead' | 'pid_reused' | 'unverifiable';
|
|
2
50
|
export declare class LockTimeoutError extends Error {
|
|
3
51
|
readonly code = "LOCK_TIMEOUT";
|
|
4
|
-
constructor(
|
|
52
|
+
constructor(_lockPath?: string);
|
|
53
|
+
}
|
|
54
|
+
export type UnsafeLockPathReason = 'symlink' | 'not_regular_file' | 'owner_too_large' | 'path_changed' | 'permission_denied' | 'process_identity_unavailable' | 'invalid_owner';
|
|
55
|
+
export declare class UnsafeLockPathError extends Error {
|
|
56
|
+
readonly reason: UnsafeLockPathReason;
|
|
57
|
+
readonly code = "UNSAFE_LOCK_PATH";
|
|
58
|
+
constructor(reason: UnsafeLockPathReason);
|
|
5
59
|
}
|
|
60
|
+
export declare const MAX_LOCK_OWNER_BYTES = 4096;
|
|
61
|
+
/** Fresh native process-start observation. Unlike lock acquisition, this does not use caches. */
|
|
62
|
+
export declare function readFileLockProcessStartIdentity(pid: number): FileLockProcessStartIdentity | null;
|
|
63
|
+
export declare function sameFileLockProcessStartIdentity(left: FileLockProcessStartIdentity, right: FileLockProcessStartIdentity): boolean;
|
|
64
|
+
export declare function parseFileLockProcessStartIdentity(value: unknown): FileLockProcessStartIdentity | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Conservative owner classification for crash recovery. PID reuse and unavailable identity
|
|
67
|
+
* remain distinct from a proven-dead PID so callers cannot reclaim ambiguous ownership.
|
|
68
|
+
*/
|
|
69
|
+
export declare function inspectFileLockOwnerProcess(owner: Pick<FileLockOwnerRecord, 'pid' | 'processStartIdentity'>): FileLockOwnerProcessStatus;
|
|
70
|
+
export declare function _setFileLockTestHooksForTest(hooks?: {
|
|
71
|
+
processStartIdentity?: (pid: number) => FileLockProcessStartIdentity | null;
|
|
72
|
+
pidAlive?: (pid: number) => boolean;
|
|
73
|
+
beforeOwnerPublish?: (temporaryPath: string, lockPath: string) => void;
|
|
74
|
+
beforeGuardianPublish?: (temporaryPath: string, guardianPath: string) => void;
|
|
75
|
+
afterGuardianPublish?: (guardianPath: string, lockPath: string) => void;
|
|
76
|
+
beforeGuardianReclaim?: (guardianPath: string, lockPath: string) => void;
|
|
77
|
+
beforeMutationGuardRelease?: (mutationGuardPath: string, lockPath: string) => void;
|
|
78
|
+
}): void;
|
|
79
|
+
/**
|
|
80
|
+
* Pre-arm an acquired lock with the exact child PID generation while retaining local ownership.
|
|
81
|
+
* A controller can therefore keep mutating its journal, while a hard controller crash cannot make
|
|
82
|
+
* the primary owner reclaimable until the child exits.
|
|
83
|
+
*/
|
|
84
|
+
export declare function attachLockGuardianToProcess(lockPath: string, expectedOwner: FileLockOwnerRecord, targetPid: number): AttachLockGuardianResult;
|
|
85
|
+
/**
|
|
86
|
+
* Drop local ownership only after an already-published guardian has been revalidated exactly.
|
|
87
|
+
* No file mutation is needed: the durable guardian receipt is the crash-safe owner binding.
|
|
88
|
+
*/
|
|
89
|
+
export declare function retainLockGuardianForProcess(lockPath: string, expectedOwner: FileLockOwnerRecord, expectedGuardian: FileLockOwnerRecord): RetainLockGuardianResult;
|
|
90
|
+
/**
|
|
91
|
+
* Remove an exact attached guardian after the child has definitely exited, retaining the primary
|
|
92
|
+
* owner so rollback or another launch can continue under the same lifecycle lease.
|
|
93
|
+
*/
|
|
94
|
+
export declare function clearLockGuardianForProcess(lockPath: string, expectedOwner: FileLockOwnerRecord, expectedGuardian: FileLockOwnerRecord): ClearLockGuardianResult;
|
|
95
|
+
/**
|
|
96
|
+
* Atomically bind an acquired lock to a still-running child and relinquish local ownership.
|
|
97
|
+
*/
|
|
98
|
+
export declare function transferLockOwnershipToProcess(lockPath: string, expectedOwner: FileLockOwnerRecord, targetPid: number): TransferLockOwnershipResult;
|
|
6
99
|
export interface AcquireLockOptions {
|
|
7
100
|
maxTries?: number;
|
|
8
101
|
waitMs?: number;
|
|
9
102
|
}
|
|
10
103
|
/**
|
|
11
|
-
* Cross-process
|
|
104
|
+
* Cross-process file lock with crashed-owner recovery.
|
|
12
105
|
*
|
|
13
106
|
* The lock file records the owner pid and token. If a waiter finds the lock held by a pid that is no longer
|
|
14
107
|
* alive (the owner crashed without releaseLock), it reclaims the stale lock instead of spinning
|
|
15
108
|
* until timeout — otherwise one crashed process would deadlock every future writer until the file
|
|
16
|
-
* is removed by hand.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
109
|
+
* is removed by hand. Empty or truncated locks are reclaimed only after the same inode and contents
|
|
110
|
+
* remain malformed for a grace period, so a live creator can finish publishing its owner payload.
|
|
111
|
+
* Acquisition publishes a fully synced temporary owner with an atomic no-replace hardlink, and stale
|
|
112
|
+
* reclaim moves the old lock aside under a mutation guard, then verifies the inode snapshot before
|
|
113
|
+
* deletion. A live owner's lock (including this
|
|
114
|
+
* process's own) is never stolen.
|
|
19
115
|
*
|
|
20
116
|
* NOTE: still synchronous (blocks the event loop while waiting) by design — it guards short
|
|
21
117
|
* synchronous critical sections (append-only writes).
|
|
22
118
|
*/
|
|
23
|
-
export declare function acquireLock(lockPath: string, opts?: AcquireLockOptions):
|
|
24
|
-
export
|
|
119
|
+
export declare function acquireLock(lockPath: string, opts?: AcquireLockOptions): FileLockOwnerRecord;
|
|
120
|
+
export type ReleaseLockReason = 'released' | 'missing' | 'ownership_changed' | 'not_owned' | 'released_with_cleanup_error' | UnsafeLockPathReason | 'release_failed';
|
|
121
|
+
export interface ReleaseLockResult {
|
|
122
|
+
released: boolean;
|
|
123
|
+
reason: ReleaseLockReason;
|
|
124
|
+
}
|
|
125
|
+
export declare function releaseLock(lockPath: string): ReleaseLockResult;
|
|
126
|
+
export declare class LockReleaseError extends Error {
|
|
127
|
+
readonly reason: ReleaseLockReason;
|
|
128
|
+
readonly code = "LOCK_RELEASE_FAILED";
|
|
129
|
+
constructor(reason: ReleaseLockReason);
|
|
130
|
+
}
|