@polycode-projects/the-mechanical-code-talker 4.0.1 → 4.1.1
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 +2 -1
- package/corpus/sprites/src/sprite-facts.jsonl +375 -8
- package/package.json +1 -1
- 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/mud-facts.mjs +15 -0
- 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 +36 -0
- package/src/domain/sprite-facts.mjs +0 -0
- package/src/domain/sprite-request.mjs +156 -0
- package/src/domain/sprite-templates.mjs +161 -14
- package/src/services/adventure-editor.mjs +8 -14
- package/src/services/adventure-viz.mjs +119 -150
- package/src/services/adventure.mjs +97 -35
- package/src/services/chat-page-viz.mjs +64 -48
- package/src/services/chat.mjs +102 -34
- package/src/services/code-explorer-viz.mjs +52 -50
- package/src/services/ingest-viz.mjs +32 -74
- package/src/services/ledger-viz.mjs +87 -70
- package/src/services/memory-panel-viz.mjs +38 -0
- package/src/services/mud-editor.mjs +10 -15
- package/src/services/mud-turn.mjs +6 -6
- package/src/services/mud-viz.mjs +119 -225
- package/src/services/p2p-room.mjs +90 -23
- package/src/services/plan-pddl.mjs +3 -1
- package/src/services/plan-viz.mjs +13 -12
- package/src/services/research-viz.mjs +25 -67
- package/src/services/spider-fly-turn.mjs +14 -22
- package/src/services/spider-fly-viz.mjs +97 -136
- package/src/services/spider-fly.mjs +69 -11
- package/src/services/sprite-catalog-viz.mjs +274 -224
- package/src/services/viz-boot.mjs +71 -0
- package/src/services/viz-room-graph.mjs +203 -0
- package/src/services/viz-theme.mjs +75 -1
- package/src/services/viz-ticker.mjs +22 -0
- package/src/surfaces/web/adventure-browser-entry.mjs +62 -47
- package/src/surfaces/web/chat-browser-entry.mjs +51 -107
- package/src/surfaces/web/code-explorer-browser-entry.mjs +192 -35
- package/src/surfaces/web/engine-surface.mjs +82 -0
- package/src/surfaces/web/ingest-browser-entry.mjs +16 -17
- package/src/surfaces/web/ledger-browser-entry.mjs +24 -56
- package/src/surfaces/web/memory-ask-browser-entry.mjs +55 -13
- package/src/surfaces/web/memory-ask-browser.bundle.js +128 -125
- package/src/surfaces/web/memory-stats.mjs +11 -0
- package/src/surfaces/web/mud-browser-entry.mjs +70 -49
- package/src/surfaces/web/plan-browser-entry.mjs +39 -50
- package/src/surfaces/web/research-browser-entry.mjs +48 -46
- package/src/surfaces/web/spider-fly-browser-entry.mjs +76 -40
- package/src/surfaces/web/sprites-browser-entry.mjs +28 -32
- package/src/surfaces/web/tmct-surface.mjs +147 -0
- package/src/surfaces/web/turn-session.mjs +124 -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 +37 -6
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// store shape.
|
|
5
5
|
import { loadMemory, readFactRows } from "../../adapters/memory/core.mjs";
|
|
6
6
|
import { provenanceTagToSource } from "../../domain/memory/trust.mjs";
|
|
7
|
+
import { serializeFactsJsonl } from "../../adapters/memory/export-jsonl.mjs";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* The memory a running session holds, broken down by where each fact came
|
|
@@ -51,3 +52,13 @@ export async function memoryStats(memoryDir) {
|
|
|
51
52
|
}
|
|
52
53
|
return { total: rows.length, bandCounts, taught };
|
|
53
54
|
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The session's whole triple store as JSONL — one
|
|
58
|
+
* { subject, predicate, object, provenance } object per line, the same shape
|
|
59
|
+
* `tmct extract` and `tmct memory --export` emit. Reads the live memory the
|
|
60
|
+
* same way memoryStats does; a page offers this as a download.
|
|
61
|
+
*/
|
|
62
|
+
export async function exportFactsJsonl(memoryDir) {
|
|
63
|
+
return serializeFactsJsonl(await loadMemory(memoryDir));
|
|
64
|
+
}
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
// auto-playing at once — this file makes no ordering promise between two
|
|
29
29
|
// concurrent calls into the same memoryDir, the same way two callers writing
|
|
30
30
|
// into any shared store concurrently would need their own queue.
|
|
31
|
-
import { runTurn } from "../../services/chat.mjs";
|
|
32
31
|
import {
|
|
33
32
|
createInMemoryStore, appendFacts, appendRule, loadMemory, readFactRows, removeFacts,
|
|
34
33
|
} from "../../adapters/memory/core.mjs";
|
|
@@ -38,7 +37,7 @@ import {
|
|
|
38
37
|
foldWorldState, worldActionRows, worldDigestRows, roomAffordances,
|
|
39
38
|
personKnowledgeLines, personKnownFoodLines,
|
|
40
39
|
diggableDirections, castInRoom, displayNameOf, isOutOfPlay, outOfPlayReasonOf, outOfPlayPhrase,
|
|
41
|
-
roomKindOf, isMudStatePredicate,
|
|
40
|
+
roomKindOf, isMudStatePredicate, worldEpochFact, snapshotSubject,
|
|
42
41
|
} from "../../services/adventure.mjs";
|
|
43
42
|
import { waveFact, playedByFact, P2P_PREDICATES } from "../../domain/p2p/facts.mjs";
|
|
44
43
|
import { relatedForTerm } from "../../domain/skos-view.mjs";
|
|
@@ -48,6 +47,9 @@ import { mudSpeciesOf } from "../../domain/game-config.mjs";
|
|
|
48
47
|
import { worldProvenanceTag } from "../../domain/worlds-pack.mjs";
|
|
49
48
|
import { resolveSpriteForClass, SPRITE_REGISTRY, classAncestorChain } from "../../domain/sprite-map.mjs";
|
|
50
49
|
import { resolveSpriteAsset } from "../../domain/sprite-templates.mjs";
|
|
50
|
+
import { createTurnSession } from "./turn-session.mjs";
|
|
51
|
+
import { publishTmctSurface } from "./tmct-surface.mjs";
|
|
52
|
+
import { graphAsk, enginePlan } from "./engine-surface.mjs";
|
|
51
53
|
|
|
52
54
|
/** A live, shared mud world several characters can each act in. `worldPayload`
|
|
53
55
|
* is `{ name, facts, rules, opening }` — the same shape adventure-browser-
|
|
@@ -60,18 +62,23 @@ import { resolveSpriteAsset } from "../../domain/sprite-templates.mjs";
|
|
|
60
62
|
* are minted, fewer leaves the ones nobody is playing out of the world
|
|
61
63
|
* altogether.
|
|
62
64
|
*
|
|
63
|
-
* Returns `{ memoryDir, windows, snapshot }`. `windows` is a plain object
|
|
65
|
+
* Returns `{ memoryDir, graph, windows, snapshot }`. `windows` is a plain object
|
|
64
66
|
* keyed by character id, each value `{ character, turn, autoplayTick,
|
|
65
67
|
* visitedRoomIds, turnsTaken, isOutOfPlay, outOfPlayReason }`. `snapshot()` is
|
|
66
68
|
* the one OMNISCIENT read this module exposes — the central world map's own
|
|
67
69
|
* data source, never a per-window one. */
|
|
68
|
-
export async function createMudSession(worldPayload, { characters = [] } = {}) {
|
|
70
|
+
export async function createMudSession(worldPayload, { characters = [], epoch = 0 } = {}) {
|
|
69
71
|
const memoryDir = createInMemoryStore();
|
|
70
72
|
const tag = worldProvenanceTag(worldPayload.name);
|
|
71
73
|
const seedFacts = worldFactsForCast(worldPayload.facts, characters);
|
|
72
74
|
await appendFacts(memoryDir, seedFacts.map((f) => ({
|
|
73
75
|
subject: f.subject, predicate: f.predicate, object: f.object, provenance: tag,
|
|
74
76
|
})));
|
|
77
|
+
// A recast opens the same deterministic ids over a fresh store while peers
|
|
78
|
+
// may still hold the old run's snapshots. The epoch marker is what makes
|
|
79
|
+
// every fold — local and merged — treat this seed as the newer state. An
|
|
80
|
+
// unrecast boot writes nothing, so a solo session's store is unchanged.
|
|
81
|
+
if (epoch > 0) await appendFacts(memoryDir, [{ ...worldEpochFact(epoch), provenance: tag }]);
|
|
75
82
|
for (const rule of worldPayload.rules) {
|
|
76
83
|
await appendRule(memoryDir, { name: rule.name, kind: rule.ruleKind, slots: rule.slots, provenance: tag });
|
|
77
84
|
}
|
|
@@ -105,9 +112,10 @@ export async function createMudSession(worldPayload, { characters = [] } = {}) {
|
|
|
105
112
|
// "it"/"there" belongs to that window's own conversation, and its own
|
|
106
113
|
// discovered-room history is the real fog of war this page promises —
|
|
107
114
|
// sharing either across characters would leak one window's state into
|
|
108
|
-
// another's.
|
|
109
|
-
|
|
110
|
-
|
|
115
|
+
// another's. createTurnSession owns focus/last for us; planState is the
|
|
116
|
+
// one piece that is NOT per-character (see planHolder above), so it is
|
|
117
|
+
// threaded through buildExtraOptions/captureExtraState instead of living
|
|
118
|
+
// in the session's own internal slot.
|
|
111
119
|
const visitedRoomIds = new Set();
|
|
112
120
|
// This character's OWN turns, not the page's shared tick counter: two
|
|
113
121
|
// windows playing at different speeds, or one paused while the other
|
|
@@ -117,6 +125,20 @@ export async function createMudSession(worldPayload, { characters = [] } = {}) {
|
|
|
117
125
|
const startRoom = await roomOf(character);
|
|
118
126
|
if (startRoom) visitedRoomIds.add(startRoom);
|
|
119
127
|
|
|
128
|
+
const turnSession = createTurnSession({
|
|
129
|
+
memoryDir, graph, lexicon, sessionId: character,
|
|
130
|
+
vocabHint: 'Try a world command ("dig north", "eat the carrot-1"), or ask "what food do you know about".',
|
|
131
|
+
buildExtraOptions: () => ({
|
|
132
|
+
uiContext: "browser", actingSubject: character, planState: planHolder.state,
|
|
133
|
+
}),
|
|
134
|
+
captureExtraState: async (result, state) => {
|
|
135
|
+
if ("planState" in result) planHolder.state = state.planState;
|
|
136
|
+
turnsTaken += 1;
|
|
137
|
+
const here = await roomOf(character);
|
|
138
|
+
if (here) visitedRoomIds.add(here);
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
120
142
|
windows[character] = {
|
|
121
143
|
character,
|
|
122
144
|
|
|
@@ -125,27 +147,7 @@ export async function createMudSession(worldPayload, { characters = [] } = {}) {
|
|
|
125
147
|
* character via actingSubject. A throwing runTurn must never end the
|
|
126
148
|
* session; this window has no other chance to show this turn's
|
|
127
149
|
* answer. */
|
|
128
|
-
|
|
129
|
-
let result;
|
|
130
|
-
try {
|
|
131
|
-
result = await runTurn(line, {
|
|
132
|
-
config: null, source: null, graph, focus, last, memoryDir,
|
|
133
|
-
sessionId: character, env: {}, lexicon, uiContext: "browser",
|
|
134
|
-
actingSubject: character, planState: planHolder.state,
|
|
135
|
-
vocabHint: 'Try a world command ("dig north", "eat the carrot-1"), or ask "what food do you know about".',
|
|
136
|
-
});
|
|
137
|
-
} catch (e) {
|
|
138
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
139
|
-
return { answer: `Something went wrong answering that (${message}). Try rephrasing.`, end: false };
|
|
140
|
-
}
|
|
141
|
-
focus = result.focus;
|
|
142
|
-
last = result.last;
|
|
143
|
-
if ("planState" in result) planHolder.state = result.planState;
|
|
144
|
-
turnsTaken += 1;
|
|
145
|
-
const here = await roomOf(character);
|
|
146
|
-
if (here) visitedRoomIds.add(here);
|
|
147
|
-
return { answer: result.answer, end: Boolean(result.end) };
|
|
148
|
-
},
|
|
150
|
+
turn: turnSession.turn,
|
|
149
151
|
|
|
150
152
|
/** One whole scripted turn (mud-turn.mjs's runMudTurn): investigate,
|
|
151
153
|
* walk toward known food, or roll at the edge (dig). `k` is the turn
|
|
@@ -221,7 +223,7 @@ export async function createMudSession(worldPayload, { characters = [] } = {}) {
|
|
|
221
223
|
const editTurn = state.turnCount + 1;
|
|
222
224
|
if (toAppend.length) {
|
|
223
225
|
await appendFacts(memoryDir, toAppend.map((f) => ({
|
|
224
|
-
subject: f.kind === "other" ? f.subject :
|
|
226
|
+
subject: f.kind === "other" ? f.subject : snapshotSubject(f.subject, editTurn, state.epoch),
|
|
225
227
|
predicate: f.predicate,
|
|
226
228
|
object: f.object,
|
|
227
229
|
provenance: f.kind === "other" ? tag : `${tag}:turn${editTurn}`,
|
|
@@ -270,7 +272,7 @@ export async function createMudSession(worldPayload, { characters = [] } = {}) {
|
|
|
270
272
|
return claims.length;
|
|
271
273
|
}
|
|
272
274
|
|
|
273
|
-
return { memoryDir, windows, snapshot, applyEdit, wave, claimCharacters };
|
|
275
|
+
return { memoryDir, graph, windows, snapshot, applyEdit, wave, claimCharacters };
|
|
274
276
|
}
|
|
275
277
|
|
|
276
278
|
/** `count` entries drawn at random from `roster`, in random order, without
|
|
@@ -351,22 +353,41 @@ export function worldFactsForCast(facts, characters) {
|
|
|
351
353
|
return rows.filter((f) => !uncast.has(f.subject)).concat(mintedCharacterFacts(rows, characters));
|
|
352
354
|
}
|
|
353
355
|
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
356
|
+
// Several characters, one world, so a line has to say who is speaking:
|
|
357
|
+
// `tmct.turn(line, { as: "mole-1" })` routes to that character's own window,
|
|
358
|
+
// which owns its private focus/last and its own fog of war. Everything else a
|
|
359
|
+
// window does — a scripted autoplayTick, its visited rooms, whether it is
|
|
360
|
+
// still in play — stays on `tmct.session.windows[id]`, because those are
|
|
361
|
+
// per-character state rather than questions anyone could ask in words.
|
|
362
|
+
//
|
|
363
|
+
// `tmct.page` keeps the sprite resolution and the digest/affordance/knowledge
|
|
364
|
+
// readers the room view and chat pills render from, the roster helpers that
|
|
365
|
+
// decide which animals this visit is played with, the two predicate lists the
|
|
366
|
+
// P2P layer checks against, and the SKOS neighbourhood behind the edit mode's
|
|
367
|
+
// cursor pills — none of which is `.toString()`-splice-safe.
|
|
368
|
+
publishTmctSurface({
|
|
369
|
+
open: createMudSession,
|
|
370
|
+
turn: (line, options, session) => {
|
|
371
|
+
const character = options?.as;
|
|
372
|
+
const characterWindow = character ? session.windows[character] : null;
|
|
373
|
+
if (!characterWindow) {
|
|
374
|
+
return {
|
|
375
|
+
answer: `say which character is speaking — tmct.turn(line, { as: "${Object.keys(session.windows)[0] || "mole-1"}" })`,
|
|
376
|
+
end: false, record: null, plan: null, research: null,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
return characterWindow.turn(line);
|
|
380
|
+
},
|
|
381
|
+
ask: graphAsk,
|
|
382
|
+
plan: enginePlan,
|
|
383
|
+
page: {
|
|
384
|
+
pickMudRoster, expandMudRoster, mintedCharacterFacts, worldFactsForCast,
|
|
385
|
+
resolveSpriteForClass, SPRITE_REGISTRY, classAncestorChain, resolveSpriteAsset,
|
|
386
|
+
foldWorldState, worldActionRows, worldDigestRows, roomAffordances,
|
|
387
|
+
personKnowledgeLines, personKnownFoodLines,
|
|
388
|
+
diggableDirections, castInRoom, displayNameOf, isOutOfPlay, outOfPlayReasonOf, outOfPlayPhrase,
|
|
389
|
+
roomKindOf,
|
|
390
|
+
isMudStatePredicate, P2P_PREDICATES,
|
|
391
|
+
relatedForTerm,
|
|
392
|
+
},
|
|
393
|
+
});
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
// engine. `maxDepth` is overridable PER CALL (not just at session creation)
|
|
25
25
|
// so the page's own max-search-depth control can re-run "solve it" on the
|
|
26
26
|
// CURRENT board without tearing down and re-teaching the whole puzzle.
|
|
27
|
-
import { runTurn } from "../../services/chat.mjs";
|
|
28
27
|
import { createInMemoryStore } from "../../adapters/memory/core.mjs";
|
|
29
28
|
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
30
29
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
@@ -32,9 +31,10 @@ import { DEFAULT_GAME_CONFIG } from "../../domain/game-config.mjs";
|
|
|
32
31
|
import { hanoiLessonSentences } from "../../domain/hanoi-lesson.mjs";
|
|
33
32
|
import { computeBlocksLayout, planToPageData, renderInputsFromPlan } from "../../services/plan-viz.mjs";
|
|
34
33
|
import { planToPddl } from "../../services/plan-pddl.mjs";
|
|
34
|
+
import { createTurnSession } from "./turn-session.mjs";
|
|
35
35
|
// Re-exported so the page can register a CDN-loaded wink-nlp pair before the
|
|
36
36
|
// first teach, the same seam chat-browser-entry.mjs exposes as
|
|
37
|
-
//
|
|
37
|
+
// tmct.page.registerWinkModel — see wink-model.mjs's own header. The hanoi
|
|
38
38
|
// lesson's own "moving a disk onto a target makes the disk rest on the
|
|
39
39
|
// target" sentence needs a REAL lemmatiser (verbLemma reduces "moving" to
|
|
40
40
|
// "move" to match the taught "move onto" action family) — without it, that
|
|
@@ -44,10 +44,12 @@ import { planToPddl } from "../../services/plan-pddl.mjs";
|
|
|
44
44
|
// model because their own gameplay never asks a taught rule to reduce a
|
|
45
45
|
// verb; the hanoi lesson is the first live session here that does.
|
|
46
46
|
import { registerWinkModel } from "../../adapters/wink-model.mjs";
|
|
47
|
+
import { publishTmctSurface } from "./tmct-surface.mjs";
|
|
48
|
+
import { graphAsk, enginePlan } from "./engine-surface.mjs";
|
|
47
49
|
|
|
48
50
|
/** 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
|
|
51
|
+
* chat dock can both drive. Returns `{ memoryDir, sessionId, graph,
|
|
52
|
+
* diskCount, maxDepth, plan, turn }`. `plan` is the puzzle's freshly solved plan (the
|
|
51
53
|
* same shape chat.mjs's planLaneAnswer returns, enriched with
|
|
52
54
|
* `becauseText` — see `turn()` below), or null when `maxDepth` was too low
|
|
53
55
|
* to find one (an honest miss, not an error: `turn()`'s own answer text
|
|
@@ -57,58 +59,45 @@ export async function createPlanSession({ diskCount = 3, maxDepth = DEFAULT_GAME
|
|
|
57
59
|
const graph = parseEntities({ individuals: [], objectProperties: [] });
|
|
58
60
|
const lexicon = loadLexicon();
|
|
59
61
|
const sessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
60
|
-
const planHolder = { state: null };
|
|
61
|
-
let focus = null;
|
|
62
|
-
let last = null;
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
config: null, source: null, graph, focus, last, memoryDir, sessionId,
|
|
84
|
-
env: {}, lexicon, vocabHint: "", planState: planHolder.state, gameConfig,
|
|
85
|
-
});
|
|
86
|
-
} catch (e) {
|
|
87
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
88
|
-
return { answer: `Something went wrong answering that (${message}). Try rephrasing, or /help.`, end: false, record: null, plan: null };
|
|
89
|
-
}
|
|
90
|
-
focus = result.focus;
|
|
91
|
-
last = result.last;
|
|
92
|
-
if ("planState" in result) planHolder.state = result.planState;
|
|
93
|
-
const plan = result.plan
|
|
94
|
-
? { ...result.plan, becauseText: planHolder.state?.becauseText ?? null }
|
|
95
|
-
: null;
|
|
96
|
-
return { answer: result.answer, end: Boolean(result.end), record: result.record ?? null, plan };
|
|
97
|
-
}
|
|
63
|
+
// `maxDepth` overrides the session's own default for just this one call
|
|
64
|
+
// (the page's own max-search-depth control threads it on every call,
|
|
65
|
+
// including a plain typed "solve it"), so raising or lowering it never
|
|
66
|
+
// requires re-teaching the board. `captureExtraState` folds `becauseText`
|
|
67
|
+
// onto the returned `plan` from the session's own plan slot — the
|
|
68
|
+
// plan-lane contract's returned object never carries it itself (only
|
|
69
|
+
// planState does), and the PDDL panel's own "because —" line needs it.
|
|
70
|
+
const session = createTurnSession({
|
|
71
|
+
memoryDir, graph, lexicon, sessionId, vocabHint: "",
|
|
72
|
+
buildExtraOptions: (state, callArgs) => ({
|
|
73
|
+
gameConfig: {
|
|
74
|
+
...DEFAULT_GAME_CONFIG,
|
|
75
|
+
planning: { ...DEFAULT_GAME_CONFIG.planning, maxDepth: callArgs?.maxDepth ?? maxDepth },
|
|
76
|
+
},
|
|
77
|
+
}),
|
|
78
|
+
captureExtraState: (result, state) => {
|
|
79
|
+
if (result.plan) result.plan = { ...result.plan, becauseText: state.planState?.becauseText ?? null };
|
|
80
|
+
},
|
|
81
|
+
});
|
|
98
82
|
|
|
99
83
|
let plan = null;
|
|
100
84
|
for (const sentence of hanoiLessonSentences(diskCount)) {
|
|
101
|
-
const r = await turn(sentence);
|
|
85
|
+
const r = await session.turn(sentence);
|
|
102
86
|
if (r.plan) plan = r.plan;
|
|
103
87
|
}
|
|
104
88
|
|
|
105
|
-
return { memoryDir, sessionId, diskCount, maxDepth, plan, turn };
|
|
89
|
+
return { memoryDir, sessionId, graph, diskCount, maxDepth, plan, turn: session.turn };
|
|
106
90
|
}
|
|
107
91
|
|
|
108
|
-
//
|
|
109
|
-
// script
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
92
|
+
// `tmct.page` keeps the board layout and the PDDL/OWL-RDF formatting the
|
|
93
|
+
// page's own script draws with, plus the wink seam the hanoi lesson needs
|
|
94
|
+
// registered before its first teach. Note that `tmct.plan(...)` here is the
|
|
95
|
+
// CAPABILITY planner, not the puzzle solver: a typed "solve it" is a
|
|
96
|
+
// conversational turn like any other, so the page reaches the hanoi plan
|
|
97
|
+
// through `tmct.turn("solve it", { maxDepth })` and reads `.plan` off it.
|
|
98
|
+
publishTmctSurface({
|
|
99
|
+
open: createPlanSession,
|
|
100
|
+
ask: graphAsk,
|
|
101
|
+
plan: enginePlan,
|
|
102
|
+
page: { computeBlocksLayout, planToPageData, renderInputsFromPlan, planToPddl, registerWinkModel },
|
|
103
|
+
});
|
|
@@ -26,10 +26,9 @@
|
|
|
26
26
|
// Gitignored, Pages-demo-site-only output — scripts/build-demo-site.mjs builds
|
|
27
27
|
// it fresh on every deploy, never committed, the same posture the chat/ingest
|
|
28
28
|
// bundles document for their own output.
|
|
29
|
-
import {
|
|
29
|
+
import { vocabExampleHint, factAnswer, factReadBack } from "../../services/chat.mjs";
|
|
30
30
|
import { clampResearchConfig } from "../../services/research.mjs";
|
|
31
|
-
import { createInMemoryStore, normFactTerm, loadMemory, readFactRows, removeFacts } from "../../adapters/memory/core.mjs";
|
|
32
|
-
import { serializeFactsJsonl } from "../../adapters/memory/export-jsonl.mjs";
|
|
31
|
+
import { createInMemoryStore, normFactTerm, loadMemory, readFactRows, removeFacts, applySeedPayload } from "../../adapters/memory/core.mjs";
|
|
33
32
|
import { provenanceTagToSource } from "../../domain/memory/trust.mjs";
|
|
34
33
|
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
35
34
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
@@ -39,6 +38,10 @@ import { registerLiveReferenceProvider, registerResearchProvider } from "../../a
|
|
|
39
38
|
import { groundTextToFacts } from "./ingest-browser-entry.mjs";
|
|
40
39
|
import { openPersistedStore } from "./idb-persist.mjs";
|
|
41
40
|
import { digestTermFromPayloadBrowser } from "./digest-client.mjs";
|
|
41
|
+
import { createTurnSession } from "./turn-session.mjs";
|
|
42
|
+
import { publishTmctSurface } from "./tmct-surface.mjs";
|
|
43
|
+
import { enginePlan } from "./engine-surface.mjs";
|
|
44
|
+
import { exportFactsJsonl } from "./memory-stats.mjs";
|
|
42
45
|
|
|
43
46
|
// The Fact individual's first-write-wins timestamp, read straight off the
|
|
44
47
|
// stored attribute (mgx:createdAt) so a "recently learned" ordering never
|
|
@@ -205,22 +208,16 @@ export async function researchSnapshot(memoryDir, sessionIds = {}, { recentCap =
|
|
|
205
208
|
*/
|
|
206
209
|
export function createResearchSession({ seedPayload = null, vocabSeeded = false, liveReference = false, onLiveLookup = null, synthesisBudget = 12, digestStructures = null } = {}) {
|
|
207
210
|
const memoryDir = createInMemoryStore();
|
|
208
|
-
|
|
211
|
+
applySeedPayload(memoryDir, seedPayload);
|
|
209
212
|
|
|
210
213
|
const graph = parseEntities({ individuals: [], objectProperties: [] });
|
|
211
214
|
const lexicon = loadLexicon();
|
|
212
215
|
const vocabHint = vocabExampleHint(vocabSeeded);
|
|
213
|
-
const chatSessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
214
216
|
// A DISTINCT id so an ingested fact's teach tag is told apart from a typed
|
|
215
217
|
// teach turn's by session id alone — the whole reason the two growth paths
|
|
216
218
|
// stay separable in the source panel.
|
|
217
219
|
const ingestSessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now() + 1);
|
|
218
|
-
const sessionIds = { chatSessionId, ingestSessionId };
|
|
219
220
|
|
|
220
|
-
let focus = null;
|
|
221
|
-
let last = null;
|
|
222
|
-
let planState = null;
|
|
223
|
-
let researchState = null;
|
|
224
221
|
const normLive = (v) => (v === "always" ? "always" : v === "supplement" ? "supplement" : Boolean(v));
|
|
225
222
|
let liveReferenceOn = normLive(liveReference);
|
|
226
223
|
let synthesisBudgetOn = Number.isFinite(synthesisBudget) ? synthesisBudget : 12;
|
|
@@ -230,8 +227,29 @@ export function createResearchSession({ seedPayload = null, vocabSeeded = false,
|
|
|
230
227
|
// (they ride its persisted state), so its queue stays internally consistent.
|
|
231
228
|
let researchConfig = clampResearchConfig();
|
|
232
229
|
|
|
230
|
+
// The chat-engine turn (research/teach/ask), threading focus/last/planState/
|
|
231
|
+
// researchState across calls the way every browser chat dock does. A throw
|
|
232
|
+
// never kills the session — the page has no other chance to show this
|
|
233
|
+
// turn's answer.
|
|
234
|
+
const turnSession = createTurnSession({
|
|
235
|
+
memoryDir, graph, lexicon, vocabHint,
|
|
236
|
+
sessionId: globalThis.crypto?.randomUUID?.() ?? String(Date.now()),
|
|
237
|
+
buildExtraOptions: () => ({
|
|
238
|
+
researchConfig, liveReference: liveReferenceOn, onLiveLookup,
|
|
239
|
+
uiContext: "browser", synthesisBudget: synthesisBudgetOn,
|
|
240
|
+
}),
|
|
241
|
+
captureExtraState: (result) => {
|
|
242
|
+
if (typeof result.liveReference === "boolean" || result.liveReference === "supplement" || result.liveReference === "always") {
|
|
243
|
+
liveReferenceOn = result.liveReference;
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
const chatSessionId = turnSession.sessionId;
|
|
248
|
+
const sessionIds = { chatSessionId, ingestSessionId };
|
|
249
|
+
|
|
233
250
|
return {
|
|
234
251
|
memoryDir,
|
|
252
|
+
graph,
|
|
235
253
|
chatSessionId,
|
|
236
254
|
ingestSessionId,
|
|
237
255
|
sessionIds,
|
|
@@ -245,28 +263,7 @@ export function createResearchSession({ seedPayload = null, vocabSeeded = false,
|
|
|
245
263
|
* unset keys keep their current value. */
|
|
246
264
|
setResearchConfig(partial) { researchConfig = clampResearchConfig({ ...researchConfig, ...(partial || {}) }); },
|
|
247
265
|
|
|
248
|
-
|
|
249
|
-
* session — the page has no other chance to show this turn's answer. */
|
|
250
|
-
async turn(line) {
|
|
251
|
-
let result;
|
|
252
|
-
try {
|
|
253
|
-
result = await runTurn(line, {
|
|
254
|
-
config: null, source: null, graph, focus, last, memoryDir, sessionId: chatSessionId,
|
|
255
|
-
env: {}, lexicon, vocabHint, planState, researchState, researchConfig,
|
|
256
|
-
liveReference: liveReferenceOn, onLiveLookup,
|
|
257
|
-
uiContext: "browser", synthesisBudget: synthesisBudgetOn,
|
|
258
|
-
});
|
|
259
|
-
} catch (e) {
|
|
260
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
261
|
-
return { answer: `Something went wrong with that (${message}). Try rephrasing.`, end: false, record: null, plan: null, research: undefined };
|
|
262
|
-
}
|
|
263
|
-
focus = result.focus;
|
|
264
|
-
last = result.last;
|
|
265
|
-
if ("planState" in result) planState = result.planState;
|
|
266
|
-
if ("researchState" in result) researchState = result.researchState;
|
|
267
|
-
if (typeof result.liveReference === "boolean" || result.liveReference === "supplement" || result.liveReference === "always") liveReferenceOn = result.liveReference;
|
|
268
|
-
return { answer: result.answer, end: Boolean(result.end), record: result.record ?? null, plan: result.plan ?? null, research: result.research };
|
|
269
|
-
},
|
|
266
|
+
turn: (line) => turnSession.turn(line),
|
|
270
267
|
|
|
271
268
|
/** Ingest a document into the SAME store, under the ingest session id so
|
|
272
269
|
* its facts carry the "ingest" source rather than "taught". */
|
|
@@ -333,17 +330,22 @@ export function createResearchSession({ seedPayload = null, vocabSeeded = false,
|
|
|
333
330
|
};
|
|
334
331
|
}
|
|
335
332
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
333
|
+
// This page's ask is already source-scoped: `tmct.ask(q, { sources })` keeps
|
|
334
|
+
// the checked source keys and runs the same factAnswer/factReadBack cascade
|
|
335
|
+
// the ledger dock runs, so a visitor can see the answer change as they tick
|
|
336
|
+
// sources off. `tmct.page` keeps the provider and wink seams the page
|
|
337
|
+
// registers, the run snapshot its timeline reads, its persisted store and its
|
|
338
|
+
// JSONL export.
|
|
339
|
+
publishTmctSurface({
|
|
340
|
+
open: createResearchSession,
|
|
341
|
+
ask: async (request, options, session) => {
|
|
342
|
+
const { text, miss } = await session.ask(request, options);
|
|
343
|
+
return { answer: text, data: null, miss };
|
|
344
|
+
},
|
|
345
|
+
plan: enginePlan,
|
|
346
|
+
page: {
|
|
347
|
+
researchSnapshot, exportFactsJsonl,
|
|
348
|
+
registerWinkModel, registerReferencePackProvider, registerLiveReferenceProvider, registerResearchProvider,
|
|
349
|
+
normFactTerm, vocabExampleHint, openPersistedStore,
|
|
350
|
+
},
|
|
351
|
+
});
|