@polycode-projects/the-mechanical-code-talker 4.0.0 → 4.1.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/README.md +178 -9
- package/corpus/reference/index.json.gz +0 -0
- package/corpus/reference/manifest.json +8 -8
- package/corpus/reference/shards/ref-00.jsonl.gz +0 -0
- package/corpus/sprites/src/sprite-facts.jsonl +401 -0
- package/corpus/worlds/index.json.gz +0 -0
- package/corpus/worlds/manifest.json +49 -9
- package/corpus/worlds/shards/greyvale-museum.jsonl.gz +0 -0
- package/corpus/worlds/shards/lantern-cottage.jsonl.gz +0 -0
- package/corpus/worlds/shards/mud-hollow.jsonl.gz +0 -0
- package/corpus/worlds/shards/mud-warren.jsonl.gz +0 -0
- package/corpus/worlds/shards/spider-fly.jsonl.gz +0 -0
- package/corpus/worlds/src/greyvale-museum.jsonl +136 -0
- package/corpus/worlds/src/lantern-cottage.jsonl +60 -0
- package/corpus/worlds/src/mud-hollow.jsonl +82 -0
- package/corpus/worlds/src/mud-warren.jsonl +124 -0
- package/corpus/worlds/src/spider-fly.jsonl +1 -1
- package/data/sprites/animal-icon.toml +11 -5
- package/data/sprites/book-icon.toml +9 -5
- package/data/sprites/cabinet-icon.toml +10 -5
- package/data/sprites/cellar-icon.toml +16 -6
- package/data/sprites/container-icon.toml +7 -3
- package/data/sprites/desk-icon.toml +8 -5
- package/data/sprites/dog-icon.toml +8 -5
- package/data/sprites/dog-with-colour-icon.toml +13 -12
- package/data/sprites/drawing-room-icon.toml +14 -5
- package/data/sprites/egg-icon.toml +6 -3
- package/data/sprites/fly-icon.toml +10 -5
- package/data/sprites/furniture-icon.toml +5 -3
- package/data/sprites/garden-icon.toml +10 -3
- package/data/sprites/key-icon.toml +3 -1
- package/data/sprites/kitchen-icon.toml +15 -6
- package/data/sprites/lamp-icon.toml +9 -4
- package/data/sprites/letter-icon.toml +7 -4
- package/data/sprites/library-icon.toml +14 -3
- package/data/sprites/pan-icon.toml +7 -3
- package/data/sprites/person-icon.toml +6 -2
- package/data/sprites/poodle-icon.toml +1 -1
- package/data/sprites/portable-icon.toml +6 -4
- package/data/sprites/portrait-icon.toml +7 -4
- package/data/sprites/room-icon.toml +11 -2
- package/data/sprites/spider-icon.toml +9 -3
- package/data/sprites/study-icon.toml +10 -2
- package/package.json +5 -4
- package/src/adapters/memory/core.mjs +20 -0
- package/src/domain/ask-vocab.mjs +71 -0
- package/src/domain/ask.mjs +168 -0
- package/src/domain/game-config.mjs +11 -0
- package/src/domain/grammar/ace.mjs +40 -4
- package/src/domain/grammar/lexicon-core.json +2 -1
- package/src/domain/grammar/lexicon.mjs +18 -0
- package/src/domain/mud-facts.mjs +15 -0
- package/src/domain/reference-pack.mjs +31 -7
- package/src/domain/router/drive.mjs +35 -9
- package/src/domain/router/registry.mjs +24 -4
- package/src/domain/router/resolver.mjs +102 -40
- package/src/domain/scene-compose.mjs +117 -0
- package/src/domain/spider-fly-world.mjs +37 -1
- package/src/domain/sprite-facts.mjs +0 -0
- package/src/domain/sprite-request.mjs +156 -0
- package/src/domain/sprite-templates.mjs +169 -20
- package/src/services/adventure-editor.mjs +8 -14
- package/src/services/adventure-viz.mjs +209 -157
- package/src/services/adventure.mjs +526 -391
- package/src/services/chat-page-viz.mjs +69 -25
- package/src/services/chat.mjs +200 -51
- package/src/services/code-explorer-viz.mjs +102 -62
- package/src/services/extract-facts.mjs +4 -7
- package/src/services/ingest-viz.mjs +68 -82
- package/src/services/ledger-viz.mjs +136 -67
- package/src/services/memory-panel-viz.mjs +62 -0
- package/src/services/mud-editor.mjs +10 -15
- package/src/services/mud-turn.mjs +6 -6
- package/src/services/mud-viz.mjs +1016 -208
- package/src/services/p2p-room.mjs +90 -23
- package/src/services/plan-pddl.mjs +3 -1
- package/src/services/plan-viz.mjs +123 -64
- package/src/services/research-viz.mjs +160 -108
- package/src/services/spider-fly-turn.mjs +15 -23
- package/src/services/spider-fly-viz.mjs +146 -161
- package/src/services/spider-fly.mjs +69 -11
- package/src/services/sprite-catalog-viz.mjs +414 -240
- package/src/services/viz-boot.mjs +71 -0
- package/src/services/viz-room-graph.mjs +203 -0
- package/src/services/viz-theme.mjs +90 -1
- package/src/services/viz-ticker.mjs +22 -0
- package/src/surfaces/web/adventure-browser-entry.mjs +49 -33
- package/src/surfaces/web/chat-browser-entry.mjs +30 -105
- package/src/surfaces/web/code-explorer-browser-entry.mjs +168 -24
- package/src/surfaces/web/ingest-browser-entry.mjs +3 -13
- package/src/surfaces/web/ledger-browser-entry.mjs +7 -47
- package/src/surfaces/web/memory-ask-browser.bundle.js +127 -124
- package/src/surfaces/web/memory-stats.mjs +11 -0
- package/src/surfaces/web/mud-browser-entry.mjs +71 -29
- package/src/surfaces/web/plan-browser-entry.mjs +22 -40
- package/src/surfaces/web/research-browser-entry.mjs +26 -41
- package/src/surfaces/web/spider-fly-browser-entry.mjs +45 -28
- package/src/surfaces/web/sprites-browser-entry.mjs +14 -27
- package/src/surfaces/web/turn-session.mjs +120 -0
- package/src/tools/definitions.mjs +30 -0
- package/src/tools/handlers/index.mjs +6 -3
- package/src/tools/handlers/kit.mjs +19 -2
- package/src/tools/handlers/tmct-ask.mjs +11 -6
- package/src/tools/handlers/tmct-ingest.mjs +5 -1
- package/src/tools/handlers/tmct-related.mjs +4 -4
- package/src/tools/handlers/tmct-sprite.mjs +147 -0
- package/src/tools/memory-fallthrough.mjs +9 -2
- package/src/tools/server.mjs +25 -1
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
// (built by scripts/build-chat-bundle.mjs).
|
|
3
3
|
//
|
|
4
4
|
// Unlike memory-ask-browser-entry.mjs (factAnswer/factReadBack only), this
|
|
5
|
-
// exposes the FULL turn engine: createChatSession wraps
|
|
6
|
-
//
|
|
7
|
-
// src/services/chat-session.mjs's
|
|
8
|
-
// filesystem side effect (no transcript
|
|
9
|
-
// Memory is an in-memory Backend-B handle,
|
|
10
|
-
// built seed payload (scripts/build-chat-seed.mjs),
|
|
11
|
-
// proof chains and the honest miss all run
|
|
5
|
+
// exposes the FULL turn engine: createChatSession wraps the shared
|
|
6
|
+
// createTurnSession (turn-session.mjs) around chat.mjs's runTurn — the
|
|
7
|
+
// focus/last/planState/researchState threading src/services/chat-session.mjs's
|
|
8
|
+
// createSession.turn does, minus every filesystem side effect (no transcript
|
|
9
|
+
// log, no sidecar, no graph upsert). Memory is an in-memory Backend-B handle,
|
|
10
|
+
// optionally pre-loaded with a built seed payload (scripts/build-chat-seed.mjs),
|
|
11
|
+
// so teach turns, recall, proof chains and the honest miss all run
|
|
12
|
+
// client-side with zero I/O.
|
|
12
13
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
14
|
+
// Two browser traps this file still owns, beyond what createTurnSession
|
|
15
|
+
// already covers (passing `env: {}` explicitly, since a browser has no
|
|
16
|
+
// `process` global; the crash-resistant catch fallback):
|
|
16
17
|
// - the uuid adapter needs node:crypto — the session id comes from the
|
|
17
18
|
// Web Crypto API instead, with a Date.now fallback for contexts
|
|
18
19
|
// without it;
|
|
@@ -25,15 +26,15 @@
|
|
|
25
26
|
// adapter — its own setDigestStructures is a documented no-op there
|
|
26
27
|
// (see that module's header), so the call is harmless either way, never
|
|
27
28
|
// a load error and never a behavior change on the Node side.
|
|
28
|
-
import {
|
|
29
|
-
import { createInMemoryStore, normFactTerm, loadMemory, readFactRows } from "../../adapters/memory/core.mjs";
|
|
30
|
-
import { serializeFactsJsonl } from "../../adapters/memory/export-jsonl.mjs";
|
|
29
|
+
import { vocabExampleHint } from "../../services/chat.mjs";
|
|
30
|
+
import { createInMemoryStore, normFactTerm, loadMemory, readFactRows, applySeedPayload } from "../../adapters/memory/core.mjs";
|
|
31
31
|
import { splitSentencesPreservingPaths } from "../../services/sentences.mjs";
|
|
32
|
-
import { provenanceTagToSource } from "../../domain/memory/trust.mjs";
|
|
33
32
|
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
34
33
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
35
34
|
import { registerWinkModel } from "../../adapters/wink-model.mjs";
|
|
36
35
|
import { setDigestStructures } from "../../adapters/corpus/digest-bank.mjs";
|
|
36
|
+
import { createTurnSession } from "./turn-session.mjs";
|
|
37
|
+
import { memoryStats, exportFactsJsonl } from "./memory-stats.mjs";
|
|
37
38
|
// The reference-pack provider seam: the page registers a fetch-backed
|
|
38
39
|
// provider over public/reference-pack/ so the engine's pack lookups work
|
|
39
40
|
// where the gzipped fs layout cannot (the module's own fs loader degrades to
|
|
@@ -76,10 +77,7 @@ import { openPersistedStore } from "./idb-persist.mjs";
|
|
|
76
77
|
export function createChatSession({ seedPayload = null, vocabSeeded = false, liveReference = false, onLiveLookup = null, synthesisBudget = 12, digestStructures = null } = {}) {
|
|
77
78
|
setDigestStructures(digestStructures || []);
|
|
78
79
|
const memoryDir = createInMemoryStore();
|
|
79
|
-
|
|
80
|
-
// and objectProperties only) still carries the classes/prefixes scaffolding
|
|
81
|
-
// the write path recounts — teach turns must work on any seed.
|
|
82
|
-
if (seedPayload) memoryDir.payload = { ...memoryDir.payload, ...seedPayload };
|
|
80
|
+
applySeedPayload(memoryDir, seedPayload);
|
|
83
81
|
|
|
84
82
|
// A known-empty code graph: code-structure questions get the same honest
|
|
85
83
|
// no-code-graph answer an un-pointed CLI session gives, never a crash.
|
|
@@ -88,10 +86,6 @@ export function createChatSession({ seedPayload = null, vocabSeeded = false, liv
|
|
|
88
86
|
const vocabHint = vocabExampleHint(vocabSeeded);
|
|
89
87
|
const sessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
90
88
|
|
|
91
|
-
let focus = null;
|
|
92
|
-
let last = null;
|
|
93
|
-
let planState = null;
|
|
94
|
-
let researchState = null;
|
|
95
89
|
// Four-state, like the CLI: false (off), true (rescue on a miss),
|
|
96
90
|
// "supplement" (also append a cited read-out under every grounded vocabulary
|
|
97
91
|
// answer), or "always" (widen that to every grounded answer). The two string
|
|
@@ -102,6 +96,19 @@ export function createChatSession({ seedPayload = null, vocabSeeded = false, liv
|
|
|
102
96
|
// page's slider sets it; 0 stores article facts without any entailed rows.
|
|
103
97
|
let synthesisBudgetOn = Number.isFinite(synthesisBudget) ? synthesisBudget : 12;
|
|
104
98
|
|
|
99
|
+
const session = createTurnSession({
|
|
100
|
+
memoryDir, graph, lexicon, sessionId, vocabHint,
|
|
101
|
+
buildExtraOptions: () => ({
|
|
102
|
+
liveReference: liveReferenceOn, onLiveLookup,
|
|
103
|
+
uiContext: "browser", synthesisBudget: synthesisBudgetOn,
|
|
104
|
+
}),
|
|
105
|
+
// `result.liveReference` mirrors a `/wiki on|off|supplement|always`
|
|
106
|
+
// command back into this session's own toggle state.
|
|
107
|
+
captureExtraState: (result) => {
|
|
108
|
+
if (typeof result.liveReference === "boolean" || result.liveReference === "supplement" || result.liveReference === "always") liveReferenceOn = result.liveReference;
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
105
112
|
return {
|
|
106
113
|
memoryDir,
|
|
107
114
|
sessionId,
|
|
@@ -113,92 +120,10 @@ export function createChatSession({ seedPayload = null, vocabSeeded = false, liv
|
|
|
113
120
|
/** The page's slider seam: set the auto-synthesis budget for every later
|
|
114
121
|
* learn-on-miss load. Clamped to a non-negative integer; 0 disables it. */
|
|
115
122
|
setSynthesisBudget(n) { synthesisBudgetOn = Number.isFinite(n) && n > 0 ? Math.floor(n) : 0; },
|
|
116
|
-
|
|
117
|
-
/** One dispatched turn. A throwing runTurn must never kill the session —
|
|
118
|
-
* the page has no other chance to show this turn's answer. */
|
|
119
|
-
async turn(line) {
|
|
120
|
-
let result;
|
|
121
|
-
try {
|
|
122
|
-
result = await runTurn(line, {
|
|
123
|
-
config: null, source: null, graph, focus, last, memoryDir, sessionId,
|
|
124
|
-
env: {}, lexicon, vocabHint, planState, researchState,
|
|
125
|
-
liveReference: liveReferenceOn, onLiveLookup,
|
|
126
|
-
uiContext: "browser", synthesisBudget: synthesisBudgetOn,
|
|
127
|
-
});
|
|
128
|
-
} catch (e) {
|
|
129
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
130
|
-
return { answer: `Something went wrong answering that (${message}). Try rephrasing, or /help.`, end: false, record: null, plan: null, research: null };
|
|
131
|
-
}
|
|
132
|
-
focus = result.focus;
|
|
133
|
-
last = result.last;
|
|
134
|
-
if ("planState" in result) planState = result.planState;
|
|
135
|
-
if ("researchState" in result) researchState = result.researchState;
|
|
136
|
-
if (typeof result.liveReference === "boolean" || result.liveReference === "supplement" || result.liveReference === "always") liveReferenceOn = result.liveReference;
|
|
137
|
-
// `research` distinguishes three cases on purpose: a queue snapshot
|
|
138
|
-
// (research turn), null (a research turn that ended the run), and
|
|
139
|
-
// undefined (not a research turn — the page leaves its controls alone).
|
|
140
|
-
return { answer: result.answer, end: Boolean(result.end), record: result.record ?? null, plan: result.plan ?? null, research: result.research };
|
|
141
|
-
},
|
|
123
|
+
turn: session.turn,
|
|
142
124
|
};
|
|
143
125
|
}
|
|
144
126
|
|
|
145
|
-
/**
|
|
146
|
-
* The memory a running session holds, broken down by where each fact came
|
|
147
|
-
* from: the seed corpus bands it booted with (keyed by the SAME band name
|
|
148
|
-
* build-chat-seed.mjs/extensions.mjs seed under — "human", "seon",
|
|
149
|
-
* "conceptnet" today, whichever bands a future seed adds tomorrow) plus
|
|
150
|
-
* whatever has been taught THIS session over chat. Reuses memory/trust.mjs's
|
|
151
|
-
* own `provenanceTagToSource` against each fact's already-stored provenance
|
|
152
|
-
* tag(s) (readFactRows' `provenance`, the ' | '-joined compat string) rather
|
|
153
|
-
* than inventing a second provenance parse — the same tag chat.mjs's own
|
|
154
|
-
* "(source: ...)" citation already carries, so this panel and a turn's
|
|
155
|
-
* citation always agree on where a fact came from.
|
|
156
|
-
*
|
|
157
|
-
* Returns { total, bandCounts, taught }: `bandCounts` maps a band label to
|
|
158
|
-
* its fact count ("taught this session" for a teach/operator-sourced fact
|
|
159
|
-
* with no corpus band, "other" for anything provenance can't place);
|
|
160
|
-
* `taught` lists every session-taught fact (subject/predicate/object + its
|
|
161
|
-
* own provenance tag), most-recently-taught last — a stats panel's
|
|
162
|
-
* provenance column reads straight off this, no further lookup needed.
|
|
163
|
-
*/
|
|
164
|
-
export async function memoryStats(memoryDir) {
|
|
165
|
-
const memory = await loadMemory(memoryDir);
|
|
166
|
-
const rows = readFactRows(memory);
|
|
167
|
-
const bandCounts = {};
|
|
168
|
-
const taught = [];
|
|
169
|
-
for (const row of rows) {
|
|
170
|
-
const tags = String(row.provenance || "").split(" | ").filter(Boolean);
|
|
171
|
-
let band = null;
|
|
172
|
-
let isTaught = false;
|
|
173
|
-
let taughtTag = "";
|
|
174
|
-
for (const tag of tags) {
|
|
175
|
-
const src = provenanceTagToSource(tag);
|
|
176
|
-
if (!src) continue;
|
|
177
|
-
// corpusWeak (a /r/RelatedTo-strength triple, e.g. ConceptNet's or
|
|
178
|
-
// SEON's own weaker associations) names the SAME band as corpus (a
|
|
179
|
-
// /r/IsA-strength one) — both carry `src.name`, and a band count that
|
|
180
|
-
// dropped the weak tier would undercount a corpus by exactly its
|
|
181
|
-
// weak-relation facts (SEON: 19 of them, all `corpus-weak:seon`).
|
|
182
|
-
if ((src.kind === "corpus" || src.kind === "corpusWeak") && src.name) band = src.name;
|
|
183
|
-
if (src.kind === "teach" || src.kind === "operator") { isTaught = true; taughtTag = tag; }
|
|
184
|
-
}
|
|
185
|
-
const label = band || (isTaught ? "taught this session" : "other");
|
|
186
|
-
bandCounts[label] = (bandCounts[label] || 0) + 1;
|
|
187
|
-
if (isTaught) taught.push({ subject: row.subject, predicate: row.predicate, object: row.object, tag: taughtTag });
|
|
188
|
-
}
|
|
189
|
-
return { total: rows.length, bandCounts, taught };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* The session's whole triple store as JSONL — one
|
|
194
|
-
* { subject, predicate, object, provenance } object per line, the same shape
|
|
195
|
-
* `tmct extract` and `tmct memory --export` emit. Reads the live memory the
|
|
196
|
-
* same way memoryStats does; the page offers it as a download.
|
|
197
|
-
*/
|
|
198
|
-
export async function exportFactsJsonl(memoryDir) {
|
|
199
|
-
return serializeFactsJsonl(await loadMemory(memoryDir));
|
|
200
|
-
}
|
|
201
|
-
|
|
202
127
|
/**
|
|
203
128
|
* Every fact a "research <topic>" run has stored so far, in storage order —
|
|
204
129
|
* the exposure the "researched this session" panel needs and runTurn's
|
|
@@ -13,14 +13,17 @@
|
|
|
13
13
|
// Gitignored, built fresh by scripts/build-code-explorer-bundle.mjs; the page
|
|
14
14
|
// degrades to a static view when it is absent (renderCodeExplorerHtml's own
|
|
15
15
|
// contract), so nothing here is ever published.
|
|
16
|
-
import {
|
|
16
|
+
import { vocabExampleHint } from "../../services/chat.mjs";
|
|
17
17
|
import { createInMemoryStore, normFactTerm } from "../../adapters/memory/core.mjs";
|
|
18
18
|
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
19
19
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
20
20
|
import { registerWinkModel } from "../../adapters/wink-model.mjs";
|
|
21
21
|
import * as source from "../../adapters/source.mjs";
|
|
22
|
-
import { computeCodeExplorerData, computeCodeLedger } from "../../services/code-explorer-viz.mjs";
|
|
22
|
+
import { computeCodeExplorerData, computeCodeLedger, edgePhrase } from "../../services/code-explorer-viz.mjs";
|
|
23
23
|
import { generateCodeHints } from "../../domain/code-explorer-hints.mjs";
|
|
24
|
+
import { createTurnSession } from "./turn-session.mjs";
|
|
25
|
+
import { tmct_ask } from "../../tools/handlers/tmct-ask.mjs";
|
|
26
|
+
import { RELATIONS } from "../../domain/ask-vocab.mjs";
|
|
24
27
|
|
|
25
28
|
/**
|
|
26
29
|
* A browser code-explorer session over the real turn engine. Registers the
|
|
@@ -54,38 +57,179 @@ export function createCodeExplorerSession({ graphPayload, seedPayload = null, vo
|
|
|
54
57
|
// ever read, but the code lanes still do path math (join/dirname) on it.
|
|
55
58
|
const config = { graphFile: "graph.json" };
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
const turnSession = createTurnSession({
|
|
61
|
+
memoryDir, graph, lexicon, sessionId, vocabHint,
|
|
62
|
+
// The code explorer is the one page whose turns run against a real
|
|
63
|
+
// provider seam (source.mjs, registered above) and a virtual graph file
|
|
64
|
+
// path, so both override createTurnSession's config:null/source:null
|
|
65
|
+
// defaults on every call.
|
|
66
|
+
buildExtraOptions: () => ({ config, source }),
|
|
67
|
+
});
|
|
60
68
|
|
|
61
69
|
return {
|
|
62
70
|
memoryDir,
|
|
63
71
|
sessionId,
|
|
64
|
-
|
|
65
|
-
let result;
|
|
66
|
-
try {
|
|
67
|
-
result = await runTurn(line, {
|
|
68
|
-
config, source, graph, focus, last, memoryDir, sessionId,
|
|
69
|
-
env: {}, lexicon, vocabHint, planState,
|
|
70
|
-
});
|
|
71
|
-
} catch (e) {
|
|
72
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
73
|
-
return { answer: `Something went wrong answering that (${message}). Try rephrasing, or /help.`, end: false, record: null };
|
|
74
|
-
}
|
|
75
|
-
focus = result.focus;
|
|
76
|
-
last = result.last;
|
|
77
|
-
if ("planState" in result) planState = result.planState;
|
|
78
|
-
return { answer: result.answer, end: Boolean(result.end), record: result.record ?? null };
|
|
79
|
-
},
|
|
72
|
+
turn: turnSession.turn,
|
|
80
73
|
};
|
|
81
74
|
}
|
|
82
75
|
|
|
76
|
+
// ---- "what relates to X", asked ------------------------------------------
|
|
77
|
+
|
|
78
|
+
// A symbol-grain predicate asks as its coarse sibling: ask()'s relation
|
|
79
|
+
// vocabulary carries one question per grain pair, not two.
|
|
80
|
+
const COARSE_RELATION = { callsSymbol: "calls", touchesSymbol: "touches" };
|
|
81
|
+
|
|
82
|
+
/** The two questions ask() understands about one relation kind, both spelled
|
|
83
|
+
* from its own vocabulary: `verbs[0]` reads as the reverse direction ("what
|
|
84
|
+
* couples to X"), `bare` as the forward one ("what does X import"). A relation
|
|
85
|
+
* taught to ask-vocab is asked here with no second table to keep in step. */
|
|
86
|
+
function relationQuestions(kind, term) {
|
|
87
|
+
const { bare, verbs } = RELATIONS[kind];
|
|
88
|
+
return [
|
|
89
|
+
{ shape: "reverse", query: `what ${verbs[0]} ${term}` },
|
|
90
|
+
{ shape: "forward", query: `what does ${term} ${bare}` },
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The kinds worth asking about: the ones this graph actually stores, folded
|
|
95
|
+
* onto ask()'s vocabulary keys. A graph carrying only imports asks two
|
|
96
|
+
* questions, not twenty. */
|
|
97
|
+
function askableRelationKinds(graph) {
|
|
98
|
+
const kinds = new Set();
|
|
99
|
+
for (const relation of graph.relations) {
|
|
100
|
+
if (!relation.edges.length) continue;
|
|
101
|
+
const kind = COARSE_RELATION[relation.predicate] || relation.predicate;
|
|
102
|
+
if (Object.hasOwn(RELATIONS, kind)) kinds.add(kind);
|
|
103
|
+
}
|
|
104
|
+
return [...kinds];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Every stored edge as `<subjectId> <objectId>`, keyed by the coarse relation
|
|
108
|
+
* kind so both grains of a pair land in one set, beside a label-to-ids index.
|
|
109
|
+
* An answer names the individuals it found; these two decide whether the row
|
|
110
|
+
* built from that answer is an edge the graph actually holds. */
|
|
111
|
+
function indexEdges(graph) {
|
|
112
|
+
const edgesByKind = new Map();
|
|
113
|
+
for (const relation of graph.relations) {
|
|
114
|
+
const kind = COARSE_RELATION[relation.predicate] || relation.predicate;
|
|
115
|
+
let pairs = edgesByKind.get(kind);
|
|
116
|
+
if (!pairs) edgesByKind.set(kind, (pairs = new Set()));
|
|
117
|
+
for (const edge of relation.edges) pairs.add(`${edge.subject} ${edge.object}`);
|
|
118
|
+
}
|
|
119
|
+
const idsByLabel = new Map();
|
|
120
|
+
for (const individual of graph.individuals) {
|
|
121
|
+
if (!individual?.label || !individual.id) continue;
|
|
122
|
+
const ids = idsByLabel.get(individual.label);
|
|
123
|
+
if (ids) ids.push(individual.id);
|
|
124
|
+
else idsByLabel.set(individual.label, [individual.id]);
|
|
125
|
+
}
|
|
126
|
+
return { edgesByKind, idsByLabel };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// parseEntities walks the whole payload, and a focus click re-asks the same
|
|
130
|
+
// graph, so one parse and one edge index per payload serve every question put
|
|
131
|
+
// to it.
|
|
132
|
+
const readGraphs = new WeakMap();
|
|
133
|
+
function readGraph(payload) {
|
|
134
|
+
let read = readGraphs.get(payload);
|
|
135
|
+
if (!read) {
|
|
136
|
+
const graph = parseEntities(payload);
|
|
137
|
+
read = { graph, ...indexEdges(graph) };
|
|
138
|
+
readGraphs.set(payload, read);
|
|
139
|
+
}
|
|
140
|
+
return read;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const rowKey = (row) => JSON.stringify([row.s, row.kind, row.o]);
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* "What relates to <term>", asked instead of computed. Every relation kind the
|
|
147
|
+
* loaded graph carries becomes two real tmct_ask round trips over that graph —
|
|
148
|
+
* the same ask() every chat turn on this page already reaches — and the
|
|
149
|
+
* answers' typed `matches` become the explorer sidebar's rows. The page stops
|
|
150
|
+
* deciding what relates to what, so the panel and the conversation cannot
|
|
151
|
+
* scope or phrase one question two ways.
|
|
152
|
+
*
|
|
153
|
+
* Two things have to hold before an answer becomes a row. ask() must have
|
|
154
|
+
* parsed the question as the question asked: an identifier that is itself a
|
|
155
|
+
* relation verb makes it read "what tests run" as a question about testing, and
|
|
156
|
+
* rows off that answer would describe the wrong term. And the row must be an
|
|
157
|
+
* edge this graph holds between the term and what the answer named — ask()
|
|
158
|
+
* resolves a term against the asked relation's own range, so "what contains
|
|
159
|
+
* Task" legitimately answers about `Task.title`'s container, which is a true
|
|
160
|
+
* sentence about a different pair than the one the sidebar is drawing. A row
|
|
161
|
+
* the graph cannot confirm is dropped rather than shown; the engine's own prose
|
|
162
|
+
* is where an inexact resolution belongs, because there it can say so.
|
|
163
|
+
*
|
|
164
|
+
* `grounded` is false when no question was understood as asked, which is the
|
|
165
|
+
* caller's signal to fall back rather than show an empty neighbourhood for a
|
|
166
|
+
* term the graph plainly has edges for.
|
|
167
|
+
*
|
|
168
|
+
* Returns { term, rows, asked, grounded }: `rows` in computeCodeLedger's own
|
|
169
|
+
* row shape so one renderer draws both, `asked` every question put to the
|
|
170
|
+
* engine beside what came back.
|
|
171
|
+
*/
|
|
172
|
+
export function askRelatedFacts(graphPayload, term) {
|
|
173
|
+
const rows = [];
|
|
174
|
+
const asked = [];
|
|
175
|
+
let grounded = false;
|
|
176
|
+
if (!graphPayload || !term) return { term: term || null, rows, asked, grounded };
|
|
177
|
+
|
|
178
|
+
const { graph, edgesByKind, idsByLabel } = readGraph(graphPayload);
|
|
179
|
+
// An edge group's objectLabel is denormalized and can be shorter than the
|
|
180
|
+
// individual's own ("assignTo" for `Task.assignTo`), so the row list offers
|
|
181
|
+
// clickable terms this graph holds no individual for. There is nothing to ask
|
|
182
|
+
// about one, and saying so leaves the caller its row-list fallback instead of
|
|
183
|
+
// an answered-and-empty neighbourhood.
|
|
184
|
+
const termIds = idsByLabel.get(term) || [];
|
|
185
|
+
if (!termIds.length) return { term, rows, asked, grounded };
|
|
186
|
+
|
|
187
|
+
const seen = new Set();
|
|
188
|
+
for (const kind of askableRelationKinds(graph)) {
|
|
189
|
+
const storedEdges = edgesByKind.get(kind) || new Set();
|
|
190
|
+
const holdsEdge = (subjectId, objectId) => storedEdges.has(`${subjectId} ${objectId}`);
|
|
191
|
+
for (const { shape, query } of relationQuestions(kind, term)) {
|
|
192
|
+
const envelope = tmct_ask({ query }, { graph }).data;
|
|
193
|
+
const parsed = envelope?.parsed;
|
|
194
|
+
const answeredAsAsked = parsed?.shape === shape
|
|
195
|
+
&& parsed?.kind === kind
|
|
196
|
+
&& parsed?.object === term
|
|
197
|
+
&& !envelope.ambiguous;
|
|
198
|
+
if (!answeredAsAsked) {
|
|
199
|
+
asked.push({ query, kind, shape, used: false, parsedAs: parsed?.canonical?.machine ?? null });
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
grounded = true;
|
|
203
|
+
const matches = Array.isArray(envelope.matches) ? envelope.matches : [];
|
|
204
|
+
let confirmed = 0;
|
|
205
|
+
for (const match of matches) {
|
|
206
|
+
if (!match?.label || !match.id) continue;
|
|
207
|
+
const aboutTerm = termIds.some((termId) => (shape === "reverse"
|
|
208
|
+
? holdsEdge(match.id, termId)
|
|
209
|
+
: holdsEdge(termId, match.id)));
|
|
210
|
+
if (!aboutTerm) continue;
|
|
211
|
+
confirmed += 1;
|
|
212
|
+
const row = shape === "reverse"
|
|
213
|
+
? { s: match.label, kind, phrase: edgePhrase(kind), o: term, sClass: match.type || "", oClass: "" }
|
|
214
|
+
: { s: term, kind, phrase: edgePhrase(kind), o: match.label, sClass: "", oClass: match.type || "" };
|
|
215
|
+
const key = rowKey(row);
|
|
216
|
+
if (seen.has(key)) continue;
|
|
217
|
+
seen.add(key);
|
|
218
|
+
rows.push(row);
|
|
219
|
+
}
|
|
220
|
+
asked.push({ query, kind, shape, used: true, miss: Boolean(envelope.miss), matched: matches.length, confirmed, traversal: envelope.traversal ?? null });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return { term, rows, asked, grounded };
|
|
224
|
+
}
|
|
225
|
+
|
|
83
226
|
// Exposed for the page's inline client: the re-derivation helpers so a graph
|
|
84
|
-
// swapped through the desktop picker re-renders without duplicating logic,
|
|
85
|
-
//
|
|
86
|
-
// the ledger page.
|
|
227
|
+
// swapped through the desktop picker re-renders without duplicating logic, the
|
|
228
|
+
// engine's own answer to "what relates to the focus", plus the wink loader hook
|
|
229
|
+
// registerWinkModel and normFactTerm the chat shares with the ledger page.
|
|
87
230
|
globalThis.tmctCodeExplorer = {
|
|
88
231
|
createCodeExplorerSession,
|
|
232
|
+
askRelatedFacts,
|
|
89
233
|
computeCodeExplorerData,
|
|
90
234
|
computeCodeLedger,
|
|
91
235
|
generateCodeHints,
|
|
@@ -24,14 +24,13 @@
|
|
|
24
24
|
// runTurn engine, the same weight class as the chat/ledger bundles, and is
|
|
25
25
|
// never published.
|
|
26
26
|
import { runTurn, vocabExampleHint } from "../../services/chat.mjs";
|
|
27
|
-
import { createInMemoryStore, normFactTerm, loadMemory, readFactRows, appendFact } from "../../adapters/memory/core.mjs";
|
|
28
|
-
import { serializeFactsJsonl } from "../../adapters/memory/export-jsonl.mjs";
|
|
27
|
+
import { createInMemoryStore, normFactTerm, loadMemory, readFactRows, appendFact, applySeedPayload } from "../../adapters/memory/core.mjs";
|
|
29
28
|
import { splitSentencesPreservingPaths, stripCitationResidue } from "../../services/sentences.mjs";
|
|
30
29
|
import { clauseCandidates, optimisticTriples } from "../../services/extract-facts.mjs";
|
|
31
30
|
import { touchedFactRows } from "../../domain/memory/touched-facts.mjs";
|
|
32
31
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
33
32
|
import { registerWinkModel, winkInstance } from "../../adapters/wink-model.mjs";
|
|
34
|
-
import { memoryStats } from "./memory-stats.mjs";
|
|
33
|
+
import { memoryStats, exportFactsJsonl } from "./memory-stats.mjs";
|
|
35
34
|
import { openPersistedStore } from "./idb-persist.mjs";
|
|
36
35
|
|
|
37
36
|
// The pronoun subjects a bounded carry substitutes with the last unique
|
|
@@ -178,7 +177,7 @@ export async function groundTextToFacts(text, { memoryDir, sessionId, lexicon, v
|
|
|
178
177
|
*/
|
|
179
178
|
export function createIngestSession({ seedPayload = null, vocabSeeded = false } = {}) {
|
|
180
179
|
const memoryDir = createInMemoryStore();
|
|
181
|
-
|
|
180
|
+
applySeedPayload(memoryDir, seedPayload);
|
|
182
181
|
|
|
183
182
|
const lexicon = loadLexicon();
|
|
184
183
|
const vocabHint = vocabExampleHint(vocabSeeded);
|
|
@@ -193,15 +192,6 @@ export function createIngestSession({ seedPayload = null, vocabSeeded = false }
|
|
|
193
192
|
};
|
|
194
193
|
}
|
|
195
194
|
|
|
196
|
-
/**
|
|
197
|
-
* The session's whole triple store as JSONL — the same
|
|
198
|
-
* { subject, predicate, object, provenance } shape `tmct extract` and
|
|
199
|
-
* `tmct memory --export` emit, offered to the page as the canonical download.
|
|
200
|
-
*/
|
|
201
|
-
export async function exportFactsJsonl(memoryDir) {
|
|
202
|
-
return serializeFactsJsonl(await loadMemory(memoryDir));
|
|
203
|
-
}
|
|
204
|
-
|
|
205
195
|
globalThis.tmctIngest = {
|
|
206
196
|
createIngestSession, groundTextToFacts, exportFactsJsonl, registerWinkModel, normFactTerm,
|
|
207
197
|
memoryStats, openPersistedStore,
|
|
@@ -16,9 +16,8 @@
|
|
|
16
16
|
// class as chat/spider-fly/adventure/plan's own browser bundles) and is
|
|
17
17
|
// never published — only the hosted demo site's public/ledger.html links to
|
|
18
18
|
// it, as an optional sibling script the page degrades honestly without.
|
|
19
|
-
import {
|
|
20
|
-
import { createInMemoryStore, normFactTerm,
|
|
21
|
-
import { serializeFactsJsonl } from "../../adapters/memory/export-jsonl.mjs";
|
|
19
|
+
import { vocabExampleHint } from "../../services/chat.mjs";
|
|
20
|
+
import { createInMemoryStore, normFactTerm, applySeedPayload } from "../../adapters/memory/core.mjs";
|
|
22
21
|
import { splitSentencesPreservingPaths } from "../../services/sentences.mjs";
|
|
23
22
|
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
24
23
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
@@ -26,6 +25,8 @@ import { registerWinkModel } from "../../adapters/wink-model.mjs";
|
|
|
26
25
|
import { registerResearchProvider } from "../../adapters/corpus/wikipedia-live.mjs";
|
|
27
26
|
import { computeLedgerDataFromPayload } from "../../services/ledger-viz.mjs";
|
|
28
27
|
import { digestTermFromPayloadBrowser } from "./digest-client.mjs";
|
|
28
|
+
import { createTurnSession } from "./turn-session.mjs";
|
|
29
|
+
import { exportFactsJsonl } from "./memory-stats.mjs";
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* A browser ledger-dock session over the real turn engine — createChatSession's
|
|
@@ -43,56 +44,15 @@ import { digestTermFromPayloadBrowser } from "./digest-client.mjs";
|
|
|
43
44
|
*/
|
|
44
45
|
export function createLedgerSession({ seedPayload = null, vocabSeeded = false } = {}) {
|
|
45
46
|
const memoryDir = createInMemoryStore();
|
|
46
|
-
|
|
47
|
-
// carries the classes/prefixes scaffolding the write path recounts —
|
|
48
|
-
// teach turns must work regardless of what the seed payload carries.
|
|
49
|
-
if (seedPayload) memoryDir.payload = { ...memoryDir.payload, ...seedPayload };
|
|
47
|
+
applySeedPayload(memoryDir, seedPayload);
|
|
50
48
|
|
|
51
49
|
const graph = parseEntities({ individuals: [], objectProperties: [] });
|
|
52
50
|
const lexicon = loadLexicon();
|
|
53
51
|
const vocabHint = vocabExampleHint(vocabSeeded);
|
|
54
52
|
const sessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
let planState = null;
|
|
59
|
-
let researchState = null;
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
memoryDir,
|
|
63
|
-
sessionId,
|
|
64
|
-
|
|
65
|
-
/** One dispatched turn. A throwing runTurn must never kill the session —
|
|
66
|
-
* the page has no other chance to show this turn's answer. */
|
|
67
|
-
async turn(line) {
|
|
68
|
-
let result;
|
|
69
|
-
try {
|
|
70
|
-
result = await runTurn(line, {
|
|
71
|
-
config: null, source: null, graph, focus, last, memoryDir, sessionId,
|
|
72
|
-
env: {}, lexicon, vocabHint, planState, researchState,
|
|
73
|
-
});
|
|
74
|
-
} catch (e) {
|
|
75
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
76
|
-
return { answer: `Something went wrong answering that (${message}). Try rephrasing, or /help.`, end: false, record: null, research: null };
|
|
77
|
-
}
|
|
78
|
-
focus = result.focus;
|
|
79
|
-
last = result.last;
|
|
80
|
-
if ("planState" in result) planState = result.planState;
|
|
81
|
-
if ("researchState" in result) researchState = result.researchState;
|
|
82
|
-
// `research` distinguishes a queue snapshot (research turn) from null
|
|
83
|
-
// (a research turn that ended the run) from undefined (not a research
|
|
84
|
-
// turn) — the dock's controls only react to the first two.
|
|
85
|
-
return { answer: result.answer, end: Boolean(result.end), record: result.record ?? null, research: result.research };
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** The session's whole triple store as JSONL — the same
|
|
91
|
-
* { subject, predicate, object, provenance } shape `tmct extract` and
|
|
92
|
-
* `tmct memory --export` emit. The ledger dock's "export facts" control reads
|
|
93
|
-
* this and offers it as a download. */
|
|
94
|
-
export async function exportFactsJsonl(memoryDir) {
|
|
95
|
-
return serializeFactsJsonl(await loadMemory(memoryDir));
|
|
54
|
+
const session = createTurnSession({ memoryDir, graph, lexicon, sessionId, vocabHint });
|
|
55
|
+
return { memoryDir, sessionId, turn: session.turn };
|
|
96
56
|
}
|
|
97
57
|
|
|
98
58
|
// Re-exported so ledger-viz.mjs's own inline script never has to duplicate
|