@evomap/evolver-core 2.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/algo/antiDistill.d.ts +35 -0
- package/dist/algo/antiDistill.js +167 -0
- package/dist/algo/bans.d.ts +6 -0
- package/dist/algo/bans.js +33 -0
- package/dist/algo/candidateAssembly.d.ts +65 -0
- package/dist/algo/candidateAssembly.js +196 -0
- package/dist/algo/capabilityCandidates.d.ts +38 -0
- package/dist/algo/capabilityCandidates.js +131 -0
- package/dist/algo/confidence.d.ts +106 -0
- package/dist/algo/confidence.js +196 -0
- package/dist/algo/cycleEngine.d.ts +143 -0
- package/dist/algo/cycleEngine.js +417 -0
- package/dist/algo/cycleFailureClassifier.d.ts +52 -0
- package/dist/algo/cycleFailureClassifier.js +104 -0
- package/dist/algo/epigenetics.d.ts +14 -0
- package/dist/algo/epigenetics.js +41 -0
- package/dist/algo/evolutionEvent.d.ts +23 -0
- package/dist/algo/evolutionEvent.js +24 -0
- package/dist/algo/exploration.d.ts +34 -0
- package/dist/algo/exploration.js +55 -0
- package/dist/algo/geneHealth.d.ts +24 -0
- package/dist/algo/geneHealth.js +16 -0
- package/dist/algo/geneIntake.d.ts +37 -0
- package/dist/algo/geneIntake.js +100 -0
- package/dist/algo/genePromotion.d.ts +48 -0
- package/dist/algo/genePromotion.js +53 -0
- package/dist/algo/geneSelection.d.ts +128 -0
- package/dist/algo/geneSelection.js +222 -0
- package/dist/algo/index.d.ts +18 -0
- package/dist/algo/index.js +18 -0
- package/dist/algo/mutation.d.ts +12 -0
- package/dist/algo/mutation.js +22 -0
- package/dist/algo/orchestrator.d.ts +65 -0
- package/dist/algo/orchestrator.js +63 -0
- package/dist/algo/solidify.d.ts +43 -0
- package/dist/algo/solidify.js +63 -0
- package/dist/algo/strategyPresets.d.ts +32 -0
- package/dist/algo/strategyPresets.js +61 -0
- package/dist/assetstore/index.d.ts +8 -0
- package/dist/assetstore/index.js +8 -0
- package/dist/assetstore/learningHistory.d.ts +25 -0
- package/dist/assetstore/learningHistory.js +38 -0
- package/dist/assetstore/localJsonl.d.ts +34 -0
- package/dist/assetstore/localJsonl.js +182 -0
- package/dist/assetstore/pendingSignals.d.ts +22 -0
- package/dist/assetstore/pendingSignals.js +171 -0
- package/dist/assetstore/provenance.d.ts +39 -0
- package/dist/assetstore/provenance.js +109 -0
- package/dist/assetstore/provider.d.ts +48 -0
- package/dist/assetstore/provider.js +36 -0
- package/dist/assetstore/remoteStub.d.ts +32 -0
- package/dist/assetstore/remoteStub.js +36 -0
- package/dist/assetstore/reviewFilter.d.ts +20 -0
- package/dist/assetstore/reviewFilter.js +44 -0
- package/dist/assetstore/reviewLedger.d.ts +55 -0
- package/dist/assetstore/reviewLedger.js +121 -0
- package/dist/benchmark/antiGeneBenchmark.d.ts +79 -0
- package/dist/benchmark/antiGeneBenchmark.js +241 -0
- package/dist/benchmark/antiGeneRollout.d.ts +80 -0
- package/dist/benchmark/antiGeneRollout.js +256 -0
- package/dist/benchmark/benchmark.d.ts +32 -0
- package/dist/benchmark/benchmark.js +26 -0
- package/dist/benchmark/evolutionThesisSolver.d.ts +25 -0
- package/dist/benchmark/evolutionThesisSolver.js +41 -0
- package/dist/benchmark/index.d.ts +5 -0
- package/dist/benchmark/index.js +5 -0
- package/dist/benchmark/thesis.d.ts +85 -0
- package/dist/benchmark/thesis.js +150 -0
- package/dist/bootstrap/envFingerprint.d.ts +45 -0
- package/dist/bootstrap/envFingerprint.js +72 -0
- package/dist/bootstrap/index.d.ts +2 -0
- package/dist/bootstrap/index.js +2 -0
- package/dist/bootstrap/selfEvolve.d.ts +36 -0
- package/dist/bootstrap/selfEvolve.js +26 -0
- package/dist/cycle/cycleTimeline.d.ts +35 -0
- package/dist/cycle/cycleTimeline.js +74 -0
- package/dist/cycle/index.d.ts +2 -0
- package/dist/cycle/index.js +2 -0
- package/dist/cycle/stateMachine.d.ts +6 -0
- package/dist/cycle/stateMachine.js +25 -0
- package/dist/daemon/cycleContext.d.ts +12 -0
- package/dist/daemon/cycleContext.js +13 -0
- package/dist/daemon/drain.d.ts +11 -0
- package/dist/daemon/drain.js +16 -0
- package/dist/daemon/guards.d.ts +87 -0
- package/dist/daemon/guards.js +142 -0
- package/dist/daemon/idempotency.d.ts +11 -0
- package/dist/daemon/idempotency.js +36 -0
- package/dist/daemon/idleScheduler.d.ts +55 -0
- package/dist/daemon/idleScheduler.js +181 -0
- package/dist/daemon/index.d.ts +8 -0
- package/dist/daemon/index.js +8 -0
- package/dist/daemon/stepRunner.d.ts +12 -0
- package/dist/daemon/stepRunner.js +31 -0
- package/dist/daemon/unhandledRejectionWindow.d.ts +21 -0
- package/dist/daemon/unhandledRejectionWindow.js +52 -0
- package/dist/daemon/watchdog.d.ts +11 -0
- package/dist/daemon/watchdog.js +14 -0
- package/dist/events/eventSchema.d.ts +132 -0
- package/dist/events/eventSchema.js +30 -0
- package/dist/events/eventStore.d.ts +32 -0
- package/dist/events/eventStore.js +137 -0
- package/dist/events/ingest.d.ts +34 -0
- package/dist/events/ingest.js +61 -0
- package/dist/events/paths.d.ts +22 -0
- package/dist/events/paths.js +44 -0
- package/dist/events/projectors.d.ts +9 -0
- package/dist/events/projectors.js +11 -0
- package/dist/events/public.d.ts +14 -0
- package/dist/events/public.js +8 -0
- package/dist/events/replayer.d.ts +26 -0
- package/dist/events/replayer.js +67 -0
- package/dist/events/reports.d.ts +90 -0
- package/dist/events/reports.js +244 -0
- package/dist/events/retention.d.ts +70 -0
- package/dist/events/retention.js +177 -0
- package/dist/events/sink.d.ts +5 -0
- package/dist/events/sink.js +1 -0
- package/dist/exec/autoExec.d.ts +137 -0
- package/dist/exec/autoExec.js +221 -0
- package/dist/exec/autonomousCycle.d.ts +48 -0
- package/dist/exec/autonomousCycle.js +78 -0
- package/dist/exec/claudeBridge.d.ts +112 -0
- package/dist/exec/claudeBridge.js +219 -0
- package/dist/exec/index.d.ts +9 -0
- package/dist/exec/index.js +11 -0
- package/dist/exec/openPrRegistry.d.ts +60 -0
- package/dist/exec/openPrRegistry.js +130 -0
- package/dist/exec/policy/blastRadius.d.ts +31 -0
- package/dist/exec/policy/blastRadius.js +67 -0
- package/dist/exec/policy/constraints.d.ts +26 -0
- package/dist/exec/policy/constraints.js +62 -0
- package/dist/exec/policy/destructive.d.ts +45 -0
- package/dist/exec/policy/destructive.js +108 -0
- package/dist/exec/policy/failureMode.d.ts +33 -0
- package/dist/exec/policy/failureMode.js +49 -0
- package/dist/exec/policy/index.d.ts +24 -0
- package/dist/exec/policy/index.js +30 -0
- package/dist/exec/policy/protectedPaths.d.ts +17 -0
- package/dist/exec/policy/protectedPaths.js +65 -0
- package/dist/exec/policyCheck.d.ts +2 -0
- package/dist/exec/policyCheck.js +7 -0
- package/dist/exec/prompt.d.ts +34 -0
- package/dist/exec/prompt.js +108 -0
- package/dist/exec/proofOfWork.d.ts +9 -0
- package/dist/exec/proofOfWork.js +11 -0
- package/dist/exec/runnerRegistry.d.ts +123 -0
- package/dist/exec/runnerRegistry.js +237 -0
- package/dist/exec/selfPr.d.ts +83 -0
- package/dist/exec/selfPr.js +104 -0
- package/dist/exec/selfPrObfuscation.d.ts +21 -0
- package/dist/exec/selfPrObfuscation.js +85 -0
- package/dist/hooks/hooks.d.ts +117 -0
- package/dist/hooks/hooks.js +89 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hub/assetCallLog.d.ts +43 -0
- package/dist/hub/assetCallLog.js +72 -0
- package/dist/hub/bindings.d.ts +28 -0
- package/dist/hub/bindings.js +90 -0
- package/dist/hub/capability.d.ts +278 -0
- package/dist/hub/capability.js +1 -0
- package/dist/hub/conversationDistiller.d.ts +88 -0
- package/dist/hub/conversationDistiller.js +264 -0
- package/dist/hub/fake.d.ts +45 -0
- package/dist/hub/fake.js +69 -0
- package/dist/hub/hubReview.d.ts +91 -0
- package/dist/hub/hubReview.js +106 -0
- package/dist/hub/index.d.ts +10 -0
- package/dist/hub/index.js +10 -0
- package/dist/hub/ingest.d.ts +15 -0
- package/dist/hub/ingest.js +15 -0
- package/dist/hub/questionGenerator.d.ts +59 -0
- package/dist/hub/questionGenerator.js +406 -0
- package/dist/hub/reuseDecision.d.ts +87 -0
- package/dist/hub/reuseDecision.js +127 -0
- package/dist/hub/sanitize.d.ts +52 -0
- package/dist/hub/sanitize.js +321 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +27 -0
- package/dist/mailbox/catalog.d.ts +17 -0
- package/dist/mailbox/catalog.js +53 -0
- package/dist/mailbox/daemon.d.ts +48 -0
- package/dist/mailbox/daemon.js +61 -0
- package/dist/mailbox/dispatch.d.ts +46 -0
- package/dist/mailbox/dispatch.js +62 -0
- package/dist/mailbox/envelope.d.ts +43 -0
- package/dist/mailbox/envelope.js +52 -0
- package/dist/mailbox/index.d.ts +8 -0
- package/dist/mailbox/index.js +8 -0
- package/dist/mailbox/ipcServer.d.ts +46 -0
- package/dist/mailbox/ipcServer.js +168 -0
- package/dist/mailbox/requestReply.d.ts +40 -0
- package/dist/mailbox/requestReply.js +77 -0
- package/dist/mailbox/store.d.ts +74 -0
- package/dist/mailbox/store.js +308 -0
- package/dist/mailbox/wake.d.ts +45 -0
- package/dist/mailbox/wake.js +60 -0
- package/dist/material/boundary.d.ts +7 -0
- package/dist/material/boundary.js +14 -0
- package/dist/material/consumer.d.ts +21 -0
- package/dist/material/consumer.js +52 -0
- package/dist/material/emit.d.ts +55 -0
- package/dist/material/emit.js +52 -0
- package/dist/material/factory.d.ts +27 -0
- package/dist/material/factory.js +25 -0
- package/dist/material/index.d.ts +8 -0
- package/dist/material/index.js +8 -0
- package/dist/material/materialStore.d.ts +24 -0
- package/dist/material/materialStore.js +76 -0
- package/dist/material/sampling.d.ts +21 -0
- package/dist/material/sampling.js +39 -0
- package/dist/material/sources.d.ts +25 -0
- package/dist/material/sources.js +33 -0
- package/dist/material/watermark.d.ts +21 -0
- package/dist/material/watermark.js +76 -0
- package/dist/observers/cursorRewriteObserver.d.ts +46 -0
- package/dist/observers/cursorRewriteObserver.js +103 -0
- package/dist/observers/distillObserver.d.ts +31 -0
- package/dist/observers/distillObserver.js +93 -0
- package/dist/observers/index.d.ts +5 -0
- package/dist/observers/index.js +5 -0
- package/dist/observers/observerBus.d.ts +44 -0
- package/dist/observers/observerBus.js +73 -0
- package/dist/observers/reflectionObserver.d.ts +31 -0
- package/dist/observers/reflectionObserver.js +124 -0
- package/dist/observers/valueDigestObserver.d.ts +50 -0
- package/dist/observers/valueDigestObserver.js +70 -0
- package/dist/ops/cleanup.d.ts +23 -0
- package/dist/ops/cleanup.js +68 -0
- package/dist/ops/health.d.ts +37 -0
- package/dist/ops/health.js +96 -0
- package/dist/ops/index.d.ts +7 -0
- package/dist/ops/index.js +7 -0
- package/dist/ops/recall.d.ts +65 -0
- package/dist/ops/recall.js +152 -0
- package/dist/ops/reuseOutcomes.d.ts +68 -0
- package/dist/ops/reuseOutcomes.js +126 -0
- package/dist/ops/savingsCore.d.ts +47 -0
- package/dist/ops/savingsCore.js +112 -0
- package/dist/ops/selfUpdate.d.ts +106 -0
- package/dist/ops/selfUpdate.js +294 -0
- package/dist/ops/valueLedger.d.ts +205 -0
- package/dist/ops/valueLedger.js +238 -0
- package/dist/ops/valueOutreach.d.ts +70 -0
- package/dist/ops/valueOutreach.js +244 -0
- package/dist/personality/drift.d.ts +45 -0
- package/dist/personality/drift.js +106 -0
- package/dist/personality/events.d.ts +25 -0
- package/dist/personality/events.js +37 -0
- package/dist/personality/evolveOps.d.ts +20 -0
- package/dist/personality/evolveOps.js +93 -0
- package/dist/personality/index.d.ts +11 -0
- package/dist/personality/index.js +11 -0
- package/dist/personality/mutate.d.ts +21 -0
- package/dist/personality/mutate.js +39 -0
- package/dist/personality/pivot.d.ts +23 -0
- package/dist/personality/pivot.js +22 -0
- package/dist/personality/prompt.d.ts +6 -0
- package/dist/personality/prompt.js +62 -0
- package/dist/personality/riskGate.d.ts +42 -0
- package/dist/personality/riskGate.js +80 -0
- package/dist/personality/schema.d.ts +255 -0
- package/dist/personality/schema.js +119 -0
- package/dist/personality/select.d.ts +46 -0
- package/dist/personality/select.js +71 -0
- package/dist/personality/stats.d.ts +46 -0
- package/dist/personality/stats.js +82 -0
- package/dist/personality/store.d.ts +35 -0
- package/dist/personality/store.js +92 -0
- package/dist/schema/common.d.ts +28 -0
- package/dist/schema/common.js +17 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.js +5 -0
- package/dist/schema/material.d.ts +201 -0
- package/dist/schema/material.js +50 -0
- package/dist/schema/problem.d.ts +160 -0
- package/dist/schema/problem.js +45 -0
- package/dist/schema/proofOfWork.d.ts +97 -0
- package/dist/schema/proofOfWork.js +9 -0
- package/dist/schema/signal.d.ts +41 -0
- package/dist/schema/signal.js +19 -0
- package/dist/shadow/index.d.ts +6 -0
- package/dist/shadow/index.js +6 -0
- package/dist/shadow/jsonlSink.d.ts +17 -0
- package/dist/shadow/jsonlSink.js +27 -0
- package/dist/shadow/redact.d.ts +11 -0
- package/dist/shadow/redact.js +18 -0
- package/dist/shadow/shadowHub.d.ts +12 -0
- package/dist/shadow/shadowHub.js +42 -0
- package/dist/shadow/shadowMailbox.d.ts +17 -0
- package/dist/shadow/shadowMailbox.js +25 -0
- package/dist/shadow/shadowStore.d.ts +18 -0
- package/dist/shadow/shadowStore.js +55 -0
- package/dist/shadow/sink.d.ts +37 -0
- package/dist/shadow/sink.js +18 -0
- package/dist/signals/cycleHistoryFromEvents.d.ts +11 -0
- package/dist/signals/cycleHistoryFromEvents.js +96 -0
- package/dist/signals/expand.d.ts +34 -0
- package/dist/signals/expand.js +125 -0
- package/dist/signals/extractor.d.ts +25 -0
- package/dist/signals/extractor.js +57 -0
- package/dist/signals/index.d.ts +6 -0
- package/dist/signals/index.js +6 -0
- package/dist/signals/metaSignals.d.ts +57 -0
- package/dist/signals/metaSignals.js +158 -0
- package/dist/signals/signalGate.d.ts +15 -0
- package/dist/signals/signalGate.js +40 -0
- package/dist/signals/traceSignals.d.ts +44 -0
- package/dist/signals/traceSignals.js +123 -0
- package/dist/signatures/classifier.d.ts +16 -0
- package/dist/signatures/classifier.js +29 -0
- package/dist/signatures/index.d.ts +2 -0
- package/dist/signatures/index.js +2 -0
- package/dist/signatures/signatures.d.ts +22 -0
- package/dist/signatures/signatures.js +42 -0
- package/dist/strategy/experiment.d.ts +48 -0
- package/dist/strategy/experiment.js +63 -0
- package/dist/strategy/index.d.ts +2 -0
- package/dist/strategy/index.js +2 -0
- package/dist/strategy/strategyPoint.d.ts +30 -0
- package/dist/strategy/strategyPoint.js +23 -0
- package/dist/trace/index.d.ts +2 -0
- package/dist/trace/index.js +2 -0
- package/dist/trace/trajectory.d.ts +82 -0
- package/dist/trace/trajectory.js +117 -0
- package/dist/trace/trajectoryExport.d.ts +237 -0
- package/dist/trace/trajectoryExport.js +1818 -0
- package/dist/trigger/budget.d.ts +16 -0
- package/dist/trigger/budget.js +20 -0
- package/dist/trigger/engine.d.ts +12 -0
- package/dist/trigger/engine.js +23 -0
- package/dist/trigger/index.d.ts +6 -0
- package/dist/trigger/index.js +6 -0
- package/dist/trigger/problemBuilder.d.ts +24 -0
- package/dist/trigger/problemBuilder.js +32 -0
- package/dist/trigger/trigger.d.ts +22 -0
- package/dist/trigger/trigger.js +27 -0
- package/dist/trigger/valueModel.d.ts +26 -0
- package/dist/trigger/valueModel.js +25 -0
- package/dist/trigger/wfq.d.ts +13 -0
- package/dist/trigger/wfq.js +25 -0
- package/dist/util/fileLock.d.ts +24 -0
- package/dist/util/fileLock.js +227 -0
- package/dist/util/index.d.ts +2 -0
- package/dist/util/index.js +2 -0
- package/dist/util/version.d.ts +12 -0
- package/dist/util/version.js +51 -0
- package/dist/verify/index.d.ts +4 -0
- package/dist/verify/index.js +4 -0
- package/dist/verify/rollback.d.ts +22 -0
- package/dist/verify/rollback.js +33 -0
- package/dist/verify/sandboxRunner.d.ts +39 -0
- package/dist/verify/sandboxRunner.js +116 -0
- package/dist/verify/sandboxedValidation.d.ts +35 -0
- package/dist/verify/sandboxedValidation.js +56 -0
- package/dist/verify/validation.d.ts +41 -0
- package/dist/verify/validation.js +85 -0
- package/dist/wire/canonicalize.golden.d.ts +23 -0
- package/dist/wire/canonicalize.golden.js +46 -0
- package/dist/wire/geneHints.d.ts +64 -0
- package/dist/wire/geneHints.js +100 -0
- package/dist/wire/index.d.ts +91 -0
- package/dist/wire/index.js +3 -0
- package/dist/wire/schemaGate.d.ts +11 -0
- package/dist/wire/schemaGate.js +49 -0
- package/dist/workflow/dsl.d.ts +54 -0
- package/dist/workflow/dsl.js +15 -0
- package/dist/workflow/engine.d.ts +30 -0
- package/dist/workflow/engine.js +94 -0
- package/dist/workflow/index.d.ts +2 -0
- package/dist/workflow/index.js +2 -0
- package/package.json +29 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { mkdirSync, existsSync, openSync, readSync, closeSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { ulid as makeUlid } from 'ulid';
|
|
5
|
+
import { createEnvelope } from './envelope.js';
|
|
6
|
+
// node:sqlite 是较新内建,只暴露 `node:sqlite`(无 `sqlite` 裸别名)。
|
|
7
|
+
// 经 createRequire 动态加载,避免打包器静态剥前缀成 `sqlite` 后解析失败(vitest/vite)。
|
|
8
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
9
|
+
const { DatabaseSync } = nodeRequire('node:sqlite');
|
|
10
|
+
export const MAX_ATTEMPTS = 5;
|
|
11
|
+
export const DEFAULT_IMPORT_JSONL_MAX_LINE_BYTES = 16 * 1024 * 1024;
|
|
12
|
+
const JSONL_READ_CHUNK_BYTES = 64 * 1024;
|
|
13
|
+
export function expBackoffMs(attempt) {
|
|
14
|
+
return Math.min(4000 * 2 ** (attempt - 1), 5 * 60 * 1000); // 4s→8s→…→5min
|
|
15
|
+
}
|
|
16
|
+
function forEachJsonlRecord(path, onRecord) {
|
|
17
|
+
if (!existsSync(path))
|
|
18
|
+
return;
|
|
19
|
+
const maxLineBytes = Number.isFinite(Number(process.env['EVOLVER_IMPORT_JSONL_MAX_LINE_BYTES']))
|
|
20
|
+
&& Number(process.env['EVOLVER_IMPORT_JSONL_MAX_LINE_BYTES']) > 0
|
|
21
|
+
? Math.floor(Number(process.env['EVOLVER_IMPORT_JSONL_MAX_LINE_BYTES']))
|
|
22
|
+
: DEFAULT_IMPORT_JSONL_MAX_LINE_BYTES;
|
|
23
|
+
const buf = Buffer.allocUnsafe(JSONL_READ_CHUNK_BYTES);
|
|
24
|
+
let fd;
|
|
25
|
+
let parts = [];
|
|
26
|
+
let lineBytes = 0;
|
|
27
|
+
let dropping = false;
|
|
28
|
+
const reset = () => { parts = []; lineBytes = 0; dropping = false; };
|
|
29
|
+
const append = (segment) => {
|
|
30
|
+
if (dropping || segment.length === 0)
|
|
31
|
+
return;
|
|
32
|
+
if (lineBytes + segment.length > maxLineBytes) {
|
|
33
|
+
reset();
|
|
34
|
+
dropping = true;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
parts.push(Buffer.from(segment));
|
|
38
|
+
lineBytes += segment.length;
|
|
39
|
+
};
|
|
40
|
+
const finish = () => {
|
|
41
|
+
if (dropping || lineBytes === 0) {
|
|
42
|
+
reset();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const text = Buffer.concat(parts, lineBytes).toString('utf8').trim();
|
|
46
|
+
reset();
|
|
47
|
+
if (!text)
|
|
48
|
+
return;
|
|
49
|
+
try {
|
|
50
|
+
onRecord(JSON.parse(text));
|
|
51
|
+
}
|
|
52
|
+
catch { /* skip corrupt rows */ }
|
|
53
|
+
};
|
|
54
|
+
try {
|
|
55
|
+
fd = openSync(path, 'r');
|
|
56
|
+
for (;;) {
|
|
57
|
+
const bytes = readSync(fd, buf, 0, buf.length, null);
|
|
58
|
+
if (bytes <= 0)
|
|
59
|
+
break;
|
|
60
|
+
let start = 0;
|
|
61
|
+
for (let i = 0; i < bytes; i += 1) {
|
|
62
|
+
if (buf[i] !== 0x0a)
|
|
63
|
+
continue;
|
|
64
|
+
append(buf.subarray(start, i));
|
|
65
|
+
finish();
|
|
66
|
+
start = i + 1;
|
|
67
|
+
}
|
|
68
|
+
if (start < bytes)
|
|
69
|
+
append(buf.subarray(start, bytes));
|
|
70
|
+
}
|
|
71
|
+
finish();
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
if (fd !== undefined)
|
|
75
|
+
closeSync(fd);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function rowToEnvelope(r) {
|
|
79
|
+
return {
|
|
80
|
+
id: r['id'], type: r['type'], direction: r['direction'],
|
|
81
|
+
status: r['status'], handler: r['handler'],
|
|
82
|
+
payload: r['payload'] ? JSON.parse(r['payload']) : {},
|
|
83
|
+
correlationId: r['correlationId'], replyTo: r['replyTo'] ?? null,
|
|
84
|
+
receiptId: r['receiptId'], idempotencyKey: r['idempotencyKey'],
|
|
85
|
+
sourceAgent: r['sourceAgent'], targetAgent: r['targetAgent'],
|
|
86
|
+
runtimeNamespace: r['runtimeNamespace'],
|
|
87
|
+
attempts: r['attempts'], nextRetryAt: r['nextRetryAt'] ?? null,
|
|
88
|
+
ttlAt: r['ttlAt'] ?? null,
|
|
89
|
+
createdAt: r['createdAt'], updatedAt: r['updatedAt'],
|
|
90
|
+
schemaVersion: r['schemaVersion'], feedsMaterial: Boolean(r['feedsMaterial']),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** mailbox sqlite 引擎 (WAL + busy_timeout). 状态机 pending→in_flight→done/failed/expired + 租约 + 重试/DLQ. */
|
|
94
|
+
export class MailboxStore {
|
|
95
|
+
db;
|
|
96
|
+
constructor(opts) {
|
|
97
|
+
mkdirSync(dirname(opts.path), { recursive: true });
|
|
98
|
+
this.db = new DatabaseSync(opts.path);
|
|
99
|
+
this.db.exec('PRAGMA journal_mode = WAL');
|
|
100
|
+
// PRAGMA can't be parameterized, so the value is string-interpolated — sanitize to a non-negative integer
|
|
101
|
+
// first so a non-numeric busyTimeoutMs can never become SQL injection (defense-in-depth; #196).
|
|
102
|
+
const rawBusy = Number(opts.busyTimeoutMs ?? 5000);
|
|
103
|
+
const busyTimeoutMs = Number.isFinite(rawBusy) ? Math.max(0, Math.floor(rawBusy)) : 5000;
|
|
104
|
+
this.db.exec(`PRAGMA busy_timeout = ${busyTimeoutMs}`);
|
|
105
|
+
this.db.exec(`CREATE TABLE IF NOT EXISTS messages (
|
|
106
|
+
id TEXT PRIMARY KEY, type TEXT, direction TEXT, status TEXT, handler TEXT,
|
|
107
|
+
payload TEXT, correlationId TEXT, replyTo TEXT, receiptId TEXT, idempotencyKey TEXT,
|
|
108
|
+
sourceAgent TEXT, targetAgent TEXT, runtimeNamespace TEXT,
|
|
109
|
+
attempts INTEGER, nextRetryAt INTEGER, ttlAt INTEGER, createdAt INTEGER, updatedAt INTEGER,
|
|
110
|
+
schemaVersion TEXT, feedsMaterial INTEGER, dlq INTEGER DEFAULT 0, leasedUntil INTEGER, workerId TEXT, lastError TEXT)`);
|
|
111
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_status ON messages(status, handler)');
|
|
112
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_corr ON messages(correlationId)');
|
|
113
|
+
this.db.exec('CREATE TABLE IF NOT EXISTS idempotency (key TEXT PRIMARY KEY, result TEXT, at INTEGER)');
|
|
114
|
+
this.db.exec('CREATE TABLE IF NOT EXISTS kv (k TEXT PRIMARY KEY, v TEXT)');
|
|
115
|
+
}
|
|
116
|
+
/** 通用 KV 状态(M6: sync 游标 inbound_cursor / lifecycle reauth 退避 / node_id 等). */
|
|
117
|
+
getState(key) {
|
|
118
|
+
const r = this.db.prepare('SELECT v FROM kv WHERE k=?').get(key);
|
|
119
|
+
return r ? r['v'] : undefined;
|
|
120
|
+
}
|
|
121
|
+
setState(key, value) {
|
|
122
|
+
this.db.prepare('INSERT INTO kv (k,v) VALUES (?,?) ON CONFLICT(k) DO UPDATE SET v=excluded.v').run(key, value);
|
|
123
|
+
}
|
|
124
|
+
/** 投递; 同 id 幂等(OR IGNORE). 返回 receiptId. */
|
|
125
|
+
send(e) {
|
|
126
|
+
const r = this.db.prepare(`INSERT OR IGNORE INTO messages
|
|
127
|
+
(id,type,direction,status,handler,payload,correlationId,replyTo,receiptId,idempotencyKey,sourceAgent,targetAgent,runtimeNamespace,attempts,nextRetryAt,ttlAt,createdAt,updatedAt,schemaVersion,feedsMaterial,dlq)
|
|
128
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,0)`).run(e.id, e.type, e.direction, e.status, e.handler, JSON.stringify(e.payload), e.correlationId, e.replyTo, e.receiptId, e.idempotencyKey, e.sourceAgent, e.targetAgent, e.runtimeNamespace, e.attempts, e.nextRetryAt, e.ttlAt, e.createdAt, e.updatedAt, e.schemaVersion, e.feedsMaterial ? 1 : 0);
|
|
129
|
+
return { receiptId: e.id, stored: Number(r.changes) > 0 };
|
|
130
|
+
}
|
|
131
|
+
getById(id) {
|
|
132
|
+
const r = this.db.prepare('SELECT * FROM messages WHERE id = ?').get(id);
|
|
133
|
+
return r ? rowToEnvelope(r) : undefined;
|
|
134
|
+
}
|
|
135
|
+
list(opts = {}) {
|
|
136
|
+
const where = [];
|
|
137
|
+
const args = [];
|
|
138
|
+
if (opts.status) {
|
|
139
|
+
where.push('status = ?');
|
|
140
|
+
args.push(opts.status);
|
|
141
|
+
}
|
|
142
|
+
if (opts.handler) {
|
|
143
|
+
where.push('handler = ?');
|
|
144
|
+
args.push(opts.handler);
|
|
145
|
+
}
|
|
146
|
+
if (opts.runtimeNamespace) {
|
|
147
|
+
where.push('runtimeNamespace = ?');
|
|
148
|
+
args.push(opts.runtimeNamespace);
|
|
149
|
+
}
|
|
150
|
+
const sql = `SELECT * FROM messages ${where.length ? `WHERE ${where.join(' AND ')}` : ''} ORDER BY createdAt LIMIT ?`;
|
|
151
|
+
// Clamp the LIMIT bind. The IPC list route feeds this `?limit=` straight from an external query string, so a
|
|
152
|
+
// non-finite value (NaN from `?limit=abc`, Infinity from `?limit=1e400`) would bind as null/error and a huge
|
|
153
|
+
// finite value is an unbounded read (memory DoS) even on the token-authed endpoint. Finite + positive + capped.
|
|
154
|
+
const lim = Number.isFinite(opts.limit) && opts.limit > 0
|
|
155
|
+
? Math.min(Math.floor(opts.limit), 10_000)
|
|
156
|
+
: 1000;
|
|
157
|
+
args.push(lim);
|
|
158
|
+
return this.db.prepare(sql).all(...args).map(rowToEnvelope);
|
|
159
|
+
}
|
|
160
|
+
countByStatus(status) {
|
|
161
|
+
return Number(this.db.prepare('SELECT COUNT(*) c FROM messages WHERE status = ?').get(status)['c']);
|
|
162
|
+
}
|
|
163
|
+
/** pending 计数(可按 handler/runtimeNamespace 分区), 用于 agent wake 去抖. */
|
|
164
|
+
countPending(handler, runtimeNamespace) {
|
|
165
|
+
const where = ["status='pending'", 'dlq=0'];
|
|
166
|
+
const args = [];
|
|
167
|
+
if (handler !== undefined) {
|
|
168
|
+
where.push('handler=?');
|
|
169
|
+
args.push(handler);
|
|
170
|
+
}
|
|
171
|
+
if (runtimeNamespace !== undefined) {
|
|
172
|
+
where.push('runtimeNamespace=?');
|
|
173
|
+
args.push(runtimeNamespace);
|
|
174
|
+
}
|
|
175
|
+
return Number(this.db.prepare(`SELECT COUNT(*) c FROM messages WHERE ${where.join(' AND ')}`).get(...args)['c']);
|
|
176
|
+
}
|
|
177
|
+
/** 只统计「现在就能 claim」的出站, 供 cadence 判定, 避免 deferred 拉低 idle 退避.
|
|
178
|
+
* 谓词须与 claim() 对齐: 到点 pending + 租约过期的 in_flight 孤儿; 否则孤儿出站(claim 能回收却不被计)会被误判 idle, 恢复最多慢一个 idle 周期. */
|
|
179
|
+
countClaimable(handler, now, runtimeNamespace) {
|
|
180
|
+
const where = ['dlq=0', 'handler=?', "(status='pending' OR (status='in_flight' AND leasedUntil < ?))", '(nextRetryAt IS NULL OR nextRetryAt <= ?)'];
|
|
181
|
+
const args = [handler, now, now];
|
|
182
|
+
if (runtimeNamespace !== undefined) {
|
|
183
|
+
where.push('runtimeNamespace=?');
|
|
184
|
+
args.push(runtimeNamespace);
|
|
185
|
+
}
|
|
186
|
+
return Number(this.db.prepare(`SELECT COUNT(*) c FROM messages WHERE ${where.join(' AND ')}`).get(...args)['c']);
|
|
187
|
+
}
|
|
188
|
+
hasMessageWithIdempotencyKey(idempotencyKey) {
|
|
189
|
+
return this.db.prepare('SELECT 1 FROM messages WHERE idempotencyKey = ? LIMIT 1').get(idempotencyKey) !== undefined;
|
|
190
|
+
}
|
|
191
|
+
hasMessageWithPayload(type, payload) {
|
|
192
|
+
return this.db.prepare('SELECT 1 FROM messages WHERE type = ? AND payload = ? LIMIT 1')
|
|
193
|
+
.get(type, JSON.stringify(payload)) !== undefined;
|
|
194
|
+
}
|
|
195
|
+
/** 原子 claim: pending(或租约过期的 in_flight) → in_flight + 租约. 可按 runtimeNamespace 分区. */
|
|
196
|
+
claim(handler, limit, leaseMs, now, runtimeNamespace) {
|
|
197
|
+
this.db.exec('BEGIN IMMEDIATE');
|
|
198
|
+
try {
|
|
199
|
+
const nsClause = runtimeNamespace !== undefined ? ' AND runtimeNamespace=?' : '';
|
|
200
|
+
const nsArgs = runtimeNamespace !== undefined ? [runtimeNamespace] : [];
|
|
201
|
+
const rows = this.db.prepare(`SELECT * FROM messages WHERE handler=? AND dlq=0
|
|
202
|
+
AND (status='pending' OR (status='in_flight' AND leasedUntil < ?))
|
|
203
|
+
AND (nextRetryAt IS NULL OR nextRetryAt <= ?)${nsClause}
|
|
204
|
+
ORDER BY createdAt LIMIT ?`).all(handler, now, now, ...nsArgs, limit);
|
|
205
|
+
const workerId = makeUlid();
|
|
206
|
+
const upd = this.db.prepare(`UPDATE messages SET status='in_flight', leasedUntil=?, workerId=?, updatedAt=? WHERE id=?`);
|
|
207
|
+
for (const r of rows)
|
|
208
|
+
upd.run(now + leaseMs, workerId, now, r['id']);
|
|
209
|
+
this.db.exec('COMMIT');
|
|
210
|
+
return rows.map((r) => ({ ...rowToEnvelope(r), status: 'in_flight' }));
|
|
211
|
+
}
|
|
212
|
+
catch (e) {
|
|
213
|
+
this.db.exec('ROLLBACK');
|
|
214
|
+
throw e;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
complete(id, now) {
|
|
218
|
+
this.db.prepare(`UPDATE messages SET status='done', leasedUntil=NULL, workerId=NULL, updatedAt=? WHERE id=?`).run(now, id);
|
|
219
|
+
}
|
|
220
|
+
/** 失败: attempts<N→退避回 pending; ≥N→DLQ(不自动丢). */
|
|
221
|
+
fail(id, err, now, maxAttempts = MAX_ATTEMPTS) {
|
|
222
|
+
const m = this.getById(id);
|
|
223
|
+
if (!m)
|
|
224
|
+
return;
|
|
225
|
+
const attempts = m.attempts + 1;
|
|
226
|
+
if (attempts >= maxAttempts) {
|
|
227
|
+
this.db.prepare(`UPDATE messages SET status='failed', dlq=1, attempts=?, lastError=?, leasedUntil=NULL, updatedAt=? WHERE id=?`).run(attempts, err, now, id);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
this.db.prepare(`UPDATE messages SET status='pending', attempts=?, nextRetryAt=?, lastError=?, leasedUntil=NULL, workerId=NULL, updatedAt=? WHERE id=?`)
|
|
231
|
+
.run(attempts, now + expBackoffMs(attempts), err, now, id);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/** 暂缓: transient upstream outage 不消耗 attempts, 仅设置下次可 claim 时间. */
|
|
235
|
+
defer(id, err, now, retryAfterMs) {
|
|
236
|
+
const delay = Math.max(0, Number.isFinite(retryAfterMs) ? retryAfterMs : 0);
|
|
237
|
+
this.db.prepare(`UPDATE messages SET status='pending', nextRetryAt=?, lastError=?, leasedUntil=NULL, workerId=NULL, updatedAt=? WHERE id=?`)
|
|
238
|
+
.run(now + delay, err, now, id);
|
|
239
|
+
}
|
|
240
|
+
/** DLQ 重放(人工/agent 显式, 不自动丢). */
|
|
241
|
+
replayDlq(id, now) {
|
|
242
|
+
this.db.prepare(`UPDATE messages SET status='pending', dlq=0, attempts=0, nextRetryAt=NULL, leasedUntil=NULL, updatedAt=? WHERE id=?`).run(now, id);
|
|
243
|
+
}
|
|
244
|
+
dlq() { return this.db.prepare('SELECT * FROM messages WHERE dlq=1').all().map(rowToEnvelope); }
|
|
245
|
+
/** TTL 扫描: ttlAt 过期且未 done/dlq → expired. */
|
|
246
|
+
expireOld(now) {
|
|
247
|
+
const r = this.db.prepare(`UPDATE messages SET status='expired', updatedAt=? WHERE ttlAt < ? AND status NOT IN ('done','expired') AND dlq=0`).run(now, now);
|
|
248
|
+
return Number(r.changes);
|
|
249
|
+
}
|
|
250
|
+
/** M2-5 关联线程: 同 correlationId 全部消息(请求+应答), 按 createdAt 排序. */
|
|
251
|
+
findByCorrelation(correlationId) {
|
|
252
|
+
return this.db.prepare('SELECT * FROM messages WHERE correlationId=? ORDER BY createdAt').all(correlationId).map(rowToEnvelope);
|
|
253
|
+
}
|
|
254
|
+
/** 关联线程中除 requestId 外最新一条 = 应答(durable, 跨重启/进程). */
|
|
255
|
+
getReply(correlationId, requestId) {
|
|
256
|
+
const r = this.db.prepare('SELECT * FROM messages WHERE correlationId=? AND id<>? ORDER BY createdAt DESC LIMIT 1').get(correlationId, requestId);
|
|
257
|
+
return r ? rowToEnvelope(r) : undefined;
|
|
258
|
+
}
|
|
259
|
+
/** 构造并投递一条对 `to` 的应答(继承 correlationId, 收发方对调, replyTo 清空). */
|
|
260
|
+
reply(to, replyType, payload, now, over = {}) {
|
|
261
|
+
const env = createEnvelope({
|
|
262
|
+
type: replyType,
|
|
263
|
+
payload,
|
|
264
|
+
correlationId: to.correlationId,
|
|
265
|
+
sourceAgent: to.targetAgent,
|
|
266
|
+
targetAgent: to.sourceAgent,
|
|
267
|
+
runtimeNamespace: to.runtimeNamespace,
|
|
268
|
+
replyTo: null,
|
|
269
|
+
now,
|
|
270
|
+
...over,
|
|
271
|
+
});
|
|
272
|
+
const res = this.send(env);
|
|
273
|
+
return { envelope: env, ...res };
|
|
274
|
+
}
|
|
275
|
+
/** M2-5 轻量状态视图(运维/IPC 查询用). */
|
|
276
|
+
getStatus(id) {
|
|
277
|
+
const r = this.db.prepare('SELECT id,type,status,attempts,nextRetryAt,ttlAt,dlq FROM messages WHERE id=?').get(id);
|
|
278
|
+
if (!r)
|
|
279
|
+
return undefined;
|
|
280
|
+
return {
|
|
281
|
+
id: r['id'], type: r['type'], status: r['status'],
|
|
282
|
+
attempts: Number(r['attempts']), nextRetryAt: r['nextRetryAt'] ?? null,
|
|
283
|
+
ttlAt: r['ttlAt'] ?? null, dlq: Number(r['dlq']) === 1,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/** 幂等(A13): 副作用 handler 用 idempotencyKey 去重; 命中返缓存不重跑. */
|
|
287
|
+
isProcessed(key) { return this.db.prepare('SELECT 1 FROM idempotency WHERE key=?').get(key) !== undefined; }
|
|
288
|
+
getProcessed(key) {
|
|
289
|
+
const r = this.db.prepare('SELECT result FROM idempotency WHERE key=?').get(key);
|
|
290
|
+
return r ? JSON.parse(r['result']) : undefined;
|
|
291
|
+
}
|
|
292
|
+
markProcessed(key, result, now) {
|
|
293
|
+
this.db.prepare('INSERT OR IGNORE INTO idempotency (key,result,at) VALUES (?,?,?)').run(key, JSON.stringify(result ?? null), now);
|
|
294
|
+
}
|
|
295
|
+
/** v1 messages.jsonl → sqlite 迁移(幂等可重入). */
|
|
296
|
+
importJsonl(path) {
|
|
297
|
+
let n = 0;
|
|
298
|
+
forEachJsonlRecord(path, (record) => {
|
|
299
|
+
try {
|
|
300
|
+
if (this.send(record).stored)
|
|
301
|
+
n += 1;
|
|
302
|
+
}
|
|
303
|
+
catch { /* skip */ }
|
|
304
|
+
});
|
|
305
|
+
return n;
|
|
306
|
+
}
|
|
307
|
+
close() { this.db.close(); }
|
|
308
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { MailboxStore } from './store.js';
|
|
2
|
+
export type WakeMode = 'resident' | 'one-shot';
|
|
3
|
+
export type WakeAction = 'signal' | 'spawn' | 'none';
|
|
4
|
+
/** 一个 runtime 实例的唤醒目标(常驻 vs 一次性). */
|
|
5
|
+
export interface WakeTarget {
|
|
6
|
+
runtimeNamespace: string;
|
|
7
|
+
mode: WakeMode;
|
|
8
|
+
/** resident: 唤醒已跑进程(本地信号/写 wake 文件/HTTP poke). */
|
|
9
|
+
signal?: () => Promise<void> | void;
|
|
10
|
+
/** one-shot 或 resident 已死: 拉起进程(spawn `claude` 等). */
|
|
11
|
+
spawn?: () => Promise<void> | void;
|
|
12
|
+
/** resident 存活探测; 返回 false → 回退到 spawn. 默认视为存活. */
|
|
13
|
+
isAlive?: () => boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface AgentWakerDeps {
|
|
16
|
+
store: MailboxStore;
|
|
17
|
+
/** 静态列表或每次 tick 动态求值(runtime 注册/注销). */
|
|
18
|
+
targets: WakeTarget[] | (() => WakeTarget[]);
|
|
19
|
+
/** 同 namespace 最小唤醒间隔(去抖, 默认 2s). */
|
|
20
|
+
cooldownMs?: number;
|
|
21
|
+
now: () => number;
|
|
22
|
+
}
|
|
23
|
+
export interface WakeOutcome {
|
|
24
|
+
runtimeNamespace: string;
|
|
25
|
+
action: WakeAction;
|
|
26
|
+
pending: number;
|
|
27
|
+
reason?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Agent 唤醒器(M2-8). 监视各 namespace 的 pending agent 消息, 去抖后唤醒 runtime:
|
|
31
|
+
* - resident 且存活 → signal(轻量 poke).
|
|
32
|
+
* - one-shot 或 resident 已死 → spawn(拉起进程).
|
|
33
|
+
* runtime 起来后经 IPC(M2-6) claim('agent') 自取消息.
|
|
34
|
+
*/
|
|
35
|
+
export declare class AgentWaker {
|
|
36
|
+
private readonly deps;
|
|
37
|
+
private readonly lastWake;
|
|
38
|
+
private readonly cooldownMs;
|
|
39
|
+
constructor(deps: AgentWakerDeps);
|
|
40
|
+
private resolveTargets;
|
|
41
|
+
/** 扫一轮: 对有 pending agent 工作且过了冷却的 namespace 触发唤醒. */
|
|
42
|
+
tick(): Promise<WakeOutcome[]>;
|
|
43
|
+
/** 测试/强制: 清冷却(下次 tick 必唤醒). */
|
|
44
|
+
resetCooldown(runtimeNamespace?: string): void;
|
|
45
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent 唤醒器(M2-8). 监视各 namespace 的 pending agent 消息, 去抖后唤醒 runtime:
|
|
3
|
+
* - resident 且存活 → signal(轻量 poke).
|
|
4
|
+
* - one-shot 或 resident 已死 → spawn(拉起进程).
|
|
5
|
+
* runtime 起来后经 IPC(M2-6) claim('agent') 自取消息.
|
|
6
|
+
*/
|
|
7
|
+
export class AgentWaker {
|
|
8
|
+
deps;
|
|
9
|
+
lastWake = new Map();
|
|
10
|
+
cooldownMs;
|
|
11
|
+
constructor(deps) {
|
|
12
|
+
this.deps = deps;
|
|
13
|
+
this.cooldownMs = deps.cooldownMs ?? 2000;
|
|
14
|
+
}
|
|
15
|
+
resolveTargets() {
|
|
16
|
+
return typeof this.deps.targets === 'function' ? this.deps.targets() : this.deps.targets;
|
|
17
|
+
}
|
|
18
|
+
/** 扫一轮: 对有 pending agent 工作且过了冷却的 namespace 触发唤醒. */
|
|
19
|
+
async tick() {
|
|
20
|
+
const now = this.deps.now();
|
|
21
|
+
const out = [];
|
|
22
|
+
for (const t of this.resolveTargets()) {
|
|
23
|
+
const pending = this.deps.store.countPending('agent', t.runtimeNamespace);
|
|
24
|
+
if (pending === 0) {
|
|
25
|
+
out.push({ runtimeNamespace: t.runtimeNamespace, action: 'none', pending, reason: 'idle' });
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const last = this.lastWake.get(t.runtimeNamespace) ?? -Infinity;
|
|
29
|
+
if (now - last < this.cooldownMs) {
|
|
30
|
+
out.push({ runtimeNamespace: t.runtimeNamespace, action: 'none', pending, reason: 'cooldown' });
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const alive = t.mode === 'resident' && (t.isAlive ? t.isAlive() : true);
|
|
34
|
+
let action = 'none';
|
|
35
|
+
if (alive && t.signal) {
|
|
36
|
+
await t.signal();
|
|
37
|
+
action = 'signal';
|
|
38
|
+
}
|
|
39
|
+
else if (t.spawn) {
|
|
40
|
+
await t.spawn();
|
|
41
|
+
action = 'spawn';
|
|
42
|
+
}
|
|
43
|
+
else if (t.signal) {
|
|
44
|
+
await t.signal();
|
|
45
|
+
action = 'signal';
|
|
46
|
+
} // 无 spawn 回退 signal
|
|
47
|
+
if (action !== 'none')
|
|
48
|
+
this.lastWake.set(t.runtimeNamespace, now);
|
|
49
|
+
out.push({ runtimeNamespace: t.runtimeNamespace, action, pending });
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
/** 测试/强制: 清冷却(下次 tick 必唤醒). */
|
|
54
|
+
resetCooldown(runtimeNamespace) {
|
|
55
|
+
if (runtimeNamespace)
|
|
56
|
+
this.lastWake.delete(runtimeNamespace);
|
|
57
|
+
else
|
|
58
|
+
this.lastWake.clear();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** 来源边界 (批注#37): Material 只来自 session/tool log, 杜绝 root_events/asset/MV 回灌. */
|
|
2
|
+
const FORBIDDEN = [
|
|
3
|
+
/root_events\.jsonl/, /genes\.jsonl/, /capsules\.jsonl/, /\bevents\.jsonl/,
|
|
4
|
+
/[/\\]mv[/\\]/, /[/\\]assets[/\\]/,
|
|
5
|
+
];
|
|
6
|
+
export class MaterialBoundaryError extends Error {
|
|
7
|
+
constructor(msg) { super(`来源边界违规: ${msg}`); this.name = 'MaterialBoundaryError'; }
|
|
8
|
+
}
|
|
9
|
+
export function assertMaterialSource(m) {
|
|
10
|
+
if (m.kind !== 'session_log' && m.kind !== 'tool_event' && m.kind !== 'llm_trace')
|
|
11
|
+
throw new MaterialBoundaryError(`非法 kind: ${m.kind} (只允许 session_log/tool_event/llm_trace)`);
|
|
12
|
+
if (FORBIDDEN.some((re) => re.test(m.sourcePath)))
|
|
13
|
+
throw new MaterialBoundaryError(`禁止把资产/事件层回灌为 Material: ${m.sourcePath}`);
|
|
14
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { MaterialStore } from './materialStore.js';
|
|
2
|
+
import type { Material } from '../schema/material.js';
|
|
3
|
+
export interface ConsumerOptions {
|
|
4
|
+
store: MaterialStore;
|
|
5
|
+
path?: string;
|
|
6
|
+
}
|
|
7
|
+
/** consumer group + 进度游标 (at-least-once): committed 仅 ack 后推进, 防重复消费. */
|
|
8
|
+
export declare class ConsumerGroups {
|
|
9
|
+
private readonly opts;
|
|
10
|
+
private readonly committed;
|
|
11
|
+
constructor(opts: ConsumerOptions);
|
|
12
|
+
private cur;
|
|
13
|
+
/** 取下一批未 ack 的 (不推进 committed → 崩溃重启再 claim 拿到同批). */
|
|
14
|
+
claim(group: string, batchSize: number): Material[];
|
|
15
|
+
/** ack: 把 committed 推过 acked 的连续前缀. */
|
|
16
|
+
ack(group: string, materialIds: string[]): void;
|
|
17
|
+
/** reset: 'earliest' 重放全部历史 / 指定 index. */
|
|
18
|
+
reset(group: string, to: 'earliest' | number): void;
|
|
19
|
+
position(group: string): number;
|
|
20
|
+
private persist;
|
|
21
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
/** consumer group + 进度游标 (at-least-once): committed 仅 ack 后推进, 防重复消费. */
|
|
4
|
+
export class ConsumerGroups {
|
|
5
|
+
opts;
|
|
6
|
+
committed = new Map();
|
|
7
|
+
constructor(opts) {
|
|
8
|
+
this.opts = opts;
|
|
9
|
+
// A corrupt cursor (a crash mid-persist) must NOT crash the resident daemon at startup: degrade to empty
|
|
10
|
+
// (re-claim from 0). At-least-once + idempotent downstream make re-processing safe, far better than a crash loop.
|
|
11
|
+
if (opts.path && existsSync(opts.path)) {
|
|
12
|
+
try {
|
|
13
|
+
const o = JSON.parse(readFileSync(opts.path, 'utf8'));
|
|
14
|
+
for (const [k, v] of Object.entries(o))
|
|
15
|
+
if (typeof v === 'number' && Number.isFinite(v))
|
|
16
|
+
this.committed.set(k, v);
|
|
17
|
+
}
|
|
18
|
+
catch { /* corrupt cursor → start from 0 (idempotent re-claim) */ }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
cur(group) { return this.committed.get(group) ?? 0; }
|
|
22
|
+
/** 取下一批未 ack 的 (不推进 committed → 崩溃重启再 claim 拿到同批). */
|
|
23
|
+
claim(group, batchSize) {
|
|
24
|
+
const all = this.opts.store.readAll();
|
|
25
|
+
return all.slice(this.cur(group), this.cur(group) + batchSize);
|
|
26
|
+
}
|
|
27
|
+
/** ack: 把 committed 推过 acked 的连续前缀. */
|
|
28
|
+
ack(group, materialIds) {
|
|
29
|
+
const ackSet = new Set(materialIds);
|
|
30
|
+
const all = this.opts.store.readAll();
|
|
31
|
+
let c = this.cur(group);
|
|
32
|
+
while (c < all.length && ackSet.has(all[c].materialId))
|
|
33
|
+
c += 1;
|
|
34
|
+
this.committed.set(group, c);
|
|
35
|
+
this.persist();
|
|
36
|
+
}
|
|
37
|
+
/** reset: 'earliest' 重放全部历史 / 指定 index. */
|
|
38
|
+
reset(group, to) {
|
|
39
|
+
this.committed.set(group, to === 'earliest' ? 0 : to);
|
|
40
|
+
this.persist();
|
|
41
|
+
}
|
|
42
|
+
position(group) { return this.cur(group); }
|
|
43
|
+
persist() {
|
|
44
|
+
if (!this.opts.path)
|
|
45
|
+
return;
|
|
46
|
+
mkdirSync(dirname(this.opts.path), { recursive: true });
|
|
47
|
+
// Atomic write: a crash mid-write must leave the OLD cursor intact, not a truncated/corrupt file.
|
|
48
|
+
const tmp = `${this.opts.path}.tmp`;
|
|
49
|
+
writeFileSync(tmp, JSON.stringify(Object.fromEntries(this.committed)));
|
|
50
|
+
renameSync(tmp, this.opts.path);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Material } from '../schema/material.js';
|
|
2
|
+
export interface MailboxEnvelope {
|
|
3
|
+
type: string;
|
|
4
|
+
payload: unknown;
|
|
5
|
+
}
|
|
6
|
+
/** M2 mailbox 引擎的最小接口; M1 用 jsonl 兜底, M2 替换(接口不变). */
|
|
7
|
+
export interface MailboxSink {
|
|
8
|
+
enqueue(e: MailboxEnvelope): Promise<{
|
|
9
|
+
receiptId: string;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
/** material 事件 payload (批注#40). source_agent 仅 runtime_session 有 (proxy trace agent-无关, #95). */
|
|
13
|
+
export interface MaterialEventPayload {
|
|
14
|
+
material_id: string;
|
|
15
|
+
source_agent?: string;
|
|
16
|
+
source_kind: string;
|
|
17
|
+
source_path: string;
|
|
18
|
+
kind: string;
|
|
19
|
+
size: number;
|
|
20
|
+
hash: string;
|
|
21
|
+
discovered_at: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function toMaterialEventPayload(m: Material): MaterialEventPayload;
|
|
24
|
+
/** jsonl 兜底 MailboxSink (M2 落地后替换为真引擎). */
|
|
25
|
+
export declare class JsonlMailboxSink implements MailboxSink {
|
|
26
|
+
private readonly path;
|
|
27
|
+
constructor(path: string);
|
|
28
|
+
enqueue(e: MailboxEnvelope): Promise<{
|
|
29
|
+
receiptId: string;
|
|
30
|
+
}>;
|
|
31
|
+
readAll(): Array<{
|
|
32
|
+
receiptId: string;
|
|
33
|
+
type: string;
|
|
34
|
+
payload: unknown;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
/** 攒批 + 投递 pending_materials (批注#40, mailbox 草案 §4). */
|
|
38
|
+
export declare class MaterialEmitter {
|
|
39
|
+
private readonly sink;
|
|
40
|
+
private readonly batchSize;
|
|
41
|
+
private readonly buffer;
|
|
42
|
+
constructor(sink: MailboxSink, batchSize?: number);
|
|
43
|
+
add(m: Material): void;
|
|
44
|
+
size(): number;
|
|
45
|
+
flush(): Promise<{
|
|
46
|
+
receiptId: string;
|
|
47
|
+
batchId: string;
|
|
48
|
+
count: number;
|
|
49
|
+
} | null>;
|
|
50
|
+
addAndMaybeFlush(m: Material): Promise<{
|
|
51
|
+
receiptId: string;
|
|
52
|
+
batchId: string;
|
|
53
|
+
count: number;
|
|
54
|
+
} | null>;
|
|
55
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, readFileSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { ulid as makeUlid } from 'ulid';
|
|
4
|
+
export function toMaterialEventPayload(m) {
|
|
5
|
+
return {
|
|
6
|
+
material_id: m.materialId, ...(m.sourceAgent ? { source_agent: m.sourceAgent } : {}), source_kind: m.sourceKind,
|
|
7
|
+
source_path: m.sourcePath,
|
|
8
|
+
kind: m.kind, size: m.watermark.size, hash: m.watermark.contentHash ?? '', discovered_at: m.capturedAt,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/** jsonl 兜底 MailboxSink (M2 落地后替换为真引擎). */
|
|
12
|
+
export class JsonlMailboxSink {
|
|
13
|
+
path;
|
|
14
|
+
constructor(path) {
|
|
15
|
+
this.path = path;
|
|
16
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
async enqueue(e) {
|
|
19
|
+
const receiptId = makeUlid();
|
|
20
|
+
appendFileSync(this.path, `${JSON.stringify({ receiptId, ...e })}\n`);
|
|
21
|
+
return { receiptId };
|
|
22
|
+
}
|
|
23
|
+
readAll() {
|
|
24
|
+
if (!existsSync(this.path))
|
|
25
|
+
return [];
|
|
26
|
+
return readFileSync(this.path, 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** 攒批 + 投递 pending_materials (批注#40, mailbox 草案 §4). */
|
|
30
|
+
export class MaterialEmitter {
|
|
31
|
+
sink;
|
|
32
|
+
batchSize;
|
|
33
|
+
buffer = [];
|
|
34
|
+
constructor(sink, batchSize = 20) {
|
|
35
|
+
this.sink = sink;
|
|
36
|
+
this.batchSize = batchSize;
|
|
37
|
+
}
|
|
38
|
+
add(m) { this.buffer.push(toMaterialEventPayload(m)); }
|
|
39
|
+
size() { return this.buffer.length; }
|
|
40
|
+
async flush() {
|
|
41
|
+
if (this.buffer.length === 0)
|
|
42
|
+
return null;
|
|
43
|
+
const batchId = makeUlid();
|
|
44
|
+
const materials = this.buffer.splice(0);
|
|
45
|
+
const { receiptId } = await this.sink.enqueue({ type: 'pending_materials', payload: { batchId, materials } });
|
|
46
|
+
return { receiptId, batchId, count: materials.length };
|
|
47
|
+
}
|
|
48
|
+
async addAndMaybeFlush(m) {
|
|
49
|
+
this.add(m);
|
|
50
|
+
return this.buffer.length >= this.batchSize ? this.flush() : null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Material } from '../schema/material.js';
|
|
2
|
+
import type { SourceAgent } from '../schema/common.js';
|
|
3
|
+
import type { Watermark } from '../schema/material.js';
|
|
4
|
+
/** Fresh ULID for a Material record. Kept in core so the ULID constraint lives next to the schema. */
|
|
5
|
+
export declare function newMaterialId(): string;
|
|
6
|
+
export interface BuildMaterialInput {
|
|
7
|
+
/** Runtime agent for a session/tool source. Omit for an agent-agnostic origin (proxy trace), #95. */
|
|
8
|
+
sourceAgent?: SourceAgent;
|
|
9
|
+
/** Origin class (#95). Defaults to runtime_session (the only pre-#95 origin). */
|
|
10
|
+
sourceKind?: 'runtime_session' | 'proxy_trace';
|
|
11
|
+
sourcePath: string;
|
|
12
|
+
kind: 'session_log' | 'tool_event' | 'llm_trace';
|
|
13
|
+
watermark: Watermark;
|
|
14
|
+
consumerGroup: string;
|
|
15
|
+
payload?: unknown;
|
|
16
|
+
/** Injected for determinism in tests; defaults to a fresh ULID. */
|
|
17
|
+
materialId?: string;
|
|
18
|
+
/** Injected for determinism in tests; defaults to wall-clock ISO time. */
|
|
19
|
+
capturedAt?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build a schema-valid Material from a recording event. The composition layer (CLI) owns the source content;
|
|
23
|
+
* core owns the record shape + id/clock generation. Re-record idempotency is the caller's concern (see the
|
|
24
|
+
* file watermark cursor): identical content yields a fresh materialId here, so dedup must gate the call, not
|
|
25
|
+
* the build (a Material's identity is its ULID, not its content — that is the M1 substrate's design).
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildMaterial(input: BuildMaterialInput): Material;
|