@polycode-projects/the-mechanical-code-talker 4.1.0 → 4.1.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/README.md +31 -18
- package/bin/tmct.mjs +3 -0
- package/data/templates/responses.jsonl +3 -0
- package/package.json +2 -1
- package/src/adapters/memory/core.mjs +1358 -196
- package/src/adapters/memory/inspect.mjs +11 -0
- package/src/adapters/memory/shacl.mjs +38 -0
- package/src/adapters/p2p/webrtc-transport.mjs +28 -5
- package/src/domain/ask-vocab.mjs +39 -0
- package/src/domain/ask.mjs +183 -34
- package/src/domain/grammar/assert.mjs +8 -2
- package/src/domain/hanoi-board.mjs +232 -0
- package/src/domain/ingest-facts.mjs +120 -0
- package/src/domain/interpret/normalize.mjs +49 -0
- package/src/domain/memory/compaction.mjs +284 -0
- package/src/domain/memory/resolution.mjs +171 -0
- package/src/domain/memory/trust.mjs +175 -5
- package/src/domain/memory-facts.mjs +139 -0
- package/src/domain/p2p/facts.mjs +21 -0
- package/src/domain/p2p/peer-id.mjs +15 -0
- package/src/domain/p2p/provenance-relabel.mjs +13 -2
- package/src/domain/p2p/sync-filter.mjs +5 -1
- package/src/domain/p2p/wire.mjs +7 -4
- package/src/domain/scene-compose.mjs +2 -2
- package/src/domain/sprite-facts.mjs +0 -0
- package/src/domain/sprite-request.mjs +1 -1
- package/src/services/adventure-viz.mjs +43 -39
- package/src/services/adventure.mjs +70 -44
- package/src/services/chat-page-viz.mjs +403 -332
- package/src/services/chat.mjs +274 -156
- package/src/services/code-explorer-viz.mjs +142 -55
- package/src/services/index.mjs +1 -1
- package/src/services/ingest-viz.mjs +153 -28
- package/src/services/ledger-viz.mjs +47 -47
- package/src/services/memory-panel-viz.mjs +8 -3
- package/src/services/mud-turn.mjs +11 -8
- package/src/services/mud-viz.mjs +474 -234
- package/src/services/p2p-room.mjs +110 -23
- package/src/services/plan-viz.mjs +72 -13
- package/src/services/research-viz.mjs +35 -24
- package/src/services/share-overlay-viz.mjs +623 -0
- package/src/services/spider-fly-viz.mjs +24 -24
- package/src/services/sprite-catalog-viz.mjs +307 -82
- package/src/surfaces/web/adventure-browser-entry.mjs +46 -25
- package/src/surfaces/web/chat-browser-entry.mjs +55 -9
- package/src/surfaces/web/code-explorer-browser-entry.mjs +30 -16
- package/src/surfaces/web/engine-surface.mjs +82 -0
- package/src/surfaces/web/ingest-browser-entry.mjs +81 -11
- package/src/surfaces/web/ledger-browser-entry.mjs +47 -14
- package/src/surfaces/web/memory-ask-browser-entry.mjs +55 -13
- package/src/surfaces/web/memory-ask-browser.bundle.js +149 -116
- package/src/surfaces/web/mud-browser-entry.mjs +77 -25
- package/src/surfaces/web/p2p-browser-entry.mjs +1 -1
- package/src/surfaces/web/plan-browser-entry.mjs +49 -11
- package/src/surfaces/web/research-browser-entry.mjs +33 -24
- package/src/surfaces/web/spider-fly-browser-entry.mjs +32 -13
- package/src/surfaces/web/sprites-browser-entry.mjs +51 -11
- package/src/surfaces/web/tmct-surface.mjs +159 -0
- package/src/surfaces/web/turn-session.mjs +16 -5
- package/src/tools/server.mjs +13 -6
|
@@ -35,10 +35,13 @@
|
|
|
35
35
|
// game-shaped falls through to the ordinary conversational layer, exactly
|
|
36
36
|
// like a real CLI session.
|
|
37
37
|
import { createTurnSession } from "./turn-session.mjs";
|
|
38
|
+
import { publishTmctSurface } from "./tmct-surface.mjs";
|
|
39
|
+
import { graphAsk, enginePlan } from "./engine-surface.mjs";
|
|
38
40
|
import {
|
|
39
41
|
createInMemoryStore, appendFacts, appendRule, loadMemory, readFactRows, removeFacts,
|
|
40
42
|
} from "../../adapters/memory/core.mjs";
|
|
41
43
|
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
44
|
+
import { memoryFactGraphPayload } from "../../domain/memory-facts.mjs";
|
|
42
45
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
43
46
|
import { foldWorldState, worldDigestRows, roomAffordances, worldActionRows } from "../../services/adventure.mjs";
|
|
44
47
|
import { runAdventureAutoplayTick, exposedFacts } from "../../services/adventure-autoplay.mjs";
|
|
@@ -101,7 +104,21 @@ export async function createAdventureSession(worldPayload, { restoredPayload = n
|
|
|
101
104
|
const openingHere = foldWorldState(worldActionRows(openingRows)).placements.get("player")?.object ?? null;
|
|
102
105
|
if (openingHere) visitedRoomIds.add(openingHere);
|
|
103
106
|
|
|
104
|
-
|
|
107
|
+
// A known-empty code graph: code-structure questions get the same honest
|
|
108
|
+
// no-code-graph answer an un-pointed CLI session gives, never a crash — the
|
|
109
|
+
// turn engine keeps reading THIS one for its own in-turn code lane.
|
|
110
|
+
const codeGraph = parseEntities({ individuals: [], objectProperties: [] });
|
|
111
|
+
|
|
112
|
+
// What `tmct.ask()` traverses is a different graph: this world's own memory
|
|
113
|
+
// store, projected through memoryFactGraphPayload. Rebuilt on demand rather
|
|
114
|
+
// than once at open, because every taught fact and every played turn grows
|
|
115
|
+
// the store.
|
|
116
|
+
let memoryGraph = parseEntities({ individuals: [], objectProperties: [] });
|
|
117
|
+
async function refreshGraph() {
|
|
118
|
+
memoryGraph = parseEntities(memoryFactGraphPayload(readFactRows(await loadMemory(memoryDir))));
|
|
119
|
+
return memoryGraph;
|
|
120
|
+
}
|
|
121
|
+
|
|
105
122
|
const lexicon = loadLexicon();
|
|
106
123
|
|
|
107
124
|
// createTurnSession owns focus/last and the catch fallback; planHolder
|
|
@@ -115,9 +132,9 @@ export async function createAdventureSession(worldPayload, { restoredPayload = n
|
|
|
115
132
|
// when the command didn't move anyone) — the manual-play half of the
|
|
116
133
|
// merged exposure set.
|
|
117
134
|
const turnSession = createTurnSession({
|
|
118
|
-
memoryDir, graph, lexicon, sessionId,
|
|
135
|
+
memoryDir, graph: codeGraph, lexicon, sessionId,
|
|
119
136
|
vocabHint: 'Try a world question ("where is the key"), or teach me: "remember: the moat is a ditch".',
|
|
120
|
-
buildExtraOptions: () => ({
|
|
137
|
+
buildExtraOptions: () => ({ planState: planHolder.state }),
|
|
121
138
|
captureExtraState: async (result) => {
|
|
122
139
|
if ("planState" in result) planHolder.state = result.planState;
|
|
123
140
|
const here = foldWorldState(worldActionRows(readFactRows(await loadMemory(memoryDir)))).placements.get("player")?.object ?? null;
|
|
@@ -127,6 +144,9 @@ export async function createAdventureSession(worldPayload, { restoredPayload = n
|
|
|
127
144
|
|
|
128
145
|
return {
|
|
129
146
|
memoryDir,
|
|
147
|
+
codeGraph,
|
|
148
|
+
get graph() { return memoryGraph; },
|
|
149
|
+
refreshGraph,
|
|
130
150
|
|
|
131
151
|
/** One auto-play tick: infer the goal, execute exactly one move through
|
|
132
152
|
* adventureTurn (adventure-autoplay.mjs's own contract), thread the
|
|
@@ -192,25 +212,26 @@ export async function createAdventureSession(worldPayload, { restoredPayload = n
|
|
|
192
212
|
};
|
|
193
213
|
}
|
|
194
214
|
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
// affordances the chat
|
|
198
|
-
//
|
|
199
|
-
// the
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
215
|
+
// `tmct.page` keeps what the page draws with and the engine has no
|
|
216
|
+
// plain-English form for: sprite resolution, the digest reader, the room
|
|
217
|
+
// affordances the chat pills read from, the exposure-filtered fold the
|
|
218
|
+
// goal-status panel mirrors, the SKOS neighbourhood and is-a chain behind the
|
|
219
|
+
// edit mode's cursor pills, the persisted-store wrapper, and the manor map's
|
|
220
|
+
// own BFS-grid layout and SVG renderer (neither is `.toString()`-splice-safe:
|
|
221
|
+
// roomGraphSvg needs escapeHtml, directedGridLayout its own exit-delta table).
|
|
222
|
+
publishTmctSurface({
|
|
223
|
+
open: createAdventureSession,
|
|
224
|
+
// The memory projection is rebuilt first, so a direct tmct.ask() sees every
|
|
225
|
+
// fact taught or played into this world since the last one.
|
|
226
|
+
ask: async (request, options, session) => {
|
|
227
|
+
await session.refreshGraph();
|
|
228
|
+
return graphAsk(request, options, session);
|
|
229
|
+
},
|
|
230
|
+
plan: enginePlan,
|
|
231
|
+
page: {
|
|
232
|
+
resolveSpriteForClass, SPRITE_REGISTRY, resolveSpriteAsset,
|
|
233
|
+
worldDigestRows, roomAffordances, foldWorldState, exposedFacts,
|
|
234
|
+
relatedForTerm, classAncestorChain, openPersistedStore,
|
|
235
|
+
directedGridLayout, roomGraphSvg,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
@@ -30,6 +30,7 @@ import { vocabExampleHint } from "../../services/chat.mjs";
|
|
|
30
30
|
import { createInMemoryStore, normFactTerm, loadMemory, readFactRows, applySeedPayload } from "../../adapters/memory/core.mjs";
|
|
31
31
|
import { splitSentencesPreservingPaths } from "../../services/sentences.mjs";
|
|
32
32
|
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
33
|
+
import { memoryFactGraphPayload } from "../../domain/memory-facts.mjs";
|
|
33
34
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
34
35
|
import { registerWinkModel } from "../../adapters/wink-model.mjs";
|
|
35
36
|
import { setDigestStructures } from "../../adapters/corpus/digest-bank.mjs";
|
|
@@ -51,6 +52,8 @@ import { registerLiveReferenceProvider, registerResearchProvider } from "../../a
|
|
|
51
52
|
// decides when to save/load/clear; this entry only carries the wrapper
|
|
52
53
|
// across the bundle boundary.
|
|
53
54
|
import { openPersistedStore } from "./idb-persist.mjs";
|
|
55
|
+
import { publishTmctSurface } from "./tmct-surface.mjs";
|
|
56
|
+
import { graphAsk, enginePlan } from "./engine-surface.mjs";
|
|
54
57
|
|
|
55
58
|
/**
|
|
56
59
|
* A browser chat session over the real turn engine.
|
|
@@ -70,9 +73,14 @@ import { openPersistedStore } from "./idb-persist.mjs";
|
|
|
70
73
|
* is module-scope, last write wins, and every session created after this one
|
|
71
74
|
* shares it.
|
|
72
75
|
*
|
|
73
|
-
* Returns { memoryDir, sessionId, turn }.
|
|
74
|
-
* { answer, end, record, plan } and threads
|
|
75
|
-
* calls exactly as the CLI session does.
|
|
76
|
+
* Returns { memoryDir, sessionId, graph, codeGraph, refreshGraph, turn }.
|
|
77
|
+
* `turn(line)` resolves to { answer, end, record, plan } and threads
|
|
78
|
+
* focus/last/planState between calls exactly as the CLI session does.
|
|
79
|
+
*
|
|
80
|
+
* The two graphs are separate on purpose. `codeGraph` is the known-empty index
|
|
81
|
+
* the turn engine reads, and `graph` is this session's memory store projected
|
|
82
|
+
* for `ask()` — a question about taught facts has a real graph to traverse
|
|
83
|
+
* while a code-structure question keeps its honest no-code-graph refusal.
|
|
76
84
|
*/
|
|
77
85
|
export function createChatSession({ seedPayload = null, vocabSeeded = false, liveReference = false, onLiveLookup = null, synthesisBudget = 12, digestStructures = null } = {}) {
|
|
78
86
|
setDigestStructures(digestStructures || []);
|
|
@@ -80,10 +88,24 @@ export function createChatSession({ seedPayload = null, vocabSeeded = false, liv
|
|
|
80
88
|
applySeedPayload(memoryDir, seedPayload);
|
|
81
89
|
|
|
82
90
|
// A known-empty code graph: code-structure questions get the same honest
|
|
83
|
-
// no-code-graph answer an un-pointed CLI session gives, never a crash.
|
|
84
|
-
|
|
91
|
+
// no-code-graph answer an un-pointed CLI session gives, never a crash. The
|
|
92
|
+
// turn engine keeps reading THIS one, so the identity-led greeting, the teach
|
|
93
|
+
// pointer and the zero module counts a page earns from an empty index all
|
|
94
|
+
// stay exactly as they were.
|
|
95
|
+
const codeGraph = parseEntities({ individuals: [], objectProperties: [] });
|
|
96
|
+
|
|
97
|
+
// What `tmct.ask()` traverses is a different graph: this session's own memory
|
|
98
|
+
// store, projected through memoryFactGraphPayload. Rebuilt on demand rather
|
|
99
|
+
// than once at open, because every teach, ingest and research turn grows the
|
|
100
|
+
// store — the same reason spider-fly rebuilds its board before each turn.
|
|
101
|
+
let memoryGraph = parseEntities({ individuals: [], objectProperties: [] });
|
|
102
|
+
async function refreshGraph() {
|
|
103
|
+
memoryGraph = parseEntities(memoryFactGraphPayload(readFactRows(await loadMemory(memoryDir))));
|
|
104
|
+
return memoryGraph;
|
|
105
|
+
}
|
|
106
|
+
|
|
85
107
|
const lexicon = loadLexicon();
|
|
86
|
-
const vocabHint = vocabExampleHint(vocabSeeded);
|
|
108
|
+
const vocabHint = vocabExampleHint(vocabSeeded, "browser");
|
|
87
109
|
const sessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
88
110
|
|
|
89
111
|
// Four-state, like the CLI: false (off), true (rescue on a miss),
|
|
@@ -97,10 +119,10 @@ export function createChatSession({ seedPayload = null, vocabSeeded = false, liv
|
|
|
97
119
|
let synthesisBudgetOn = Number.isFinite(synthesisBudget) ? synthesisBudget : 12;
|
|
98
120
|
|
|
99
121
|
const session = createTurnSession({
|
|
100
|
-
memoryDir, graph, lexicon, sessionId, vocabHint,
|
|
122
|
+
memoryDir, graph: codeGraph, lexicon, sessionId, vocabHint,
|
|
101
123
|
buildExtraOptions: () => ({
|
|
102
124
|
liveReference: liveReferenceOn, onLiveLookup,
|
|
103
|
-
|
|
125
|
+
synthesisBudget: synthesisBudgetOn,
|
|
104
126
|
}),
|
|
105
127
|
// `result.liveReference` mirrors a `/wiki on|off|supplement|always`
|
|
106
128
|
// command back into this session's own toggle state.
|
|
@@ -112,6 +134,9 @@ export function createChatSession({ seedPayload = null, vocabSeeded = false, liv
|
|
|
112
134
|
return {
|
|
113
135
|
memoryDir,
|
|
114
136
|
sessionId,
|
|
137
|
+
get graph() { return memoryGraph; },
|
|
138
|
+
codeGraph,
|
|
139
|
+
refreshGraph,
|
|
115
140
|
get liveReference() { return liveReferenceOn; },
|
|
116
141
|
/** The page's toggle seam: set the live Wikipedia mode for every later turn
|
|
117
142
|
* (the `/wiki on|off|supplement|always` command sets the same state). */
|
|
@@ -144,4 +169,25 @@ export async function researchedFactRows(memoryDir) {
|
|
|
144
169
|
.map((row) => ({ subject: row.subject, predicate: row.predicate, object: row.object }));
|
|
145
170
|
}
|
|
146
171
|
|
|
147
|
-
|
|
172
|
+
// The page reaches the engine through the one shared surface: `tmct.open()`
|
|
173
|
+
// opens this session, `tmct.turn()` runs the dock, `tmct.ask()` puts a
|
|
174
|
+
// question to the session's own graph. What stays on `tmct.page` is what has
|
|
175
|
+
// no plain-English form — the vendor/provider seams the page registers before
|
|
176
|
+
// the first turn, its IndexedDB wrapper, and the two serializers its export
|
|
177
|
+
// and paste-ingest controls run.
|
|
178
|
+
publishTmctSurface({
|
|
179
|
+
open: createChatSession,
|
|
180
|
+
// The memory projection is rebuilt first, so a direct tmct.ask() sees every
|
|
181
|
+
// fact taught, pasted or researched since the last one.
|
|
182
|
+
ask: async (request, options, session) => {
|
|
183
|
+
await session.refreshGraph();
|
|
184
|
+
return graphAsk(request, options, session);
|
|
185
|
+
},
|
|
186
|
+
plan: enginePlan,
|
|
187
|
+
page: {
|
|
188
|
+
registerWinkModel, registerReferencePackProvider, registerLiveReferenceProvider,
|
|
189
|
+
registerResearchProvider, normFactTerm, vocabExampleHint, memoryStats,
|
|
190
|
+
openPersistedStore, exportFactsJsonl, researchedFactRows,
|
|
191
|
+
splitSentences: splitSentencesPreservingPaths,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
@@ -22,6 +22,8 @@ import * as source from "../../adapters/source.mjs";
|
|
|
22
22
|
import { computeCodeExplorerData, computeCodeLedger, edgePhrase } from "../../services/code-explorer-viz.mjs";
|
|
23
23
|
import { generateCodeHints } from "../../domain/code-explorer-hints.mjs";
|
|
24
24
|
import { createTurnSession } from "./turn-session.mjs";
|
|
25
|
+
import { publishTmctSurface } from "./tmct-surface.mjs";
|
|
26
|
+
import { graphAsk, enginePlan } from "./engine-surface.mjs";
|
|
25
27
|
import { tmct_ask } from "../../tools/handlers/tmct-ask.mjs";
|
|
26
28
|
import { RELATIONS } from "../../domain/ask-vocab.mjs";
|
|
27
29
|
|
|
@@ -37,7 +39,7 @@ import { RELATIONS } from "../../domain/ask-vocab.mjs";
|
|
|
37
39
|
* payload carries the starter vocabulary. Teaches land in the same in-memory
|
|
38
40
|
* store, so a taught fact never touches disk.
|
|
39
41
|
*
|
|
40
|
-
* Returns { memoryDir, sessionId, turn }, the createChatSession shape the
|
|
42
|
+
* Returns { memoryDir, sessionId, graph, turn }, the createChatSession shape the
|
|
41
43
|
* page's chat expects.
|
|
42
44
|
*/
|
|
43
45
|
export function createCodeExplorerSession({ graphPayload, seedPayload = null, vocabSeeded = false } = {}) {
|
|
@@ -51,7 +53,7 @@ export function createCodeExplorerSession({ graphPayload, seedPayload = null, vo
|
|
|
51
53
|
// the write path recounts — teach turns must work on any seed.
|
|
52
54
|
if (seedPayload) memoryDir.payload = { ...memoryDir.payload, ...seedPayload };
|
|
53
55
|
const lexicon = loadLexicon();
|
|
54
|
-
const vocabHint = vocabExampleHint(vocabSeeded);
|
|
56
|
+
const vocabHint = vocabExampleHint(vocabSeeded, "browser");
|
|
55
57
|
const sessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
56
58
|
// A stable virtual path: the provider answers every fetch, so no file is
|
|
57
59
|
// ever read, but the code lanes still do path math (join/dirname) on it.
|
|
@@ -69,6 +71,7 @@ export function createCodeExplorerSession({ graphPayload, seedPayload = null, vo
|
|
|
69
71
|
return {
|
|
70
72
|
memoryDir,
|
|
71
73
|
sessionId,
|
|
74
|
+
graph,
|
|
72
75
|
turn: turnSession.turn,
|
|
73
76
|
};
|
|
74
77
|
}
|
|
@@ -223,17 +226,28 @@ export function askRelatedFacts(graphPayload, term) {
|
|
|
223
226
|
return { term, rows, asked, grounded };
|
|
224
227
|
}
|
|
225
228
|
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
229
|
+
// This page holds a REAL code graph, so `tmct.ask` is the engine answering
|
|
230
|
+
// over it — the same round trip askRelatedFacts already makes twice per
|
|
231
|
+
// relation kind to build the sidebar's rows.
|
|
232
|
+
//
|
|
233
|
+
// `tmct.page` keeps the re-derivation helpers a graph swapped through the
|
|
234
|
+
// desktop picker re-renders with, plus the wink and term-normalizing seams the
|
|
235
|
+
// chat shares with the ledger page. `createCodeExplorerSession` sits there
|
|
236
|
+
// too: the page keeps its own session slot, created lazily on the first turn
|
|
237
|
+
// once the general-knowledge seed has landed, rather than the one `tmct.open`
|
|
238
|
+
// would install eagerly — so it calls the factory straight from the bag.
|
|
239
|
+
publishTmctSurface({
|
|
240
|
+
open: createCodeExplorerSession,
|
|
241
|
+
ask: graphAsk,
|
|
242
|
+
plan: enginePlan,
|
|
243
|
+
page: {
|
|
244
|
+
createCodeExplorerSession,
|
|
245
|
+
askRelatedFacts,
|
|
246
|
+
computeCodeExplorerData,
|
|
247
|
+
computeCodeLedger,
|
|
248
|
+
generateCodeHints,
|
|
249
|
+
parseEntities,
|
|
250
|
+
normFactTerm,
|
|
251
|
+
registerWinkModel,
|
|
252
|
+
},
|
|
253
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// engine-surface.mjs — the standard `ask` and `plan` routes a browser page
|
|
2
|
+
// wires into publishTmctSurface (tmct-surface.mjs).
|
|
3
|
+
//
|
|
4
|
+
// Both run the SAME machinery the CLI runs, over the session's own in-memory
|
|
5
|
+
// state rather than a repo on disk: `graphAsk` dispatches `tmct_ask` through
|
|
6
|
+
// the tool layer and hands back the structured envelope beside the prose
|
|
7
|
+
// (dispatchToolStructured's `{ content, data }`); `enginePlan` builds the
|
|
8
|
+
// capability planner's context from whatever the session holds and runs one
|
|
9
|
+
// request through it.
|
|
10
|
+
//
|
|
11
|
+
// Kept out of tmct-surface.mjs on purpose. That module is pure wiring and
|
|
12
|
+
// imports nothing, so memory-ask-browser-entry.mjs — the one bundle that is
|
|
13
|
+
// committed and published, and is deliberately small — can publish the same
|
|
14
|
+
// `globalThis.tmct` without pulling the tool layer and the router into its
|
|
15
|
+
// bundle.
|
|
16
|
+
import { dispatchToolStructured } from "../../tools/server.mjs";
|
|
17
|
+
import { capabilityPlanDeps, noCodeGraph } from "../../services/chat.mjs";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* One plain-English question, answered against the graph this session holds,
|
|
21
|
+
* through the same `tmct_ask` tool the CLI dispatches. Returns
|
|
22
|
+
* `{ answer, data, miss }` — `data` is ask()'s own envelope (the parse, the
|
|
23
|
+
* matches it found, the traversal it walked), which is what a panel renders
|
|
24
|
+
* rows from and what makes an answer auditable.
|
|
25
|
+
*
|
|
26
|
+
* A session whose graph is empty answers with an honest miss. That is the real
|
|
27
|
+
* state of a memory-only page: its rows are facts in a store, and ask() reads
|
|
28
|
+
* a graph. spider-fly is the page that closed that gap for its own world, by
|
|
29
|
+
* projecting its live board into a graph (worldRelationGraphPayload) before
|
|
30
|
+
* every turn — the same route is open to any page whose rows have a shape
|
|
31
|
+
* worth asking over.
|
|
32
|
+
*/
|
|
33
|
+
export async function graphAsk(request, options, session) {
|
|
34
|
+
const { content, data } = await dispatchToolStructured("tmct_ask", { query: request }, {
|
|
35
|
+
graph: session.graph,
|
|
36
|
+
memoryBackend: session.memoryDir,
|
|
37
|
+
});
|
|
38
|
+
return { answer: content, data, miss: Boolean(data?.miss) };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* One compound or maintenance-goal request, planned and executed over whatever
|
|
43
|
+
* this session holds — the same capability router `/plan` reaches, with the
|
|
44
|
+
* same memory-only mode a page with no code graph needs (a code-graph
|
|
45
|
+
* capability there refuses by naming the graph it hasn't got).
|
|
46
|
+
*
|
|
47
|
+
* Returns the planner's own result: `{ refused, why, driver, calls, composed,
|
|
48
|
+
* observed }`. No prose is composed here — a page that wants the written
|
|
49
|
+
* answer asks for it in words through `tmct.turn("/plan …")`, which is the
|
|
50
|
+
* same split `dispatchTool` and `dispatchToolStructured` already draw.
|
|
51
|
+
*/
|
|
52
|
+
export async function enginePlan(request, options, session) {
|
|
53
|
+
const graph = session.graph && !noCodeGraph(session.graph) ? session.graph : null;
|
|
54
|
+
const memoryDir = session.memoryDir ?? null;
|
|
55
|
+
if (!graph && !memoryDir) {
|
|
56
|
+
return {
|
|
57
|
+
refused: true,
|
|
58
|
+
why: "nothing to plan over — this session holds neither a code graph nor a memory store",
|
|
59
|
+
driver: null, calls: [], composed: null, observed: null,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const { buildCapabilityPlanCtx, runCapabilityPlan, declaredCapabilityNames } =
|
|
63
|
+
await import("../../domain/router/drive.mjs");
|
|
64
|
+
const ctx = await buildCapabilityPlanCtx({
|
|
65
|
+
...capabilityPlanDeps(), config: null, source: null, graph, memoryDir,
|
|
66
|
+
});
|
|
67
|
+
try {
|
|
68
|
+
const result = await runCapabilityPlan(request, declaredCapabilityNames(), ctx);
|
|
69
|
+
return {
|
|
70
|
+
refused: Boolean(result.refused),
|
|
71
|
+
why: Array.isArray(result.why) ? result.why.join("; ") : (result.why ?? null),
|
|
72
|
+
driver: result.driver ?? null,
|
|
73
|
+
calls: result.calls ?? [],
|
|
74
|
+
composed: result.composed ?? null,
|
|
75
|
+
observed: result.observed ?? null,
|
|
76
|
+
};
|
|
77
|
+
} finally {
|
|
78
|
+
// The taught registrations are per-ctx: dispose them so the next call
|
|
79
|
+
// re-reads the store instead of meeting a stale name collision.
|
|
80
|
+
for (const dispose of ctx.disposers || []) dispose();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -29,15 +29,24 @@ import { splitSentencesPreservingPaths, stripCitationResidue } from "../../servi
|
|
|
29
29
|
import { clauseCandidates, optimisticTriples } from "../../services/extract-facts.mjs";
|
|
30
30
|
import { touchedFactRows } from "../../domain/memory/touched-facts.mjs";
|
|
31
31
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
32
|
+
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
33
|
+
import { ingestFactGraphPayload, ingestedFactRows } from "../../domain/ingest-facts.mjs";
|
|
32
34
|
import { registerWinkModel, winkInstance } from "../../adapters/wink-model.mjs";
|
|
33
35
|
import { memoryStats, exportFactsJsonl } from "./memory-stats.mjs";
|
|
34
36
|
import { openPersistedStore } from "./idb-persist.mjs";
|
|
37
|
+
import { createTurnSession } from "./turn-session.mjs";
|
|
38
|
+
import { publishTmctSurface } from "./tmct-surface.mjs";
|
|
39
|
+
import { graphAsk, enginePlan } from "./engine-surface.mjs";
|
|
35
40
|
|
|
36
41
|
// The pronoun subjects a bounded carry substitutes with the last unique
|
|
37
42
|
// grounded subject in the SAME paragraph. Reset at every blank line, so a
|
|
38
43
|
// fresh paragraph never resolves against a stale antecedent.
|
|
39
44
|
const PRONOUN_LEAD_RE = /^(?:they|it|these|those|this)\b\s*/i;
|
|
40
45
|
|
|
46
|
+
// The turn kinds that move the fact store, so the projected graph a later
|
|
47
|
+
// question traverses has to be rebuilt before it answers.
|
|
48
|
+
const STORE_WRITING_TURNS = new Set(["assert", "retract"]);
|
|
49
|
+
|
|
41
50
|
/**
|
|
42
51
|
* The single recognizer seam. Splits `text` into paragraphs (blank-line
|
|
43
52
|
* separated, so the pronoun carry never bridges a topic break) and each
|
|
@@ -170,29 +179,90 @@ export async function groundTextToFacts(text, { memoryDir, sessionId, lexicon, v
|
|
|
170
179
|
* the page can then export. `seedPayload` (optional) pre-loads a graph the
|
|
171
180
|
* recognizer can recall and link against.
|
|
172
181
|
*
|
|
173
|
-
* Returns { memoryDir, sessionId,
|
|
174
|
-
* optimistic })` is the
|
|
175
|
-
* against this session's store and returns
|
|
176
|
-
* skipped, facts } summary.
|
|
182
|
+
* Returns { memoryDir, sessionId, graph, refreshGraph, askableClasses, ingest,
|
|
183
|
+
* turn }. `ingest(text, { onFact, optimistic })` is the call the ingest panes
|
|
184
|
+
* make; it drives groundTextToFacts against this session's store and returns
|
|
185
|
+
* its { sentences, recognized, skipped, facts } summary. `turn(line)` is the
|
|
186
|
+
* ask dock's own entry point — the FULL chat turn engine (the exact dispatch
|
|
187
|
+
* the CLI runs) over the same store, so a question it can't ground gets the
|
|
188
|
+
* same refusal the CLI gives.
|
|
189
|
+
*
|
|
190
|
+
* `graph` is what a question actually traverses: the ingested rows projected
|
|
191
|
+
* through ingest-facts.mjs, rebuilt by `refreshGraph()` whenever the store has
|
|
192
|
+
* moved since the last one. The rebuild is gated on a dirty flag rather than
|
|
193
|
+
* run per call because a seeded store holds tens of thousands of rows, and
|
|
194
|
+
* re-reading all of them to answer a second question about the same two
|
|
195
|
+
* sentences is the one cost this page has to avoid.
|
|
177
196
|
*/
|
|
178
197
|
export function createIngestSession({ seedPayload = null, vocabSeeded = false } = {}) {
|
|
179
198
|
const memoryDir = createInMemoryStore();
|
|
180
199
|
applySeedPayload(memoryDir, seedPayload);
|
|
181
200
|
|
|
182
201
|
const lexicon = loadLexicon();
|
|
183
|
-
const vocabHint = vocabExampleHint(vocabSeeded);
|
|
202
|
+
const vocabHint = vocabExampleHint(vocabSeeded, "browser");
|
|
184
203
|
const sessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
185
204
|
|
|
205
|
+
let graph = parseEntities({ individuals: [], objectProperties: [] });
|
|
206
|
+
let storeMovedSinceGraph = true;
|
|
207
|
+
async function refreshGraph() {
|
|
208
|
+
if (!storeMovedSinceGraph) return graph;
|
|
209
|
+
const rows = ingestedFactRows(readFactRows(await loadMemory(memoryDir)));
|
|
210
|
+
graph = parseEntities(ingestFactGraphPayload(rows));
|
|
211
|
+
storeMovedSinceGraph = false;
|
|
212
|
+
return graph;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// An empty graph is not the same as no graph to runTurn: a non-null one
|
|
216
|
+
// makes it read an ordinary "the number of X" phrase as a graph count query
|
|
217
|
+
// instead of running the teach cascade, so before anything is ingested the
|
|
218
|
+
// dock keeps the plain conversational reading.
|
|
219
|
+
const graphForTurn = () => (graph.individuals.length ? graph : null);
|
|
220
|
+
|
|
221
|
+
const turnSession = createTurnSession({
|
|
222
|
+
memoryDir, lexicon, sessionId, vocabHint,
|
|
223
|
+
buildExtraOptions: () => ({ graph: graphForTurn() }),
|
|
224
|
+
captureExtraState: (result) => {
|
|
225
|
+
if (STORE_WRITING_TURNS.has(result?.record?.via) && !result.record.miss) storeMovedSinceGraph = true;
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
186
229
|
return {
|
|
187
230
|
memoryDir,
|
|
188
231
|
sessionId,
|
|
189
|
-
|
|
190
|
-
|
|
232
|
+
get graph() { return graph; },
|
|
233
|
+
refreshGraph,
|
|
234
|
+
/** The class nouns this graph can actually list, for a page building its
|
|
235
|
+
* own "try asking" hint out of real data rather than invented examples. */
|
|
236
|
+
askableClasses() {
|
|
237
|
+
return [...new Set(graph.individuals.map((i) => i.class).filter(Boolean))].sort();
|
|
238
|
+
},
|
|
239
|
+
async ingest(text, { onFact = null, optimistic = false } = {}) {
|
|
240
|
+
const summary = await groundTextToFacts(text, { memoryDir, sessionId, lexicon, vocabHint, onFact, optimistic });
|
|
241
|
+
if (summary.recognized) storeMovedSinceGraph = true;
|
|
242
|
+
return summary;
|
|
243
|
+
},
|
|
244
|
+
async turn(line) {
|
|
245
|
+
await refreshGraph();
|
|
246
|
+
return turnSession.turn(line);
|
|
191
247
|
},
|
|
192
248
|
};
|
|
193
249
|
}
|
|
194
250
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
251
|
+
// The page's own two calls are `tmct.session.ingest(text)` and, from the ask
|
|
252
|
+
// dock, `tmct.turn(line)`. `tmct.ask` puts one question straight to the graph
|
|
253
|
+
// this session projects from what it has ingested, rebuilding it first so a
|
|
254
|
+
// fact grounded a moment ago is already visible. `tmct.page` keeps the
|
|
255
|
+
// recognizer entry point the page also drives directly, the wink seam, the
|
|
256
|
+
// stats fold behind its counters, its IndexedDB wrapper and its JSONL export.
|
|
257
|
+
publishTmctSurface({
|
|
258
|
+
open: createIngestSession,
|
|
259
|
+
ask: async (request, options, session) => {
|
|
260
|
+
await session.refreshGraph();
|
|
261
|
+
return graphAsk(request, options, session);
|
|
262
|
+
},
|
|
263
|
+
plan: enginePlan,
|
|
264
|
+
page: {
|
|
265
|
+
groundTextToFacts, exportFactsJsonl, registerWinkModel, normFactTerm,
|
|
266
|
+
memoryStats, openPersistedStore,
|
|
267
|
+
},
|
|
268
|
+
});
|
|
@@ -17,9 +17,10 @@
|
|
|
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
19
|
import { vocabExampleHint } from "../../services/chat.mjs";
|
|
20
|
-
import { createInMemoryStore, normFactTerm, applySeedPayload } from "../../adapters/memory/core.mjs";
|
|
20
|
+
import { createInMemoryStore, normFactTerm, applySeedPayload, loadMemory, readFactRows } from "../../adapters/memory/core.mjs";
|
|
21
21
|
import { splitSentencesPreservingPaths } from "../../services/sentences.mjs";
|
|
22
22
|
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
23
|
+
import { memoryFactGraphPayload } from "../../domain/memory-facts.mjs";
|
|
23
24
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
24
25
|
import { registerWinkModel } from "../../adapters/wink-model.mjs";
|
|
25
26
|
import { registerResearchProvider } from "../../adapters/corpus/wikipedia-live.mjs";
|
|
@@ -27,6 +28,8 @@ import { computeLedgerDataFromPayload } from "../../services/ledger-viz.mjs";
|
|
|
27
28
|
import { digestTermFromPayloadBrowser } from "./digest-client.mjs";
|
|
28
29
|
import { createTurnSession } from "./turn-session.mjs";
|
|
29
30
|
import { exportFactsJsonl } from "./memory-stats.mjs";
|
|
31
|
+
import { publishTmctSurface } from "./tmct-surface.mjs";
|
|
32
|
+
import { graphAsk, enginePlan } from "./engine-surface.mjs";
|
|
30
33
|
|
|
31
34
|
/**
|
|
32
35
|
* A browser ledger-dock session over the real turn engine — createChatSession's
|
|
@@ -35,7 +38,12 @@ import { exportFactsJsonl } from "./memory-stats.mjs";
|
|
|
35
38
|
* a fact taught through the dock extends the SAME graph the page renders
|
|
36
39
|
* from, never a disconnected one.
|
|
37
40
|
*
|
|
38
|
-
* Returns { memoryDir, sessionId,
|
|
41
|
+
* Returns { memoryDir, sessionId, graph, codeGraph, refreshGraph, turn },
|
|
42
|
+
* identical to createChatSession — including the two-graph split: `codeGraph`
|
|
43
|
+
* is the known-empty index the turn engine reads, and `graph` is the ledger's
|
|
44
|
+
* own store projected for `ask()`, so a question about the facts on the page
|
|
45
|
+
* traverses those facts while a code-structure question still refuses honestly.
|
|
46
|
+
*
|
|
39
47
|
* ledger-viz.mjs's own inline script calls computeLedgerDataFromPayload
|
|
40
48
|
* (re-exported below) on `memoryDir.payload` after a turn whose record shows
|
|
41
49
|
* a successful write (`via: "assert"` or `via: "retract"`, `miss: false`) to
|
|
@@ -46,20 +54,45 @@ export function createLedgerSession({ seedPayload = null, vocabSeeded = false }
|
|
|
46
54
|
const memoryDir = createInMemoryStore();
|
|
47
55
|
applySeedPayload(memoryDir, seedPayload);
|
|
48
56
|
|
|
49
|
-
const
|
|
57
|
+
const codeGraph = parseEntities({ individuals: [], objectProperties: [] });
|
|
58
|
+
|
|
59
|
+
let memoryGraph = parseEntities({ individuals: [], objectProperties: [] });
|
|
60
|
+
async function refreshGraph() {
|
|
61
|
+
memoryGraph = parseEntities(memoryFactGraphPayload(readFactRows(await loadMemory(memoryDir))));
|
|
62
|
+
return memoryGraph;
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
const lexicon = loadLexicon();
|
|
51
|
-
const vocabHint = vocabExampleHint(vocabSeeded);
|
|
66
|
+
const vocabHint = vocabExampleHint(vocabSeeded, "browser");
|
|
52
67
|
const sessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
53
68
|
|
|
54
|
-
const session = createTurnSession({ memoryDir, graph, lexicon, sessionId, vocabHint });
|
|
55
|
-
return {
|
|
69
|
+
const session = createTurnSession({ memoryDir, graph: codeGraph, lexicon, sessionId, vocabHint });
|
|
70
|
+
return {
|
|
71
|
+
memoryDir,
|
|
72
|
+
sessionId,
|
|
73
|
+
get graph() { return memoryGraph; },
|
|
74
|
+
codeGraph,
|
|
75
|
+
refreshGraph,
|
|
76
|
+
turn: session.turn,
|
|
77
|
+
};
|
|
56
78
|
}
|
|
57
79
|
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
80
|
+
// `tmct.page` keeps what the page's own script renders with and the engine has
|
|
81
|
+
// no plain-English form for: the payload-to-rows/terms/edges/stats derivation
|
|
82
|
+
// the ledger view re-mounts from after a successful teach, the digest reader,
|
|
83
|
+
// the wink and research seams, and the two serializers behind the dock's
|
|
84
|
+
// paste-ingest and JSONL export.
|
|
85
|
+
publishTmctSurface({
|
|
86
|
+
open: createLedgerSession,
|
|
87
|
+
// The memory projection is rebuilt first, so a direct tmct.ask() sees every
|
|
88
|
+
// fact the dock or the ingest panel has taught since the last one.
|
|
89
|
+
ask: async (request, options, session) => {
|
|
90
|
+
await session.refreshGraph();
|
|
91
|
+
return graphAsk(request, options, session);
|
|
92
|
+
},
|
|
93
|
+
plan: enginePlan,
|
|
94
|
+
page: {
|
|
95
|
+
computeLedgerDataFromPayload, normFactTerm, registerWinkModel, registerResearchProvider,
|
|
96
|
+
splitSentences: splitSentencesPreservingPaths, exportFactsJsonl, digestTermFromPayloadBrowser,
|
|
97
|
+
},
|
|
98
|
+
});
|