@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
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
createInMemoryStore, appendFacts, appendRule, loadMemory, readFactRows, removeFacts,
|
|
33
33
|
} from "../../adapters/memory/core.mjs";
|
|
34
34
|
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
35
|
+
import { memoryFactGraphPayload } from "../../domain/memory-facts.mjs";
|
|
35
36
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
36
37
|
import {
|
|
37
38
|
foldWorldState, worldActionRows, worldDigestRows, roomAffordances,
|
|
@@ -48,6 +49,8 @@ import { worldProvenanceTag } from "../../domain/worlds-pack.mjs";
|
|
|
48
49
|
import { resolveSpriteForClass, SPRITE_REGISTRY, classAncestorChain } from "../../domain/sprite-map.mjs";
|
|
49
50
|
import { resolveSpriteAsset } from "../../domain/sprite-templates.mjs";
|
|
50
51
|
import { createTurnSession } from "./turn-session.mjs";
|
|
52
|
+
import { publishTmctSurface } from "./tmct-surface.mjs";
|
|
53
|
+
import { graphAsk, enginePlan } from "./engine-surface.mjs";
|
|
51
54
|
|
|
52
55
|
/** A live, shared mud world several characters can each act in. `worldPayload`
|
|
53
56
|
* is `{ name, facts, rules, opening }` — the same shape adventure-browser-
|
|
@@ -60,7 +63,10 @@ import { createTurnSession } from "./turn-session.mjs";
|
|
|
60
63
|
* are minted, fewer leaves the ones nobody is playing out of the world
|
|
61
64
|
* altogether.
|
|
62
65
|
*
|
|
63
|
-
* Returns `{ memoryDir, windows, snapshot }`.
|
|
66
|
+
* Returns `{ memoryDir, codeGraph, graph, refreshGraph, windows, snapshot }`.
|
|
67
|
+
* `codeGraph` is the known-empty index the turn engine and a scripted
|
|
68
|
+
* autoplayTick read; `graph` is this world's own memory store projected for
|
|
69
|
+
* `ask()` (refreshGraph rebuilds it on demand). `windows` is a plain object
|
|
64
70
|
* keyed by character id, each value `{ character, turn, autoplayTick,
|
|
65
71
|
* visitedRoomIds, turnsTaken, isOutOfPlay, outOfPlayReason }`. `snapshot()` is
|
|
66
72
|
* the one OMNISCIENT read this module exposes — the central world map's own
|
|
@@ -87,7 +93,11 @@ export async function createMudSession(worldPayload, { characters = [], epoch =
|
|
|
87
93
|
// way rather than through the "play <world>" opener (which needs a
|
|
88
94
|
// shipped "player" individual mud-garden deliberately has none of).
|
|
89
95
|
const planHolder = { state: { adventure: { world: worldPayload.name } } };
|
|
90
|
-
|
|
96
|
+
// A known-empty code graph: code-structure questions get the same honest
|
|
97
|
+
// no-code-graph answer an un-pointed CLI session gives, never a crash — the
|
|
98
|
+
// turn engine (and a scripted autoplayTick) keep reading THIS one for their
|
|
99
|
+
// own in-turn code lane.
|
|
100
|
+
const codeGraph = parseEntities({ individuals: [], objectProperties: [] });
|
|
91
101
|
// The world's own minted ids ("groundhog-1", "carrot-2") are declared as
|
|
92
102
|
// vocabulary inside the adventure lane itself, for the length of one world
|
|
93
103
|
// command — see adventure.mjs's own worldLexicon. This page hands over the
|
|
@@ -99,6 +109,17 @@ export async function createMudSession(worldPayload, { characters = [], epoch =
|
|
|
99
109
|
return { rows, state: foldWorldState(worldActionRows(rows)) };
|
|
100
110
|
}
|
|
101
111
|
|
|
112
|
+
// What `tmct.ask()` traverses is a different graph: this shared world's own
|
|
113
|
+
// memory store, projected through memoryFactGraphPayload. Rebuilt on demand
|
|
114
|
+
// rather than once at open, because every character's turn grows the store.
|
|
115
|
+
// Built off readWorld()'s own rows rather than a second readFactRows call.
|
|
116
|
+
let memoryGraph = parseEntities({ individuals: [], objectProperties: [] });
|
|
117
|
+
async function refreshGraph() {
|
|
118
|
+
const { rows } = await readWorld();
|
|
119
|
+
memoryGraph = parseEntities(memoryFactGraphPayload(rows));
|
|
120
|
+
return memoryGraph;
|
|
121
|
+
}
|
|
122
|
+
|
|
102
123
|
async function roomOf(character) {
|
|
103
124
|
const { state } = await readWorld();
|
|
104
125
|
return state.placements.get(character)?.object ?? null;
|
|
@@ -124,10 +145,10 @@ export async function createMudSession(worldPayload, { characters = [], epoch =
|
|
|
124
145
|
if (startRoom) visitedRoomIds.add(startRoom);
|
|
125
146
|
|
|
126
147
|
const turnSession = createTurnSession({
|
|
127
|
-
memoryDir, graph, lexicon, sessionId: character,
|
|
148
|
+
memoryDir, graph: codeGraph, lexicon, sessionId: character,
|
|
128
149
|
vocabHint: 'Try a world command ("dig north", "eat the carrot-1"), or ask "what food do you know about".',
|
|
129
150
|
buildExtraOptions: () => ({
|
|
130
|
-
|
|
151
|
+
actingSubject: character, planState: planHolder.state,
|
|
131
152
|
}),
|
|
132
153
|
captureExtraState: async (result, state) => {
|
|
133
154
|
if ("planState" in result) planHolder.state = state.planState;
|
|
@@ -156,7 +177,7 @@ export async function createMudSession(worldPayload, { characters = [], epoch =
|
|
|
156
177
|
* note }` unmodified, so the caller can render the speech-bubble/
|
|
157
178
|
* dig-flourish triggers straight off `actions`. */
|
|
158
179
|
async autoplayTick(k) {
|
|
159
|
-
const result = await runMudTurn(character, { world: worldPayload.name, memoryDir, env: {}, graph, k });
|
|
180
|
+
const result = await runMudTurn(character, { world: worldPayload.name, memoryDir, env: {}, graph: codeGraph, k });
|
|
160
181
|
// A turn that ended in starvation still happened, and still counts —
|
|
161
182
|
// only a DECLINED turn (no room to act in) leaves the tally alone.
|
|
162
183
|
if (result.room) turnsTaken += 1;
|
|
@@ -270,7 +291,13 @@ export async function createMudSession(worldPayload, { characters = [], epoch =
|
|
|
270
291
|
return claims.length;
|
|
271
292
|
}
|
|
272
293
|
|
|
273
|
-
return {
|
|
294
|
+
return {
|
|
295
|
+
memoryDir,
|
|
296
|
+
codeGraph,
|
|
297
|
+
get graph() { return memoryGraph; },
|
|
298
|
+
refreshGraph,
|
|
299
|
+
windows, snapshot, applyEdit, wave, claimCharacters,
|
|
300
|
+
};
|
|
274
301
|
}
|
|
275
302
|
|
|
276
303
|
/** `count` entries drawn at random from `roster`, in random order, without
|
|
@@ -351,22 +378,47 @@ export function worldFactsForCast(facts, characters) {
|
|
|
351
378
|
return rows.filter((f) => !uncast.has(f.subject)).concat(mintedCharacterFacts(rows, characters));
|
|
352
379
|
}
|
|
353
380
|
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
381
|
+
// Several characters, one world, so a line has to say who is speaking:
|
|
382
|
+
// `tmct.turn(line, { as: "mole-1" })` routes to that character's own window,
|
|
383
|
+
// which owns its private focus/last and its own fog of war. Everything else a
|
|
384
|
+
// window does — a scripted autoplayTick, its visited rooms, whether it is
|
|
385
|
+
// still in play — stays on `tmct.session.windows[id]`, because those are
|
|
386
|
+
// per-character state rather than questions anyone could ask in words.
|
|
387
|
+
//
|
|
388
|
+
// `tmct.page` keeps the sprite resolution and the digest/affordance/knowledge
|
|
389
|
+
// readers the room view and chat pills render from, the roster helpers that
|
|
390
|
+
// decide which animals this visit is played with, the two predicate lists the
|
|
391
|
+
// P2P layer checks against, and the SKOS neighbourhood behind the edit mode's
|
|
392
|
+
// cursor pills — none of which is `.toString()`-splice-safe.
|
|
393
|
+
publishTmctSurface({
|
|
394
|
+
open: createMudSession,
|
|
395
|
+
turn: (line, options, session) => {
|
|
396
|
+
const character = options?.as;
|
|
397
|
+
const characterWindow = character ? session.windows[character] : null;
|
|
398
|
+
if (!characterWindow) {
|
|
399
|
+
return {
|
|
400
|
+
answer: `say which character is speaking — tmct.turn(line, { as: "${Object.keys(session.windows)[0] || "mole-1"}" })`,
|
|
401
|
+
end: false, record: null, plan: null, research: null,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
return characterWindow.turn(line);
|
|
405
|
+
},
|
|
406
|
+
// The memory projection is rebuilt first, so a direct tmct.ask() sees every
|
|
407
|
+
// fact any character's turn has written into this shared world since the
|
|
408
|
+
// last one.
|
|
409
|
+
ask: async (request, options, session) => {
|
|
410
|
+
await session.refreshGraph();
|
|
411
|
+
return graphAsk(request, options, session);
|
|
412
|
+
},
|
|
413
|
+
plan: enginePlan,
|
|
414
|
+
page: {
|
|
415
|
+
pickMudRoster, expandMudRoster, mintedCharacterFacts, worldFactsForCast,
|
|
416
|
+
resolveSpriteForClass, SPRITE_REGISTRY, classAncestorChain, resolveSpriteAsset,
|
|
417
|
+
foldWorldState, worldActionRows, worldDigestRows, roomAffordances,
|
|
418
|
+
personKnowledgeLines, personKnownFoodLines,
|
|
419
|
+
diggableDirections, castInRoom, displayNameOf, isOutOfPlay, outOfPlayReasonOf, outOfPlayPhrase,
|
|
420
|
+
roomKindOf,
|
|
421
|
+
isMudStatePredicate, P2P_PREDICATES,
|
|
422
|
+
relatedForTerm,
|
|
423
|
+
},
|
|
424
|
+
});
|
|
@@ -26,7 +26,7 @@ export {
|
|
|
26
26
|
ROOM_FAILED,
|
|
27
27
|
} from "../../services/p2p-room.mjs";
|
|
28
28
|
export { createTransport } from "../../adapters/p2p/webrtc-transport.mjs";
|
|
29
|
-
export { generatePeerId, generateWorldId, generateDisplayName } from "../../domain/p2p/peer-id.mjs";
|
|
29
|
+
export { generatePeerId, generateWorldId, generateDisplayName, generateNodeId } from "../../domain/p2p/peer-id.mjs";
|
|
30
30
|
export { chatSyncableFacts, mudSyncableFacts } from "../../domain/p2p/sync-filter.mjs";
|
|
31
31
|
export { decodeInviteBlob, encodeInviteBlob } from "../../domain/p2p/wire.mjs";
|
|
32
32
|
export {
|
|
@@ -29,12 +29,13 @@ import { parseEntities } from "../../domain/codegraph.mjs";
|
|
|
29
29
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
30
30
|
import { DEFAULT_GAME_CONFIG } from "../../domain/game-config.mjs";
|
|
31
31
|
import { hanoiLessonSentences } from "../../domain/hanoi-lesson.mjs";
|
|
32
|
+
import { hanoiBoardRows, hanoiBoardGraphPayload } from "../../domain/hanoi-board.mjs";
|
|
32
33
|
import { computeBlocksLayout, planToPageData, renderInputsFromPlan } from "../../services/plan-viz.mjs";
|
|
33
34
|
import { planToPddl } from "../../services/plan-pddl.mjs";
|
|
34
35
|
import { createTurnSession } from "./turn-session.mjs";
|
|
35
36
|
// Re-exported so the page can register a CDN-loaded wink-nlp pair before the
|
|
36
37
|
// first teach, the same seam chat-browser-entry.mjs exposes as
|
|
37
|
-
//
|
|
38
|
+
// tmct.page.registerWinkModel — see wink-model.mjs's own header. The hanoi
|
|
38
39
|
// lesson's own "moving a disk onto a target makes the disk rest on the
|
|
39
40
|
// target" sentence needs a REAL lemmatiser (verbLemma reduces "moving" to
|
|
40
41
|
// "move" to match the taught "move onto" action family) — without it, that
|
|
@@ -44,10 +45,12 @@ import { createTurnSession } from "./turn-session.mjs";
|
|
|
44
45
|
// model because their own gameplay never asks a taught rule to reduce a
|
|
45
46
|
// verb; the hanoi lesson is the first live session here that does.
|
|
46
47
|
import { registerWinkModel } from "../../adapters/wink-model.mjs";
|
|
48
|
+
import { publishTmctSurface } from "./tmct-surface.mjs";
|
|
49
|
+
import { graphAsk, enginePlan } from "./engine-surface.mjs";
|
|
47
50
|
|
|
48
51
|
/** A live in-memory towers-of-hanoi session this page's live controls AND
|
|
49
|
-
* chat dock can both drive. Returns `{ memoryDir, sessionId,
|
|
50
|
-
* maxDepth, plan, turn }`. `plan` is the puzzle's freshly solved plan (the
|
|
52
|
+
* chat dock can both drive. Returns `{ memoryDir, sessionId, graph,
|
|
53
|
+
* diskCount, maxDepth, plan, turn }`. `plan` is the puzzle's freshly solved plan (the
|
|
51
54
|
* same shape chat.mjs's planLaneAnswer returns, enriched with
|
|
52
55
|
* `becauseText` — see `turn()` below), or null when `maxDepth` was too low
|
|
53
56
|
* to find one (an honest miss, not an error: `turn()`'s own answer text
|
|
@@ -84,13 +87,48 @@ export async function createPlanSession({ diskCount = 3, maxDepth = DEFAULT_GAME
|
|
|
84
87
|
if (r.plan) plan = r.plan;
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
|
|
90
|
+
// The board `tmct.ask()` traverses. The plan lane's own states are rows, not
|
|
91
|
+
// a graph, so an ask over this session used to meet an empty one and miss
|
|
92
|
+
// every question about the puzzle in front of the visitor. hanoi-board.mjs
|
|
93
|
+
// projects one position into `{individuals, objectProperties}`; the page
|
|
94
|
+
// calls `showBoard` whenever it mounts a fresh plan or the transport moves
|
|
95
|
+
// the step, so what ask() reads is what the board shows.
|
|
96
|
+
let boardPlan = plan;
|
|
97
|
+
let boardStep = 0;
|
|
98
|
+
let boardGraph = null;
|
|
99
|
+
function showBoard({ plan: nextPlan = boardPlan, step = boardStep } = {}) {
|
|
100
|
+
boardPlan = nextPlan;
|
|
101
|
+
boardStep = Math.max(0, Math.floor(Number(step) || 0));
|
|
102
|
+
boardGraph = parseEntities(hanoiBoardGraphPayload(hanoiBoardRows({ plan: boardPlan, step: boardStep })));
|
|
103
|
+
return boardGraph;
|
|
104
|
+
}
|
|
105
|
+
showBoard();
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
memoryDir, sessionId, graph, diskCount, maxDepth, plan, turn: session.turn,
|
|
109
|
+
showBoard,
|
|
110
|
+
get boardGraph() { return boardGraph; },
|
|
111
|
+
get boardStep() { return boardStep; },
|
|
112
|
+
};
|
|
88
113
|
}
|
|
89
114
|
|
|
90
|
-
//
|
|
91
|
-
// script
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
115
|
+
// `tmct.page` keeps the board layout and the PDDL/OWL-RDF formatting the
|
|
116
|
+
// page's own script draws with, plus the wink seam the hanoi lesson needs
|
|
117
|
+
// registered before its first teach. Note that `tmct.plan(...)` here is the
|
|
118
|
+
// CAPABILITY planner, not the puzzle solver: a typed "solve it" is a
|
|
119
|
+
// conversational turn like any other, so the page reaches the hanoi plan
|
|
120
|
+
// through `tmct.turn("solve it", { maxDepth })` and reads `.plan` off it.
|
|
121
|
+
//
|
|
122
|
+
// `tmct.ask(q, { step })` answers over the projected BOARD rather than the
|
|
123
|
+
// session's own (empty) code graph, so a question about the puzzle is grounded
|
|
124
|
+
// in the position on screen. Passing `step` moves the board first, which is
|
|
125
|
+
// how the page keeps the two in step while the transport scrubs.
|
|
126
|
+
publishTmctSurface({
|
|
127
|
+
open: createPlanSession,
|
|
128
|
+
ask: (request, options, session) => {
|
|
129
|
+
if (options?.step != null) session.showBoard({ step: options.step });
|
|
130
|
+
return graphAsk(request, options, { graph: session.boardGraph, memoryDir: session.memoryDir });
|
|
131
|
+
},
|
|
132
|
+
plan: enginePlan,
|
|
133
|
+
page: { computeBlocksLayout, planToPageData, renderInputsFromPlan, planToPddl, registerWinkModel },
|
|
134
|
+
});
|
|
@@ -39,15 +39,10 @@ import { groundTextToFacts } from "./ingest-browser-entry.mjs";
|
|
|
39
39
|
import { openPersistedStore } from "./idb-persist.mjs";
|
|
40
40
|
import { digestTermFromPayloadBrowser } from "./digest-client.mjs";
|
|
41
41
|
import { createTurnSession } from "./turn-session.mjs";
|
|
42
|
+
import { publishTmctSurface } from "./tmct-surface.mjs";
|
|
43
|
+
import { enginePlan } from "./engine-surface.mjs";
|
|
42
44
|
import { exportFactsJsonl } from "./memory-stats.mjs";
|
|
43
45
|
|
|
44
|
-
// The Fact individual's first-write-wins timestamp, read straight off the
|
|
45
|
-
// stored attribute (mgx:createdAt) so a "recently learned" ordering never
|
|
46
|
-
// has to re-parse a provenance tag — the research lane's own tags carry a
|
|
47
|
-
// depth, not a timestamp, so the attribute is the one field every source
|
|
48
|
-
// shares.
|
|
49
|
-
const CREATED_AT_ATTR = "mgx:createdAt";
|
|
50
|
-
|
|
51
46
|
/**
|
|
52
47
|
* The source key ONE provenance tag folds to, for the page's per-source
|
|
53
48
|
* checkboxes/history. `sessionIds` tells a teach tag apart: a teach:chat tag
|
|
@@ -100,18 +95,17 @@ const clonePayload = (payload) => {
|
|
|
100
95
|
try { return structuredClone(payload); } catch { return JSON.parse(JSON.stringify(payload)); }
|
|
101
96
|
};
|
|
102
97
|
|
|
103
|
-
/** Fact rows plus the createdAt
|
|
104
|
-
*
|
|
105
|
-
*
|
|
98
|
+
/** Fact rows plus the createdAt readFactRows keeps per assertion rather than per
|
|
99
|
+
* row — the "recently learned" panels want the timestamp, the ask filter wants
|
|
100
|
+
* the id, both want the provenance. A triple asserted by several sources was
|
|
101
|
+
* first learned when the EARLIEST of them said it, which is the moment those
|
|
102
|
+
* panels are ordering by. */
|
|
106
103
|
async function factRowsWithCreatedAt(memoryDir) {
|
|
107
104
|
const memory = await loadMemory(memoryDir);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
createdById.set(ind.id, at);
|
|
113
|
-
}
|
|
114
|
-
return readFactRows(memory).map((row) => ({ ...row, createdAt: createdById.get(row.id) || "" }));
|
|
105
|
+
return readFactRows(memory).map((row) => {
|
|
106
|
+
const stamps = (row.assertions || []).map((a) => a.createdAt).filter(Boolean).sort();
|
|
107
|
+
return { ...row, createdAt: stamps[0] || "" };
|
|
108
|
+
});
|
|
115
109
|
}
|
|
116
110
|
|
|
117
111
|
const SOURCE_ORDER = ["taught", "ingest", "research"];
|
|
@@ -210,7 +204,7 @@ export function createResearchSession({ seedPayload = null, vocabSeeded = false,
|
|
|
210
204
|
|
|
211
205
|
const graph = parseEntities({ individuals: [], objectProperties: [] });
|
|
212
206
|
const lexicon = loadLexicon();
|
|
213
|
-
const vocabHint = vocabExampleHint(vocabSeeded);
|
|
207
|
+
const vocabHint = vocabExampleHint(vocabSeeded, "browser");
|
|
214
208
|
// A DISTINCT id so an ingested fact's teach tag is told apart from a typed
|
|
215
209
|
// teach turn's by session id alone — the whole reason the two growth paths
|
|
216
210
|
// stay separable in the source panel.
|
|
@@ -234,7 +228,7 @@ export function createResearchSession({ seedPayload = null, vocabSeeded = false,
|
|
|
234
228
|
sessionId: globalThis.crypto?.randomUUID?.() ?? String(Date.now()),
|
|
235
229
|
buildExtraOptions: () => ({
|
|
236
230
|
researchConfig, liveReference: liveReferenceOn, onLiveLookup,
|
|
237
|
-
|
|
231
|
+
synthesisBudget: synthesisBudgetOn,
|
|
238
232
|
}),
|
|
239
233
|
captureExtraState: (result) => {
|
|
240
234
|
if (typeof result.liveReference === "boolean" || result.liveReference === "supplement" || result.liveReference === "always") {
|
|
@@ -247,6 +241,7 @@ export function createResearchSession({ seedPayload = null, vocabSeeded = false,
|
|
|
247
241
|
|
|
248
242
|
return {
|
|
249
243
|
memoryDir,
|
|
244
|
+
graph,
|
|
250
245
|
chatSessionId,
|
|
251
246
|
ingestSessionId,
|
|
252
247
|
sessionIds,
|
|
@@ -327,8 +322,22 @@ export function createResearchSession({ seedPayload = null, vocabSeeded = false,
|
|
|
327
322
|
};
|
|
328
323
|
}
|
|
329
324
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
325
|
+
// This page's ask is already source-scoped: `tmct.ask(q, { sources })` keeps
|
|
326
|
+
// the checked source keys and runs the same factAnswer/factReadBack cascade
|
|
327
|
+
// the ledger dock runs, so a visitor can see the answer change as they tick
|
|
328
|
+
// sources off. `tmct.page` keeps the provider and wink seams the page
|
|
329
|
+
// registers, the run snapshot its timeline reads, its persisted store and its
|
|
330
|
+
// JSONL export.
|
|
331
|
+
publishTmctSurface({
|
|
332
|
+
open: createResearchSession,
|
|
333
|
+
ask: async (request, options, session) => {
|
|
334
|
+
const { text, miss } = await session.ask(request, options);
|
|
335
|
+
return { answer: text, data: null, miss };
|
|
336
|
+
},
|
|
337
|
+
plan: enginePlan,
|
|
338
|
+
page: {
|
|
339
|
+
researchSnapshot, exportFactsJsonl,
|
|
340
|
+
registerWinkModel, registerReferencePackProvider, registerLiveReferenceProvider, registerResearchProvider,
|
|
341
|
+
normFactTerm, vocabExampleHint, openPersistedStore,
|
|
342
|
+
},
|
|
343
|
+
});
|
|
@@ -44,6 +44,8 @@
|
|
|
44
44
|
// them back (hand-written pathfinding over has-exit-* facts, not the taught
|
|
45
45
|
// action-rule DSL), so nothing here depends on them being loaded.
|
|
46
46
|
import { createTurnSession } from "./turn-session.mjs";
|
|
47
|
+
import { publishTmctSurface } from "./tmct-surface.mjs";
|
|
48
|
+
import { graphAsk, enginePlan } from "./engine-surface.mjs";
|
|
47
49
|
import { registerWinkModel } from "../../adapters/wink-model.mjs";
|
|
48
50
|
import {
|
|
49
51
|
createInMemoryStore, normFactTerm, appendFacts, loadMemory, readFactRows,
|
|
@@ -137,6 +139,16 @@ export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
|
137
139
|
initial: { turn: 0, agents: initialAgents, activeWebs: [] },
|
|
138
140
|
taxonomyRows,
|
|
139
141
|
|
|
142
|
+
/** The board as a graph, as of the last refresh. Every tick moves the
|
|
143
|
+
* pieces, so a caller putting a question to it calls `refreshGraph()`
|
|
144
|
+
* first — which is exactly what `turn()` below already does. */
|
|
145
|
+
get graph() { return graph; },
|
|
146
|
+
|
|
147
|
+
/** Rebuild the board graph from the store's current rows. Exposed so a
|
|
148
|
+
* question asked outside the chat dock (tmct.ask) reads this turn's
|
|
149
|
+
* positions and not last turn's, the same way a typed one does. */
|
|
150
|
+
refreshGraph: refreshWorldGraph,
|
|
151
|
+
|
|
140
152
|
/** Run one real engine turn directly. Returns spider-fly.mjs's own
|
|
141
153
|
* { turn, agents, ecology } shape unmodified. */
|
|
142
154
|
async tick() {
|
|
@@ -189,16 +201,23 @@ export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
|
189
201
|
};
|
|
190
202
|
}
|
|
191
203
|
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
204
|
+
// `tmct.page` is grid geometry and sprite resolution — the two things on this
|
|
205
|
+
// page the engine has no plain-English form for. Reconstructing a spider's
|
|
206
|
+
// remaining silk-thread path from its returned direction list and masking the
|
|
207
|
+
// POV overlay's visible cells both need the raw cell math; the deception pills
|
|
208
|
+
// need spider-fly-turn.mjs's own pill logic.
|
|
209
|
+
publishTmctSurface({
|
|
210
|
+
open: createSpiderFlySession,
|
|
211
|
+
// The board moves every tick, so a question rebuilds the graph first —
|
|
212
|
+
// the same refresh a typed turn does before it dispatches.
|
|
213
|
+
ask: async (request, options, session) => {
|
|
214
|
+
await session.refreshGraph();
|
|
215
|
+
return graphAsk(request, options, session);
|
|
216
|
+
},
|
|
217
|
+
plan: enginePlan,
|
|
218
|
+
page: {
|
|
219
|
+
normFactTerm, resolveSpriteForClass, SPRITE_REGISTRY, resolveSpriteAsset,
|
|
220
|
+
cellId, parseCellId, DIRECTION_DELTA, visibleCells, DEFAULT_VISION_RADIUS,
|
|
221
|
+
pillsForSpiderFly, oneStepDirectionBetween, DEFAULT_GAME_CONFIG, registerWinkModel,
|
|
222
|
+
},
|
|
223
|
+
});
|
|
@@ -18,38 +18,78 @@
|
|
|
18
18
|
// a typed class name through ask.mjs's resolveObject, so it needs the real
|
|
19
19
|
// resolver in the page rather than a self-contained function the page could
|
|
20
20
|
// splice in as text.
|
|
21
|
-
|
|
21
|
+
//
|
|
22
|
+
// `tmct.ask()` needs a real graph to traverse (engine-surface.mjs's graphAsk
|
|
23
|
+
// dispatches tmct_ask over `session.graph`), so this session projects its own
|
|
24
|
+
// store into one via spriteFactGraphPayload — sprite-facts.mjs's own
|
|
25
|
+
// counterpart to spider-fly-browser-entry.mjs's worldRelationGraphPayload
|
|
26
|
+
// use. `refreshGraph()` rebuilds it from the store's CURRENT rows (not just
|
|
27
|
+
// the embedded factRows), so a fact taught mid-session is visible to a later
|
|
28
|
+
// ask() the same way spider-fly's board rebuild is; `turn()` and the
|
|
29
|
+
// published `ask` route both call it first, mirroring spider-fly's exact
|
|
30
|
+
// wiring points.
|
|
31
|
+
import { createInMemoryStore, appendFacts, normFactTerm, loadMemory, readFactRows } from "../../adapters/memory/core.mjs";
|
|
22
32
|
import { extractSceneItems } from "../../domain/scene-compose.mjs";
|
|
23
33
|
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
24
34
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
25
|
-
import { SPRITE_FACTS_PROVENANCE } from "../../domain/sprite-facts.mjs";
|
|
35
|
+
import { SPRITE_FACTS_PROVENANCE, spriteFactGraphPayload } from "../../domain/sprite-facts.mjs";
|
|
26
36
|
import { registerWinkModel } from "../../adapters/wink-model.mjs";
|
|
27
37
|
import { createTurnSession } from "./turn-session.mjs";
|
|
38
|
+
import { publishTmctSurface } from "./tmct-surface.mjs";
|
|
39
|
+
import { graphAsk, enginePlan } from "./engine-surface.mjs";
|
|
28
40
|
|
|
29
41
|
/** A live in-memory chat session seeded with the embedded sprite-facts rows.
|
|
30
|
-
* Returns { memoryDir, sessionId, factCount, turn }. */
|
|
42
|
+
* Returns { memoryDir, sessionId, graph, refreshGraph, factCount, turn }. */
|
|
31
43
|
export async function createSpriteCatalogSession({ factRows = [] } = {}) {
|
|
32
44
|
const memoryDir = createInMemoryStore();
|
|
33
45
|
await appendFacts(memoryDir, factRows.map((f) => ({
|
|
34
46
|
subject: f.subject, predicate: f.predicate, object: f.object, provenance: SPRITE_FACTS_PROVENANCE,
|
|
35
47
|
})));
|
|
36
48
|
|
|
37
|
-
|
|
49
|
+
let graph = parseEntities({ individuals: [], objectProperties: [] });
|
|
50
|
+
async function refreshGraph() {
|
|
51
|
+
const rows = readFactRows(await loadMemory(memoryDir));
|
|
52
|
+
graph = parseEntities(spriteFactGraphPayload(rows));
|
|
53
|
+
}
|
|
54
|
+
await refreshGraph();
|
|
55
|
+
|
|
38
56
|
const lexicon = loadLexicon();
|
|
39
57
|
const sessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
40
58
|
|
|
41
|
-
|
|
59
|
+
// `graph` is re-read here, not captured once: createTurnSession binds its
|
|
60
|
+
// own copy at creation, and refreshGraph() reassigns this closure's own
|
|
61
|
+
// variable on every call.
|
|
62
|
+
const turnSession = createTurnSession({
|
|
63
|
+
memoryDir, graph, lexicon, sessionId, vocabHint: "",
|
|
64
|
+
buildExtraOptions: () => ({ graph }),
|
|
65
|
+
});
|
|
42
66
|
|
|
43
67
|
return {
|
|
44
68
|
memoryDir,
|
|
45
69
|
sessionId,
|
|
70
|
+
get graph() { return graph; },
|
|
71
|
+
refreshGraph,
|
|
46
72
|
factCount: factRows.length,
|
|
47
|
-
turn
|
|
73
|
+
async turn(line) {
|
|
74
|
+
await refreshGraph();
|
|
75
|
+
return turnSession.turn(line);
|
|
76
|
+
},
|
|
48
77
|
};
|
|
49
78
|
}
|
|
50
79
|
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
|
|
80
|
+
// `tmct.page` keeps the wink seam (the page hands in the self-hosted pair from
|
|
81
|
+
// ./vendor/wink.js, exactly the way chat.html/ledger.html/plan.html register
|
|
82
|
+
// theirs — the bundle itself never imports wink-nlp), the term normalizer, and
|
|
83
|
+
// the scene composer's parser, which reads a typed line into drawable items
|
|
84
|
+
// rather than answering anything.
|
|
85
|
+
publishTmctSurface({
|
|
86
|
+
open: createSpriteCatalogSession,
|
|
87
|
+
// The graph is rebuilt first, so a direct tmct.ask() call (not just a
|
|
88
|
+
// typed chat turn) sees any fact 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: { registerWinkModel, normFactTerm, extractSceneItems },
|
|
95
|
+
});
|