@theseus-whale/dsh-continuity 0.0.0-stage → 4.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +25 -0
- package/PANEL.md +92 -0
- package/README.md +286 -2
- package/RELEASE-NOTES.md +732 -0
- package/bin/bc-acceptance-full.mjs +91 -0
- package/bin/bc-acceptance-tight.mjs +66 -0
- package/bin/bc-acceptance.mjs +22 -0
- package/bin/bc-add-keywords.mjs +21 -0
- package/bin/bc-demo.mjs +47 -0
- package/bin/bc-expand-hint.mjs +179 -0
- package/bin/bc-ingest-index.mjs +35 -0
- package/bin/bc-ingest.mjs +31 -0
- package/bin/bc-label-demo.mjs +18 -0
- package/bin/bc-relabel.mjs +150 -0
- package/bin/bc-sessionize.mjs +59 -0
- package/bin/coref_pipeline.py +47 -0
- package/bin/coref_service.py +57 -0
- package/bin/debug_ckip.py +16 -0
- package/bin/debug_ner_zh.py +18 -0
- package/bin/doctor.mjs +275 -0
- package/bin/en_ner_error_audit.py +33 -0
- package/bin/eval-label-rules.mjs +342 -0
- package/bin/export-turns.py +68 -0
- package/bin/extract-user-paragraph.mjs +14 -0
- package/bin/extraction-composite-smoke.mjs +28 -0
- package/bin/extraction-latency.mjs +58 -0
- package/bin/extraction-user-smoke.mjs +28 -0
- package/bin/extraction_service.py +164 -0
- package/bin/facet-active-experiment.mjs +110 -0
- package/bin/facet-covid.mjs +8 -0
- package/bin/facet-curve.mjs +27 -0
- package/bin/facet-embedding-train.mjs +20 -0
- package/bin/facet-embedding-validate.mjs +83 -0
- package/bin/facet-eval-roleplay.mjs +42 -0
- package/bin/facet-eval.mjs +38 -0
- package/bin/facet-fold.mjs +31 -0
- package/bin/facet-load.mjs +16 -0
- package/bin/facet-mlp-scan.mjs +45 -0
- package/bin/facet-mvp.mjs +34 -0
- package/bin/facet-online-experiments.mjs +78 -0
- package/bin/facet-online-race.mjs +99 -0
- package/bin/facet-online-validate.mjs +51 -0
- package/bin/facet-panel.mjs +22 -0
- package/bin/facet-passage.mjs +41 -0
- package/bin/facet-random-validate.mjs +48 -0
- package/bin/facet-roleplay.mjs +33 -0
- package/bin/facet-scan.mjs +56 -0
- package/bin/facet-segment.mjs +34 -0
- package/bin/facet-train.mjs +19 -0
- package/bin/facet-validate.mjs +103 -0
- package/bin/facet-zeroshot.mjs +14 -0
- package/bin/install-templates.mjs +72 -0
- package/bin/memory-bench.mjs +53 -0
- package/bin/memory-build-summary.mjs +43 -0
- package/bin/memory-build-vector.mjs +31 -0
- package/bin/memory-build.mjs +41 -0
- package/bin/memory-calibration.mjs +70 -0
- package/bin/memory-closed-loop.mjs +119 -0
- package/bin/memory-dry-run.mjs +70 -0
- package/bin/memory-eval.mjs +104 -0
- package/bin/memory-event-alias-test.mjs +89 -0
- package/bin/memory-extraction-bench.mjs +58 -0
- package/bin/memory-load-gate-replay.mjs +137 -0
- package/bin/memory-loop-test.mjs +90 -0
- package/bin/memory-pre-turn-harness.mjs +63 -0
- package/bin/memory-query.mjs +39 -0
- package/bin/memory-replay-hints.mjs +84 -0
- package/bin/memory-scale.mjs +58 -0
- package/bin/memory-scenario-test.mjs +146 -0
- package/bin/memory-user-loop.mjs +95 -0
- package/bin/ner_bench_tiny_zh.py +59 -0
- package/bin/ner_bench_zh.py +60 -0
- package/bin/ner_conll_en.py +139 -0
- package/bin/ner_direct_en.py +123 -0
- package/bin/ner_direct_zh.py +100 -0
- package/bin/ner_pipeline.py +208 -0
- package/bin/noise-selfcheck.mjs +50 -0
- package/bin/pack-integrity.mjs +47 -0
- package/bin/panel-check.mjs +57 -0
- package/bin/plugin-integrity.mjs +74 -0
- package/bin/preview-live.mjs +13 -0
- package/bin/public-install-check.mjs +222 -0
- package/bin/relabel-apply.mjs +272 -0
- package/bin/relabel-dry-run.mjs +381 -0
- package/bin/relik_pipeline.py +39 -0
- package/bin/relik_service.py +52 -0
- package/bin/srl_pipeline.py +55 -0
- package/bin/srl_service.py +68 -0
- package/bin/summary-index.mjs +151 -0
- package/bin/summary-query.mjs +79 -0
- package/bin/vec-check.mjs +27 -0
- package/bin/zh_ner_error_audit.py +36 -0
- package/bin/zh_ner_slot_eval.py +38 -0
- package/client.js +760 -0
- package/cordis.patch.yml +378 -0
- package/docs/API-NOTES.md +1743 -0
- package/docs/ARCHITECTURE.md +355 -0
- package/docs/CACHE.md +253 -0
- package/docs/CONDUCT.md +30 -0
- package/docs/CREATOR-HANDOFF.md +102 -0
- package/docs/EXPERIMENT.md +136 -0
- package/docs/FACET-MVP-REPORT.md +473 -0
- package/docs/GUIDE-0.4.66.md +195 -0
- package/docs/GUIDE.md +127 -0
- package/docs/MECHANISM.md +243 -0
- package/docs/NEW-INSTANCE-HANDOFF.md +219 -0
- package/docs/README.md +37 -0
- package/docs/SESSION-LAYOUT.md +151 -0
- package/docs/history-map.md +249 -0
- package/docs/relabel-migration-wiring.md +366 -0
- package/guides/00-READ-FIRST.md +206 -0
- package/guides/01-INSTALL.md +207 -0
- package/guides/02-TABLES.md +225 -0
- package/guides/03-AUDIT.md +201 -0
- package/guides/04-REBUILD.md +306 -0
- package/guides/05-FIRST-RUN.md +191 -0
- package/guides/06-EMBEDDING.md +146 -0
- package/guides/07-PTC-AND-ASSETS.md +221 -0
- package/icon.svg +7 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/models/README.md +13 -0
- package/models/Xenova/bge-small-zh-v1.5/config.json +31 -0
- package/models/Xenova/bge-small-zh-v1.5/onnx/model_quantized.onnx +0 -0
- package/models/Xenova/bge-small-zh-v1.5/tokenizer.json +21278 -0
- package/models/Xenova/bge-small-zh-v1.5/tokenizer_config.json +15 -0
- package/package.json +139 -4
- package/src/asset-tool.mjs +237 -0
- package/src/assets.mjs +244 -0
- package/src/backfill.mjs +74 -0
- package/src/backup.mjs +80 -0
- package/src/deadend.mjs +95 -0
- package/src/deleted-store.mjs +35 -0
- package/src/engine.mjs +3831 -0
- package/src/facets/classifier.mjs +146 -0
- package/src/facets/embedding-service.mjs +53 -0
- package/src/facets/embedding-worker.mjs +27 -0
- package/src/facets/embedding.mjs +163 -0
- package/src/facets/extract.mjs +80 -0
- package/src/facets/fold-plan.mjs +84 -0
- package/src/facets/hybrid.mjs +29 -0
- package/src/facets/index.mjs +113 -0
- package/src/facets/linear-probe.mjs +43 -0
- package/src/facets/load.mjs +37 -0
- package/src/facets/mlp-probe.mjs +54 -0
- package/src/facets/mlp-service.mjs +53 -0
- package/src/facets/mlp-worker.mjs +26 -0
- package/src/facets/online-knn.mjs +58 -0
- package/src/facets/online-models.mjs +227 -0
- package/src/facets/panel-state.mjs +23 -0
- package/src/facets/panel.mjs +73 -0
- package/src/facets/random.mjs +26 -0
- package/src/facets/scanner.mjs +35 -0
- package/src/facets/schema.mjs +75 -0
- package/src/facets/seed-examples.mjs +58 -0
- package/src/facets/sensitivity.mjs +98 -0
- package/src/facets/service.mjs +53 -0
- package/src/facets/store.mjs +39 -0
- package/src/facets/worker.mjs +23 -0
- package/src/folds-store.mjs +109 -0
- package/src/groups.mjs +167 -0
- package/src/handoff-tool.mjs +51 -0
- package/src/handoff.mjs +127 -0
- package/src/index.mjs +649 -0
- package/src/inject-policy.mjs +17 -0
- package/src/learning.mjs +88 -0
- package/src/memory/alias-proposal.mjs +40 -0
- package/src/memory/bc-audit.mjs +245 -0
- package/src/memory/bc-index-updater.mjs +404 -0
- package/src/memory/bc-index.mjs +48 -0
- package/src/memory/bc-label.mjs +220 -0
- package/src/memory/bc-panel-state.mjs +107 -0
- package/src/memory/bc-pre-step.mjs +463 -0
- package/src/memory/bc-recall.mjs +470 -0
- package/src/memory/bc-shift.mjs +123 -0
- package/src/memory/bc-store.mjs +41 -0
- package/src/memory/bm25.mjs +112 -0
- package/src/memory/calibration.mjs +54 -0
- package/src/memory/chatter.mjs +29 -0
- package/src/memory/cmap-file.mjs +107 -0
- package/src/memory/concepts-file.mjs +41 -0
- package/src/memory/coref-slots.mjs +40 -0
- package/src/memory/event-alias-ledger.mjs +87 -0
- package/src/memory/event-log.mjs +95 -0
- package/src/memory/event-model.mjs +132 -0
- package/src/memory/event-segmenter.mjs +36 -0
- package/src/memory/event-summary-tree.mjs +122 -0
- package/src/memory/extraction-adapter.mjs +76 -0
- package/src/memory/extraction-composite.mjs +75 -0
- package/src/memory/extraction-core.mjs +76 -0
- package/src/memory/extraction-pre-step.mjs +161 -0
- package/src/memory/extraction-provider.mjs +300 -0
- package/src/memory/extraction-score.mjs +36 -0
- package/src/memory/facet-stabilizer.mjs +59 -0
- package/src/memory/history-map.mjs +139 -0
- package/src/memory/language-router.mjs +30 -0
- package/src/memory/load-gate.mjs +161 -0
- package/src/memory/mechanical-note.mjs +104 -0
- package/src/memory/onboarding.mjs +163 -0
- package/src/memory/persona.mjs +78 -0
- package/src/memory/planner.mjs +249 -0
- package/src/memory/pre-turn.mjs +80 -0
- package/src/memory/query-aliases.mjs +39 -0
- package/src/memory/recall-cue.mjs +43 -0
- package/src/memory/recall.mjs +65 -0
- package/src/memory/referent-candidates.mjs +55 -0
- package/src/memory/referent-ledger.mjs +86 -0
- package/src/memory/relabel-migrate.mjs +376 -0
- package/src/memory/relik-adapter.mjs +25 -0
- package/src/memory/special-keywords.mjs +60 -0
- package/src/memory/structured-adapters.mjs +75 -0
- package/src/memory/summary-index.mjs +335 -0
- package/src/memory/summary-tree.mjs +110 -0
- package/src/memory/term-extractor.mjs +44 -0
- package/src/memory/tokenize.mjs +9 -0
- package/src/memory/vector-index.mjs +54 -0
- package/src/memory/window-summary.mjs +133 -0
- package/src/mood.mjs +133 -0
- package/src/peer-tool.mjs +63 -0
- package/src/peer.mjs +144 -0
- package/src/policy.mjs +170 -0
- package/src/preflight.mjs +113 -0
- package/src/provenance.mjs +94 -0
- package/src/range.mjs +75 -0
- package/src/recall-exact-tool.mjs +267 -0
- package/src/recall-tool.mjs +521 -0
- package/src/recheck.mjs +99 -0
- package/src/save-start.mjs +29 -0
- package/src/session-guard.mjs +140 -0
- package/src/session-log-tool.mjs +322 -0
- package/src/session-log.mjs +606 -0
- package/src/skills.mjs +111 -0
- package/src/split-range.mjs +115 -0
- package/src/state.mjs +198 -0
- package/src/t0.mjs +149 -0
- package/src/tag-confirm.mjs +78 -0
- package/src/tag-detect.mjs +42 -0
- package/src/tag-maintenance.mjs +285 -0
- package/src/tag-reject.mjs +73 -0
- package/src/tags-store.mjs +94 -0
- package/src/tags.mjs +692 -0
- package/src/threads-backend.mjs +26 -0
- package/src/threads-core.mjs +1887 -0
- package/src/threads-store.mjs +207 -0
- package/src/threads-tool.mjs +522 -0
- package/src/threads.mjs +929 -0
- package/src/tool-preview.mjs +427 -0
- package/src/topic.mjs +63 -0
- package/src/training.mjs +75 -0
- package/src/turn-tag.mjs +62 -0
- package/src/user-turn.mjs +39 -0
- package/templates/cmap.md +11 -0
- package/templates/concepts.md +12 -0
- package/templates/habits.md +40 -0
- package/templates/keywords.md +6 -0
- package/templates/rebuild.md +134 -0
- package/templates/rules-safe.md +184 -0
- package/templates/rules.md +177 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extraction provider: default off, cache-backed first.
|
|
3
|
+
* Model-backed command mode is added in the next stage.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import crypto from 'node:crypto';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { spawn } from 'node:child_process';
|
|
9
|
+
import readline from 'node:readline';
|
|
10
|
+
import { attachStructuredExtraction } from './extraction-adapter.mjs';
|
|
11
|
+
import { validateEventSlots } from './event-model.mjs';
|
|
12
|
+
import { createCompositeProvider } from './extraction-composite.mjs';
|
|
13
|
+
|
|
14
|
+
export function resolveExtractionMode(env = process.env) {
|
|
15
|
+
const raw = String(env.DSH_EXTRACTION_MODE ?? 'off').toLowerCase();
|
|
16
|
+
if (raw === 'cache' || raw === 'model') return raw;
|
|
17
|
+
return 'off';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function splitArgs(value) {
|
|
21
|
+
const text = String(value ?? '').trim();
|
|
22
|
+
if (!text) return [];
|
|
23
|
+
if (text.startsWith('[')) {
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(text);
|
|
26
|
+
return Array.isArray(parsed) ? parsed.map(String) : [];
|
|
27
|
+
} catch {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const out = [];
|
|
32
|
+
let current = '';
|
|
33
|
+
let quote = null;
|
|
34
|
+
for (const char of text) {
|
|
35
|
+
if (quote) {
|
|
36
|
+
if (char === quote) quote = null;
|
|
37
|
+
else current += char;
|
|
38
|
+
} else if (char === '"' || char === "'") {
|
|
39
|
+
quote = char;
|
|
40
|
+
} else if (/\s/.test(char)) {
|
|
41
|
+
if (current) {
|
|
42
|
+
out.push(current);
|
|
43
|
+
current = '';
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
current += char;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (current) out.push(current);
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function keyFor(text) {
|
|
54
|
+
return crypto.createHash('sha256').update(String(text ?? ''), 'utf8').digest('hex');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readCache(cachePath) {
|
|
58
|
+
if (!cachePath || !fs.existsSync(cachePath)) return {};
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
|
|
61
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
62
|
+
} catch {
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function createCacheProvider({ cachePath = null, mode = 'cache' } = {}) {
|
|
68
|
+
const cache = mode === 'cache' ? readCache(cachePath) : {};
|
|
69
|
+
return {
|
|
70
|
+
mode,
|
|
71
|
+
async extract({ text } = {}) {
|
|
72
|
+
const started = Date.now();
|
|
73
|
+
if (mode === 'off') return { ok: false, source: 'off', reason: 'disabled', latencyMs: 0 };
|
|
74
|
+
const key = keyFor(text);
|
|
75
|
+
const hit = cache[key];
|
|
76
|
+
if (!hit) return { ok: false, source: 'cache', reason: 'cache-miss', latencyMs: Date.now() - started };
|
|
77
|
+
if (hit.slots) {
|
|
78
|
+
const checked = validateEventSlots(hit.slots);
|
|
79
|
+
if (!checked.ok) return { ok: false, source: 'cache', reason: 'invalid-slots', errors: checked.errors, latencyMs: Date.now() - started };
|
|
80
|
+
return { ok: true, source: 'cache', data: { ...hit, slots: checked.slots }, latencyMs: Date.now() - started };
|
|
81
|
+
}
|
|
82
|
+
return { ok: true, source: 'cache', data: hit, latencyMs: Date.now() - started };
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function runCommand({ command, args = [], payload, timeoutMs = 8000, env = process.env }) {
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
const child = spawn(command, args, { env, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
90
|
+
let stdout = '';
|
|
91
|
+
let stderr = '';
|
|
92
|
+
let settled = false;
|
|
93
|
+
const finish = (value) => {
|
|
94
|
+
if (settled) return;
|
|
95
|
+
settled = true;
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
resolve(value);
|
|
98
|
+
};
|
|
99
|
+
const timer = setTimeout(() => {
|
|
100
|
+
child.kill();
|
|
101
|
+
finish({ ok: false, reason: 'timeout' });
|
|
102
|
+
}, timeoutMs);
|
|
103
|
+
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
|
104
|
+
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
105
|
+
child.on('error', (error) => finish({ ok: false, reason: 'spawn-error', error: error.message }));
|
|
106
|
+
child.on('close', (code) => {
|
|
107
|
+
if (code !== 0) return finish({ ok: false, reason: 'exit-code', code, stderr: stderr.slice(-500) });
|
|
108
|
+
const lines = stdout.trim().split(/\r?\n/).filter(Boolean);
|
|
109
|
+
try {
|
|
110
|
+
return finish({ ok: true, value: JSON.parse(lines.at(-1)) });
|
|
111
|
+
} catch {
|
|
112
|
+
return finish({ ok: false, reason: 'invalid-json', stderr: stderr.slice(-500) });
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
child.stdin.end(JSON.stringify(payload));
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function createCommandProvider({ command, args = [], timeoutMs = 8000, env = process.env } = {}) {
|
|
120
|
+
const invoke = async (payload) => {
|
|
121
|
+
const started = Date.now();
|
|
122
|
+
if (!command) return { ok: false, source: 'command', reason: 'missing-command', latencyMs: 0 };
|
|
123
|
+
const run = await runCommand({ command, args, payload, timeoutMs, env });
|
|
124
|
+
if (!run.ok) return { ok: false, source: 'command', ...run, latencyMs: Date.now() - started };
|
|
125
|
+
const data = run.value ?? {};
|
|
126
|
+
if (data.ok === false) return { ok: false, source: 'command', reason: data.reason ?? 'service-error', latencyMs: Date.now() - started };
|
|
127
|
+
if (data.slots) {
|
|
128
|
+
const checked = validateEventSlots(data.slots);
|
|
129
|
+
if (!checked.ok) return { ok: false, source: 'command', reason: 'invalid-slots', errors: checked.errors, latencyMs: Date.now() - started };
|
|
130
|
+
return { ok: true, source: 'command', data: { ...data, slots: checked.slots }, latencyMs: Date.now() - started };
|
|
131
|
+
}
|
|
132
|
+
return { ok: true, source: 'command', data, latencyMs: Date.now() - started };
|
|
133
|
+
};
|
|
134
|
+
return {
|
|
135
|
+
mode: 'model',
|
|
136
|
+
extract: ({ text, ...rest } = {}) => invoke({ text, ...rest }),
|
|
137
|
+
healthCheck: () => invoke({ health: true }),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function createPersistentCommandProvider({ command, args = [], timeoutMs = 8000, env = process.env } = {}) {
|
|
142
|
+
let child = null;
|
|
143
|
+
let reader = null;
|
|
144
|
+
let started = null;
|
|
145
|
+
let seq = 0;
|
|
146
|
+
const pending = new Map();
|
|
147
|
+
|
|
148
|
+
const settle = (id, value) => {
|
|
149
|
+
const entry = pending.get(id);
|
|
150
|
+
if (!entry) return;
|
|
151
|
+
pending.delete(id);
|
|
152
|
+
clearTimeout(entry.timer);
|
|
153
|
+
entry.resolve(value);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const rejectAll = (reason) => {
|
|
157
|
+
for (const [id, entry] of pending) {
|
|
158
|
+
clearTimeout(entry.timer);
|
|
159
|
+
entry.resolve({ ok: false, reason });
|
|
160
|
+
pending.delete(id);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const start = () => {
|
|
165
|
+
if (started) return started;
|
|
166
|
+
started = new Promise((resolve, reject) => {
|
|
167
|
+
if (!command) return reject(new Error('missing-command'));
|
|
168
|
+
child = spawn(command, args, { env, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
169
|
+
child.once('error', (error) => {
|
|
170
|
+
reject(error);
|
|
171
|
+
rejectAll('spawn-error');
|
|
172
|
+
});
|
|
173
|
+
child.once('close', () => {
|
|
174
|
+
child = null;
|
|
175
|
+
reader?.close();
|
|
176
|
+
reader = null;
|
|
177
|
+
started = null;
|
|
178
|
+
rejectAll('process-exit');
|
|
179
|
+
});
|
|
180
|
+
reader = readline.createInterface({ input: child.stdout });
|
|
181
|
+
reader.on('line', (line) => {
|
|
182
|
+
try {
|
|
183
|
+
const data = JSON.parse(line);
|
|
184
|
+
if (data && data.id !== undefined) settle(data.id, { ok: true, data });
|
|
185
|
+
} catch {
|
|
186
|
+
/* ignore malformed lines; a timeout/crash will surface */
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
child.stderr.on('data', () => {});
|
|
190
|
+
resolve();
|
|
191
|
+
});
|
|
192
|
+
return started;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const request = async (payload) => {
|
|
196
|
+
if (!command) return { ok: false, reason: 'missing-command' };
|
|
197
|
+
try {
|
|
198
|
+
await start();
|
|
199
|
+
} catch (error) {
|
|
200
|
+
return { ok: false, reason: 'spawn-error', error: error.message };
|
|
201
|
+
}
|
|
202
|
+
if (!child) return { ok: false, reason: 'not-running' };
|
|
203
|
+
const id = ++seq;
|
|
204
|
+
const result = await new Promise((resolve) => {
|
|
205
|
+
const timer = setTimeout(() => {
|
|
206
|
+
pending.delete(id);
|
|
207
|
+
resolve({ ok: false, reason: 'timeout' });
|
|
208
|
+
}, timeoutMs);
|
|
209
|
+
pending.set(id, { resolve, timer });
|
|
210
|
+
child.stdin.write(`${JSON.stringify({ id, ...payload })}\n`);
|
|
211
|
+
});
|
|
212
|
+
return result;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
mode: 'model',
|
|
217
|
+
persistent: true,
|
|
218
|
+
async extract({ text, ...rest } = {}) {
|
|
219
|
+
const startedAt = Date.now();
|
|
220
|
+
const response = await request({ text, ...rest });
|
|
221
|
+
if (!response.ok) return { ok: false, source: 'persistent', reason: response.reason, latencyMs: Date.now() - startedAt };
|
|
222
|
+
const data = response.data ?? {};
|
|
223
|
+
if (data.ok === false) return { ok: false, source: 'persistent', reason: data.reason ?? 'service-error', latencyMs: Date.now() - startedAt };
|
|
224
|
+
if (data.slots) {
|
|
225
|
+
const checked = validateEventSlots(data.slots);
|
|
226
|
+
if (!checked.ok) return { ok: false, source: 'persistent', reason: 'invalid-slots', errors: checked.errors, latencyMs: Date.now() - startedAt };
|
|
227
|
+
return { ok: true, source: 'persistent', data: { ...data, slots: checked.slots }, latencyMs: Date.now() - startedAt };
|
|
228
|
+
}
|
|
229
|
+
return { ok: true, source: 'persistent', data, latencyMs: Date.now() - startedAt };
|
|
230
|
+
},
|
|
231
|
+
healthCheck: () => request({ health: true }),
|
|
232
|
+
close() {
|
|
233
|
+
if (child) child.kill();
|
|
234
|
+
child = null;
|
|
235
|
+
reader?.close();
|
|
236
|
+
reader = null;
|
|
237
|
+
started = null;
|
|
238
|
+
rejectAll('closed');
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function createExtractionProviderFromEnv(env = process.env) {
|
|
244
|
+
const mode = resolveExtractionMode(env);
|
|
245
|
+
if (mode === 'off') return createCacheProvider({ mode: 'off' });
|
|
246
|
+
if (mode === 'cache') return createCacheProvider({ cachePath: env.DSH_EXTRACTION_CACHE ?? null, mode: 'cache' });
|
|
247
|
+
if (env.DSH_EXTRACTION_CONFIG) {
|
|
248
|
+
const configPath = path.resolve(env.DSH_EXTRACTION_CONFIG);
|
|
249
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
250
|
+
const persistent = config.persistent !== false && env.DSH_EXTRACTION_PERSISTENT !== '0';
|
|
251
|
+
const factory = persistent ? createPersistentCommandProvider : createCommandProvider;
|
|
252
|
+
const service = (spec = {}) => factory({
|
|
253
|
+
command: spec.command ?? null,
|
|
254
|
+
args: Array.isArray(spec.args) ? spec.args.map(String) : [],
|
|
255
|
+
timeoutMs: Number(spec.timeoutMs ?? env.DSH_EXTRACTION_TIMEOUT_MS ?? 8000),
|
|
256
|
+
env: { ...env, ...(spec.env ?? {}) },
|
|
257
|
+
});
|
|
258
|
+
const ner = service(config.services?.ner);
|
|
259
|
+
return createCompositeProvider({
|
|
260
|
+
ner,
|
|
261
|
+
coref: config.services?.coref ? service(config.services.coref) : null,
|
|
262
|
+
srl: config.services?.srl ? service(config.services.srl) : null,
|
|
263
|
+
el: config.services?.el ? service(config.services.el) : null,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
const factory = env.DSH_EXTRACTION_PERSISTENT === '1' ? createPersistentCommandProvider : createCommandProvider;
|
|
267
|
+
return factory({
|
|
268
|
+
command: env.DSH_EXTRACTION_COMMAND ?? null,
|
|
269
|
+
args: splitArgs(env.DSH_EXTRACTION_ARGS),
|
|
270
|
+
timeoutMs: Number(env.DSH_EXTRACTION_TIMEOUT_MS ?? 8000),
|
|
271
|
+
env,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export async function stepWithProvider(controller, provider, {
|
|
276
|
+
text,
|
|
277
|
+
role = 'user',
|
|
278
|
+
facets = {},
|
|
279
|
+
confidence = 1,
|
|
280
|
+
nowTurn = 0,
|
|
281
|
+
} = {}) {
|
|
282
|
+
const extraction = await provider.extract({ text });
|
|
283
|
+
if (!extraction.ok) {
|
|
284
|
+
const gate = controller.step({ text, role, facets, confidence, eventSlots: null, nowTurn });
|
|
285
|
+
return { ok: false, extraction, gate };
|
|
286
|
+
}
|
|
287
|
+
const data = extraction.data ?? {};
|
|
288
|
+
const result = attachStructuredExtraction(controller, {
|
|
289
|
+
text,
|
|
290
|
+
role,
|
|
291
|
+
facets,
|
|
292
|
+
confidence,
|
|
293
|
+
nowTurn,
|
|
294
|
+
slots: data.slots ?? null,
|
|
295
|
+
srl: data.srl ?? null,
|
|
296
|
+
corefClusters: data.corefClusters ?? [],
|
|
297
|
+
entityLinks: data.entityLinks ?? [],
|
|
298
|
+
});
|
|
299
|
+
return { ok: result.ok, extraction, ...result };
|
|
300
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity/slot scoring for extraction benchmarks.
|
|
3
|
+
* Exact span text + label match; no fuzzy matching.
|
|
4
|
+
*/
|
|
5
|
+
export function scoreEntities(predicted = [], expected = []) {
|
|
6
|
+
const key = (entity) => `${entity.label.toUpperCase()}\u0000${entity.text}`;
|
|
7
|
+
const predictedKeys = new Set(predicted.map(key));
|
|
8
|
+
const expectedKeys = new Set(expected.map(key));
|
|
9
|
+
let tp = 0;
|
|
10
|
+
for (const item of predictedKeys) if (expectedKeys.has(item)) tp += 1;
|
|
11
|
+
const fp = predictedKeys.size - tp;
|
|
12
|
+
const fn = expectedKeys.size - tp;
|
|
13
|
+
const precision = predictedKeys.size === 0 ? 0 : tp / predictedKeys.size;
|
|
14
|
+
const recall = expectedKeys.size === 0 ? 0 : tp / expectedKeys.size;
|
|
15
|
+
const f1 = precision + recall === 0 ? 0 : (2 * precision * recall) / (precision + recall);
|
|
16
|
+
return {
|
|
17
|
+
tp,
|
|
18
|
+
fp,
|
|
19
|
+
fn,
|
|
20
|
+
precision: +precision.toFixed(4),
|
|
21
|
+
recall: +recall.toFixed(4),
|
|
22
|
+
f1: +f1.toFixed(4),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function aggregateScores(scores) {
|
|
27
|
+
const total = scores.reduce((acc, score) => ({
|
|
28
|
+
tp: acc.tp + score.tp,
|
|
29
|
+
fp: acc.fp + score.fp,
|
|
30
|
+
fn: acc.fn + score.fn,
|
|
31
|
+
}), { tp: 0, fp: 0, fn: 0 });
|
|
32
|
+
const precision = total.tp + total.fp === 0 ? 0 : total.tp / (total.tp + total.fp);
|
|
33
|
+
const recall = total.tp + total.fn === 0 ? 0 : total.tp / (total.tp + total.fn);
|
|
34
|
+
const f1 = precision + recall === 0 ? 0 : (2 * precision * recall) / (precision + recall);
|
|
35
|
+
return { ...total, precision: +precision.toFixed(4), recall: +recall.toFixed(4), f1: +f1.toFixed(4) };
|
|
36
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stabilize a noisy local facet head with lexical aliases and retrieval votes.
|
|
3
|
+
* This is a deterministic system layer, not an LLM decision.
|
|
4
|
+
*/
|
|
5
|
+
import { DEFAULT_ALIASES } from '../facets/schema.mjs';
|
|
6
|
+
|
|
7
|
+
const START_RUN = /开始(?:跑团|一局|团|游戏|新的)|开个新团|新团|开新团|来一局|开团|开一局|进入.*(?:跑团|灯室|游戏)|跑团吧|TRPG/iu;
|
|
8
|
+
|
|
9
|
+
export function lexicalFacet(text, { startOnly = false } = {}) {
|
|
10
|
+
const keys = Object.keys(DEFAULT_ALIASES).sort((a, b) => b.length - a.length);
|
|
11
|
+
const alias = keys.find((key) => text.includes(key));
|
|
12
|
+
const start = START_RUN.test(text);
|
|
13
|
+
if (!start && startOnly) return null;
|
|
14
|
+
if (start) return { activity: '跑团', source: 'start-marker' };
|
|
15
|
+
if (alias !== undefined) return { ...DEFAULT_ALIASES[alias], source: `alias:${alias}` };
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function voteFacets(records, hits) {
|
|
20
|
+
const activity = {};
|
|
21
|
+
const topic = {};
|
|
22
|
+
for (const hit of hits) {
|
|
23
|
+
const record = records.find((item) => item.turn === hit.turn);
|
|
24
|
+
if (record === undefined) continue;
|
|
25
|
+
activity[record.facets.activity] = (activity[record.facets.activity] ?? 0) + 1;
|
|
26
|
+
topic[record.facets.topic] = (topic[record.facets.topic] ?? 0) + 1;
|
|
27
|
+
}
|
|
28
|
+
const top = (counts) => Object.entries(counts).sort((a, b) => b[1] - a[1])[0] ?? null;
|
|
29
|
+
return { activity: top(activity), topic: top(topic) };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function stabilizeFacets({ raw, text, records, keyword = [], semantic = [], summary = [] }) {
|
|
33
|
+
const facets = { ...raw.facets };
|
|
34
|
+
const confidence = { ...raw.confidence };
|
|
35
|
+
const evidence = voteFacets(records, [...keyword, ...semantic]);
|
|
36
|
+
const summaryEvidence = voteFacets(records, summary);
|
|
37
|
+
const summaryAgrees = evidence.activity !== null
|
|
38
|
+
&& summaryEvidence.activity !== null
|
|
39
|
+
&& evidence.activity[0] === summaryEvidence.activity[0];
|
|
40
|
+
if (summaryAgrees) evidence.activity[1] += 1;
|
|
41
|
+
const lexical = lexicalFacet(text);
|
|
42
|
+
|
|
43
|
+
if (lexical !== null) {
|
|
44
|
+
for (const [slot, value] of Object.entries(lexical)) {
|
|
45
|
+
facets[slot] = value;
|
|
46
|
+
confidence[slot] = 0.9;
|
|
47
|
+
}
|
|
48
|
+
return { facets, confidence, evidence, summaryEvidence, summaryAgrees, reason: lexical.source };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if ((confidence.activity ?? 0) < 0.7 && evidence.activity !== null && evidence.activity[1] >= 3) {
|
|
52
|
+
facets.activity = evidence.activity[0];
|
|
53
|
+
confidence.activity = 0.65;
|
|
54
|
+
}
|
|
55
|
+
if ((confidence.topic ?? 0) < 0.6 && evidence.topic !== null && evidence.topic[1] >= 3) {
|
|
56
|
+
facets.topic = evidence.topic[0];
|
|
57
|
+
}
|
|
58
|
+
return { facets, confidence, evidence, summaryEvidence, summaryAgrees, reason: 'retrieval' };
|
|
59
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active history map: the middle-layer summary that must live in context.
|
|
3
|
+
*
|
|
4
|
+
* This renderer is deliberately deterministic: it uses the archive summary
|
|
5
|
+
* index plus the newest raw summary events, and caps by characters/tokens.
|
|
6
|
+
* A later LLM summarizer can replace the body; the carrier contract stays.
|
|
7
|
+
*/
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_MAX_TOKENS = 4000;
|
|
11
|
+
const CHARS_PER_TOKEN = 4;
|
|
12
|
+
const DETAIL_CHARS = 6000;
|
|
13
|
+
|
|
14
|
+
function textOfSummary(event) {
|
|
15
|
+
const summary = event?.data?.summary;
|
|
16
|
+
if (Array.isArray(summary)) {
|
|
17
|
+
return summary.map((block) => (block && typeof block === 'object' && typeof block.text === 'string' ? block.text : '')).join('');
|
|
18
|
+
}
|
|
19
|
+
return typeof summary === 'string' ? summary : '';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function seqOf(event) {
|
|
23
|
+
const seq = Number(event?.seq);
|
|
24
|
+
return Number.isInteger(seq) ? seq : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function rangeOf(event) {
|
|
28
|
+
const start = Number(event?.data?.shadowedRange?.start);
|
|
29
|
+
const end = Number(event?.data?.shadowedRange?.end);
|
|
30
|
+
if (!Number.isInteger(start) || !Number.isInteger(end)) return null;
|
|
31
|
+
return { from: Math.min(start, end), to: Math.max(start, end) };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function mergeRanges(records) {
|
|
35
|
+
const ranges = records
|
|
36
|
+
.map((record) => ({ from: Number(record.fromSeq), to: Number(record.toSeq) }))
|
|
37
|
+
.filter((range) => Number.isInteger(range.from) && Number.isInteger(range.to))
|
|
38
|
+
.sort((a, b) => a.from - b.from || a.to - b.to);
|
|
39
|
+
const merged = [];
|
|
40
|
+
for (const range of ranges) {
|
|
41
|
+
const last = merged.at(-1);
|
|
42
|
+
if (last && range.from <= last.to + 1) last.to = Math.max(last.to, range.to);
|
|
43
|
+
else merged.push({ ...range });
|
|
44
|
+
}
|
|
45
|
+
return merged;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function excerpt(text, maxChars = DETAIL_CHARS) {
|
|
49
|
+
const clean = String(text ?? '').replace(/\r/g, '').trim().replace(/^## /gm, '### ');
|
|
50
|
+
if (clean.length <= maxChars) return clean;
|
|
51
|
+
return clean.slice(0, maxChars).replace(/\s+\S*$/, '') + '…';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {{index?: object, events?: object[], maxTokens?: number, generatedAt?: string}} input
|
|
56
|
+
*/
|
|
57
|
+
export function buildHistoryMap({ index = null, events = [], maxTokens = DEFAULT_MAX_TOKENS, generatedAt = null } = {}) {
|
|
58
|
+
const capChars = Math.max(1000, Math.floor(maxTokens * CHARS_PER_TOKEN));
|
|
59
|
+
const records = Array.isArray(index?.records) ? index.records : [];
|
|
60
|
+
const ranges = mergeRanges(records);
|
|
61
|
+
const rawSummaries = (Array.isArray(events) ? events : [])
|
|
62
|
+
.filter((event) => event?.type === 'compaction/summary')
|
|
63
|
+
.map((event) => ({ seq: seqOf(event), range: rangeOf(event), text: textOfSummary(event), event }))
|
|
64
|
+
.filter((item) => item.range && item.text.trim().length > 0)
|
|
65
|
+
.sort((a, b) => (a.seq ?? 0) - (b.seq ?? 0));
|
|
66
|
+
const indexedMax = Number(index?.maxSummarySeq);
|
|
67
|
+
const latestRaw = rawSummaries.at(-1) ?? null;
|
|
68
|
+
const extra = rawSummaries.filter((item) => !Number.isInteger(indexedMax) || (item.seq ?? -1) > indexedMax);
|
|
69
|
+
|
|
70
|
+
const sections = [];
|
|
71
|
+
const push = (heading, body) => sections.push({ heading, body: String(body ?? '').trim() });
|
|
72
|
+
push(
|
|
73
|
+
'位置',
|
|
74
|
+
[
|
|
75
|
+
`session=${index?.sessionId ?? ''}`,
|
|
76
|
+
`coverage=${index?.coverage?.minSeq ?? '?'}..${index?.coverage?.maxSeq ?? '?'}`,
|
|
77
|
+
`index.maxSummarySeq=${indexedMax || '?'}`,
|
|
78
|
+
`latestRawSummary=${latestRaw?.seq ?? '?'}`,
|
|
79
|
+
`depth=1`,
|
|
80
|
+
`generatedAt=${generatedAt ?? index?.generatedAt ?? 'unknown'}`,
|
|
81
|
+
].join(' · '),
|
|
82
|
+
);
|
|
83
|
+
const recentRanges = ranges.slice(-3).reverse();
|
|
84
|
+
for (const range of recentRanges) {
|
|
85
|
+
const candidates = records
|
|
86
|
+
.filter((record) => Number(record.toSeq) >= range.from && Number(record.fromSeq) <= range.to)
|
|
87
|
+
.sort((a, b) => Number(b.toSeq) - Number(a.toSeq)
|
|
88
|
+
|| String(b.text ?? '').length - String(a.text ?? '').length);
|
|
89
|
+
const picked = candidates.slice(0, 2);
|
|
90
|
+
if (picked.length > 0) {
|
|
91
|
+
const perRecord = Math.max(1500, Math.floor(DETAIL_CHARS / picked.length));
|
|
92
|
+
push(
|
|
93
|
+
`最近区间 ${range.from}-${range.to}`,
|
|
94
|
+
picked.map((record) => excerpt(record.text, perRecord)).join('\n\n---\n\n'),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (extra.length > 0) {
|
|
99
|
+
push(
|
|
100
|
+
'上次索引之后的 fold',
|
|
101
|
+
extra.slice(-2).map((item) => `- seq ${item.range.from}-${item.range.to}: ${excerpt(item.text, 1200)}`).join('\n'),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
push(
|
|
105
|
+
'长期结构',
|
|
106
|
+
ranges.length === 0
|
|
107
|
+
? '(no archive ranges)'
|
|
108
|
+
: ranges.map((range) => `- seq ${range.from}-${range.to}`).join('\n'),
|
|
109
|
+
);
|
|
110
|
+
push('取原文', '细节以 raw session_log 为准;本图只指路,不是事实本身。');
|
|
111
|
+
|
|
112
|
+
// Cap by sections, dropping detail from the middle first; keep position/source.
|
|
113
|
+
let text = '';
|
|
114
|
+
const rendered = [];
|
|
115
|
+
for (const section of sections) {
|
|
116
|
+
const block = `## ${section.heading}\n${section.body}`;
|
|
117
|
+
if (text.length + block.length + 2 > capChars && rendered.length > 0) {
|
|
118
|
+
if (section.heading === '取原文') {
|
|
119
|
+
text += `\n\n## ${section.heading}\n${section.body}`;
|
|
120
|
+
}
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
rendered.push(block);
|
|
124
|
+
text += (text ? '\n\n' : '') + block;
|
|
125
|
+
}
|
|
126
|
+
if (text.length > capChars) text = text.slice(0, capChars).replace(/\s+\S*$/, '') + '…';
|
|
127
|
+
const hash = createHash('sha256').update(text, 'utf8').digest('hex').slice(0, 16);
|
|
128
|
+
return {
|
|
129
|
+
text: `[continuity:history-map] ${text}`,
|
|
130
|
+
hash,
|
|
131
|
+
maxTokens,
|
|
132
|
+
chars: text.length,
|
|
133
|
+
approxTokens: Math.ceil(text.length / CHARS_PER_TOKEN),
|
|
134
|
+
maxSummarySeq: Number.isInteger(indexedMax) ? indexedMax : null,
|
|
135
|
+
coveredThroughSeq: Number.isInteger(Number(index?.coveredThroughSeq)) ? Number(index.coveredThroughSeq) : null,
|
|
136
|
+
latestRawSummarySeq: latestRaw?.seq ?? null,
|
|
137
|
+
extraFolds: extra.length,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language router for zh / en / mixed messages.
|
|
3
|
+
* Not a language detector replacement; deterministic and explainable.
|
|
4
|
+
*/
|
|
5
|
+
export function routeLanguage(text) {
|
|
6
|
+
const source = String(text ?? '');
|
|
7
|
+
const cjk = (source.match(/[\u4e00-\u9fff]/g) ?? []).length;
|
|
8
|
+
const latin = (source.match(/[A-Za-z]/g) ?? []).length;
|
|
9
|
+
const total = cjk + latin;
|
|
10
|
+
if (total === 0) return { language: 'unknown', confidence: 0, cjk, latin };
|
|
11
|
+
const zhRatio = cjk / total;
|
|
12
|
+
const enRatio = latin / total;
|
|
13
|
+
let language = 'mixed';
|
|
14
|
+
if (cjk > 0 && latin > 0 && Math.min(zhRatio, enRatio) >= 0.1) language = 'mixed';
|
|
15
|
+
else if (zhRatio >= 0.85) language = 'zh';
|
|
16
|
+
else if (enRatio >= 0.85) language = 'en';
|
|
17
|
+
return {
|
|
18
|
+
language,
|
|
19
|
+
confidence: +Math.max(zhRatio, enRatio).toFixed(2),
|
|
20
|
+
zhRatio: +zhRatio.toFixed(2),
|
|
21
|
+
enRatio: +enRatio.toFixed(2),
|
|
22
|
+
cjk,
|
|
23
|
+
latin,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function languageTag(text) {
|
|
28
|
+
const route = routeLanguage(text);
|
|
29
|
+
return `[${route.language}] ${text}`;
|
|
30
|
+
}
|