@polycode-projects/the-mechanical-code-talker 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +90 -121
- package/src/services/adventure.mjs +97 -35
- package/src/services/chat-page-viz.mjs +32 -16
- package/src/services/chat.mjs +101 -33
- package/src/services/code-explorer-viz.mjs +51 -49
- package/src/services/ingest-viz.mjs +15 -57
- package/src/services/ledger-viz.mjs +47 -27
- 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 +87 -198
- package/src/services/p2p-room.mjs +90 -23
- package/src/services/plan-pddl.mjs +3 -1
- package/src/services/plan-viz.mjs +4 -3
- package/src/services/research-viz.mjs +10 -52
- package/src/services/spider-fly-turn.mjs +14 -22
- package/src/services/spider-fly-viz.mjs +79 -118
- package/src/services/spider-fly.mjs +69 -11
- package/src/services/sprite-catalog-viz.mjs +271 -221
- 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 +49 -33
- package/src/surfaces/web/chat-browser-entry.mjs +30 -105
- package/src/surfaces/web/code-explorer-browser-entry.mjs +168 -24
- package/src/surfaces/web/ingest-browser-entry.mjs +3 -13
- package/src/surfaces/web/ledger-browser-entry.mjs +7 -47
- package/src/surfaces/web/memory-ask-browser.bundle.js +128 -125
- package/src/surfaces/web/memory-stats.mjs +11 -0
- package/src/surfaces/web/mud-browser-entry.mjs +28 -28
- package/src/surfaces/web/plan-browser-entry.mjs +22 -40
- package/src/surfaces/web/research-browser-entry.mjs +26 -41
- package/src/surfaces/web/spider-fly-browser-entry.mjs +45 -28
- package/src/surfaces/web/sprites-browser-entry.mjs +14 -27
- package/src/surfaces/web/turn-session.mjs +120 -0
- package/src/tools/definitions.mjs +30 -0
- package/src/tools/handlers/index.mjs +6 -3
- package/src/tools/handlers/kit.mjs +19 -2
- package/src/tools/handlers/tmct-ask.mjs +11 -6
- package/src/tools/handlers/tmct-ingest.mjs +5 -1
- package/src/tools/handlers/tmct-related.mjs +4 -4
- package/src/tools/handlers/tmct-sprite.mjs +147 -0
- package/src/tools/memory-fallthrough.mjs +9 -2
- package/src/tools/server.mjs +25 -1
|
@@ -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,7 @@ 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
51
|
|
|
52
52
|
/** A live, shared mud world several characters can each act in. `worldPayload`
|
|
53
53
|
* is `{ name, facts, rules, opening }` — the same shape adventure-browser-
|
|
@@ -65,13 +65,18 @@ import { resolveSpriteAsset } from "../../domain/sprite-templates.mjs";
|
|
|
65
65
|
* visitedRoomIds, turnsTaken, isOutOfPlay, outOfPlayReason }`. `snapshot()` is
|
|
66
66
|
* the one OMNISCIENT read this module exposes — the central world map's own
|
|
67
67
|
* data source, never a per-window one. */
|
|
68
|
-
export async function createMudSession(worldPayload, { characters = [] } = {}) {
|
|
68
|
+
export async function createMudSession(worldPayload, { characters = [], epoch = 0 } = {}) {
|
|
69
69
|
const memoryDir = createInMemoryStore();
|
|
70
70
|
const tag = worldProvenanceTag(worldPayload.name);
|
|
71
71
|
const seedFacts = worldFactsForCast(worldPayload.facts, characters);
|
|
72
72
|
await appendFacts(memoryDir, seedFacts.map((f) => ({
|
|
73
73
|
subject: f.subject, predicate: f.predicate, object: f.object, provenance: tag,
|
|
74
74
|
})));
|
|
75
|
+
// A recast opens the same deterministic ids over a fresh store while peers
|
|
76
|
+
// may still hold the old run's snapshots. The epoch marker is what makes
|
|
77
|
+
// every fold — local and merged — treat this seed as the newer state. An
|
|
78
|
+
// unrecast boot writes nothing, so a solo session's store is unchanged.
|
|
79
|
+
if (epoch > 0) await appendFacts(memoryDir, [{ ...worldEpochFact(epoch), provenance: tag }]);
|
|
75
80
|
for (const rule of worldPayload.rules) {
|
|
76
81
|
await appendRule(memoryDir, { name: rule.name, kind: rule.ruleKind, slots: rule.slots, provenance: tag });
|
|
77
82
|
}
|
|
@@ -105,9 +110,10 @@ export async function createMudSession(worldPayload, { characters = [] } = {}) {
|
|
|
105
110
|
// "it"/"there" belongs to that window's own conversation, and its own
|
|
106
111
|
// discovered-room history is the real fog of war this page promises —
|
|
107
112
|
// sharing either across characters would leak one window's state into
|
|
108
|
-
// another's.
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
// another's. createTurnSession owns focus/last for us; planState is the
|
|
114
|
+
// one piece that is NOT per-character (see planHolder above), so it is
|
|
115
|
+
// threaded through buildExtraOptions/captureExtraState instead of living
|
|
116
|
+
// in the session's own internal slot.
|
|
111
117
|
const visitedRoomIds = new Set();
|
|
112
118
|
// This character's OWN turns, not the page's shared tick counter: two
|
|
113
119
|
// windows playing at different speeds, or one paused while the other
|
|
@@ -117,6 +123,20 @@ export async function createMudSession(worldPayload, { characters = [] } = {}) {
|
|
|
117
123
|
const startRoom = await roomOf(character);
|
|
118
124
|
if (startRoom) visitedRoomIds.add(startRoom);
|
|
119
125
|
|
|
126
|
+
const turnSession = createTurnSession({
|
|
127
|
+
memoryDir, graph, lexicon, sessionId: character,
|
|
128
|
+
vocabHint: 'Try a world command ("dig north", "eat the carrot-1"), or ask "what food do you know about".',
|
|
129
|
+
buildExtraOptions: () => ({
|
|
130
|
+
uiContext: "browser", actingSubject: character, planState: planHolder.state,
|
|
131
|
+
}),
|
|
132
|
+
captureExtraState: async (result, state) => {
|
|
133
|
+
if ("planState" in result) planHolder.state = state.planState;
|
|
134
|
+
turnsTaken += 1;
|
|
135
|
+
const here = await roomOf(character);
|
|
136
|
+
if (here) visitedRoomIds.add(here);
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
120
140
|
windows[character] = {
|
|
121
141
|
character,
|
|
122
142
|
|
|
@@ -125,27 +145,7 @@ export async function createMudSession(worldPayload, { characters = [] } = {}) {
|
|
|
125
145
|
* character via actingSubject. A throwing runTurn must never end the
|
|
126
146
|
* session; this window has no other chance to show this turn's
|
|
127
147
|
* 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
|
-
},
|
|
148
|
+
turn: turnSession.turn,
|
|
149
149
|
|
|
150
150
|
/** One whole scripted turn (mud-turn.mjs's runMudTurn): investigate,
|
|
151
151
|
* walk toward known food, or roll at the edge (dig). `k` is the turn
|
|
@@ -221,7 +221,7 @@ export async function createMudSession(worldPayload, { characters = [] } = {}) {
|
|
|
221
221
|
const editTurn = state.turnCount + 1;
|
|
222
222
|
if (toAppend.length) {
|
|
223
223
|
await appendFacts(memoryDir, toAppend.map((f) => ({
|
|
224
|
-
subject: f.kind === "other" ? f.subject :
|
|
224
|
+
subject: f.kind === "other" ? f.subject : snapshotSubject(f.subject, editTurn, state.epoch),
|
|
225
225
|
predicate: f.predicate,
|
|
226
226
|
object: f.object,
|
|
227
227
|
provenance: f.kind === "other" ? tag : `${tag}:turn${editTurn}`,
|
|
@@ -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,6 +31,7 @@ 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
|
// tmctChat.registerWinkModel — see wink-model.mjs's own header. The hanoi
|
|
@@ -57,52 +57,34 @@ export async function createPlanSession({ diskCount = 3, maxDepth = DEFAULT_GAME
|
|
|
57
57
|
const graph = parseEntities({ individuals: [], objectProperties: [] });
|
|
58
58
|
const lexicon = loadLexicon();
|
|
59
59
|
const sessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
60
|
-
const planHolder = { state: null };
|
|
61
|
-
let focus = null;
|
|
62
|
-
let last = null;
|
|
63
60
|
|
|
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
|
-
}
|
|
61
|
+
// `maxDepth` overrides the session's own default for just this one call
|
|
62
|
+
// (the page's own max-search-depth control threads it on every call,
|
|
63
|
+
// including a plain typed "solve it"), so raising or lowering it never
|
|
64
|
+
// requires re-teaching the board. `captureExtraState` folds `becauseText`
|
|
65
|
+
// onto the returned `plan` from the session's own plan slot — the
|
|
66
|
+
// plan-lane contract's returned object never carries it itself (only
|
|
67
|
+
// planState does), and the PDDL panel's own "because —" line needs it.
|
|
68
|
+
const session = createTurnSession({
|
|
69
|
+
memoryDir, graph, lexicon, sessionId, vocabHint: "",
|
|
70
|
+
buildExtraOptions: (state, callArgs) => ({
|
|
71
|
+
gameConfig: {
|
|
72
|
+
...DEFAULT_GAME_CONFIG,
|
|
73
|
+
planning: { ...DEFAULT_GAME_CONFIG.planning, maxDepth: callArgs?.maxDepth ?? maxDepth },
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
captureExtraState: (result, state) => {
|
|
77
|
+
if (result.plan) result.plan = { ...result.plan, becauseText: state.planState?.becauseText ?? null };
|
|
78
|
+
},
|
|
79
|
+
});
|
|
98
80
|
|
|
99
81
|
let plan = null;
|
|
100
82
|
for (const sentence of hanoiLessonSentences(diskCount)) {
|
|
101
|
-
const r = await turn(sentence);
|
|
83
|
+
const r = await session.turn(sentence);
|
|
102
84
|
if (r.plan) plan = r.plan;
|
|
103
85
|
}
|
|
104
86
|
|
|
105
|
-
return { memoryDir, sessionId, diskCount, maxDepth, plan, turn };
|
|
87
|
+
return { memoryDir, sessionId, diskCount, maxDepth, plan, turn: session.turn };
|
|
106
88
|
}
|
|
107
89
|
|
|
108
90
|
// Re-exported so the page's own rendering script (plan-viz.mjs's inlined
|
|
@@ -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,8 @@ 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 { exportFactsJsonl } from "./memory-stats.mjs";
|
|
42
43
|
|
|
43
44
|
// The Fact individual's first-write-wins timestamp, read straight off the
|
|
44
45
|
// stored attribute (mgx:createdAt) so a "recently learned" ordering never
|
|
@@ -205,22 +206,16 @@ export async function researchSnapshot(memoryDir, sessionIds = {}, { recentCap =
|
|
|
205
206
|
*/
|
|
206
207
|
export function createResearchSession({ seedPayload = null, vocabSeeded = false, liveReference = false, onLiveLookup = null, synthesisBudget = 12, digestStructures = null } = {}) {
|
|
207
208
|
const memoryDir = createInMemoryStore();
|
|
208
|
-
|
|
209
|
+
applySeedPayload(memoryDir, seedPayload);
|
|
209
210
|
|
|
210
211
|
const graph = parseEntities({ individuals: [], objectProperties: [] });
|
|
211
212
|
const lexicon = loadLexicon();
|
|
212
213
|
const vocabHint = vocabExampleHint(vocabSeeded);
|
|
213
|
-
const chatSessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
214
214
|
// A DISTINCT id so an ingested fact's teach tag is told apart from a typed
|
|
215
215
|
// teach turn's by session id alone — the whole reason the two growth paths
|
|
216
216
|
// stay separable in the source panel.
|
|
217
217
|
const ingestSessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now() + 1);
|
|
218
|
-
const sessionIds = { chatSessionId, ingestSessionId };
|
|
219
218
|
|
|
220
|
-
let focus = null;
|
|
221
|
-
let last = null;
|
|
222
|
-
let planState = null;
|
|
223
|
-
let researchState = null;
|
|
224
219
|
const normLive = (v) => (v === "always" ? "always" : v === "supplement" ? "supplement" : Boolean(v));
|
|
225
220
|
let liveReferenceOn = normLive(liveReference);
|
|
226
221
|
let synthesisBudgetOn = Number.isFinite(synthesisBudget) ? synthesisBudget : 12;
|
|
@@ -230,6 +225,26 @@ export function createResearchSession({ seedPayload = null, vocabSeeded = false,
|
|
|
230
225
|
// (they ride its persisted state), so its queue stays internally consistent.
|
|
231
226
|
let researchConfig = clampResearchConfig();
|
|
232
227
|
|
|
228
|
+
// The chat-engine turn (research/teach/ask), threading focus/last/planState/
|
|
229
|
+
// researchState across calls the way every browser chat dock does. A throw
|
|
230
|
+
// never kills the session — the page has no other chance to show this
|
|
231
|
+
// turn's answer.
|
|
232
|
+
const turnSession = createTurnSession({
|
|
233
|
+
memoryDir, graph, lexicon, vocabHint,
|
|
234
|
+
sessionId: globalThis.crypto?.randomUUID?.() ?? String(Date.now()),
|
|
235
|
+
buildExtraOptions: () => ({
|
|
236
|
+
researchConfig, liveReference: liveReferenceOn, onLiveLookup,
|
|
237
|
+
uiContext: "browser", synthesisBudget: synthesisBudgetOn,
|
|
238
|
+
}),
|
|
239
|
+
captureExtraState: (result) => {
|
|
240
|
+
if (typeof result.liveReference === "boolean" || result.liveReference === "supplement" || result.liveReference === "always") {
|
|
241
|
+
liveReferenceOn = result.liveReference;
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
const chatSessionId = turnSession.sessionId;
|
|
246
|
+
const sessionIds = { chatSessionId, ingestSessionId };
|
|
247
|
+
|
|
233
248
|
return {
|
|
234
249
|
memoryDir,
|
|
235
250
|
chatSessionId,
|
|
@@ -245,28 +260,7 @@ export function createResearchSession({ seedPayload = null, vocabSeeded = false,
|
|
|
245
260
|
* unset keys keep their current value. */
|
|
246
261
|
setResearchConfig(partial) { researchConfig = clampResearchConfig({ ...researchConfig, ...(partial || {}) }); },
|
|
247
262
|
|
|
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
|
-
},
|
|
263
|
+
turn: (line) => turnSession.turn(line),
|
|
270
264
|
|
|
271
265
|
/** Ingest a document into the SAME store, under the ingest session id so
|
|
272
266
|
* its facts carry the "ingest" source rather than "taught". */
|
|
@@ -333,15 +327,6 @@ export function createResearchSession({ seedPayload = null, vocabSeeded = false,
|
|
|
333
327
|
};
|
|
334
328
|
}
|
|
335
329
|
|
|
336
|
-
/**
|
|
337
|
-
* The session's whole triple store as JSONL — the same
|
|
338
|
-
* { subject, predicate, object, provenance } shape `tmct extract` and
|
|
339
|
-
* `tmct memory --export` emit, offered to the page as the canonical download.
|
|
340
|
-
*/
|
|
341
|
-
export async function exportFactsJsonl(memoryDir) {
|
|
342
|
-
return serializeFactsJsonl(await loadMemory(memoryDir));
|
|
343
|
-
}
|
|
344
|
-
|
|
345
330
|
globalThis.tmctResearch = {
|
|
346
331
|
createResearchSession, researchSnapshot, exportFactsJsonl,
|
|
347
332
|
registerWinkModel, registerReferencePackProvider, registerLiveReferenceProvider, registerResearchProvider,
|
|
@@ -43,14 +43,17 @@
|
|
|
43
43
|
// spider-fly.mjs's own header comment confirms grid movement never reads
|
|
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
|
-
import {
|
|
46
|
+
import { createTurnSession } from "./turn-session.mjs";
|
|
47
|
+
import { registerWinkModel } from "../../adapters/wink-model.mjs";
|
|
47
48
|
import {
|
|
48
49
|
createInMemoryStore, normFactTerm, appendFacts, loadMemory, readFactRows,
|
|
49
50
|
} from "../../adapters/memory/core.mjs";
|
|
50
51
|
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
52
|
+
import { worldRelationGraphPayload } from "../../domain/ask.mjs";
|
|
51
53
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
52
54
|
import {
|
|
53
55
|
worldFactRows, WORLD_NAME, WORLD_OPENING, cellId, parseCellId, DIRECTION_DELTA, visibleCells,
|
|
56
|
+
isLiveRenderableAgent, agentKindOf,
|
|
54
57
|
} from "../../domain/spider-fly-world.mjs";
|
|
55
58
|
import { foldSpiderFlyState, runSpiderFlyTick, startSpiderFlyGame, liveWebs, DEFAULT_VISION_RADIUS } from "../../services/spider-fly.mjs";
|
|
56
59
|
import { pillsForSpiderFly, oneStepDirectionBetween } from "../../services/spider-fly-turn.mjs";
|
|
@@ -84,13 +87,27 @@ export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
|
84
87
|
else if (f.predicate === "mgx:mass") initialAgents[f.subject] = { ...initialAgents[f.subject], mass: Number(f.object) };
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
|
|
90
|
+
// The graph the chat dock's own ask() traverses. There is no code graph
|
|
91
|
+
// here, so it holds the LIVE BOARD instead: one individual per agent, classed
|
|
92
|
+
// by its id, carrying its current cell, mass and mood. That is what makes
|
|
93
|
+
// "list the locations of flies and spiders" a real ask() capability call
|
|
94
|
+
// rather than another hand-written filter over the same rows. Rebuilt before
|
|
95
|
+
// every chat turn, since every tick moves the pieces.
|
|
96
|
+
let graph = parseEntities({ individuals: [], objectProperties: [] });
|
|
97
|
+
const readBoard = async () => {
|
|
98
|
+
const rows = readFactRows(await loadMemory(memoryDir));
|
|
99
|
+
return { rows, state: foldSpiderFlyState(rows) };
|
|
100
|
+
};
|
|
101
|
+
async function refreshWorldGraph() {
|
|
102
|
+
const { rows, state } = await readBoard();
|
|
103
|
+
graph = parseEntities(worldRelationGraphPayload(rows, {
|
|
104
|
+
classOf: (id) => (isLiveRenderableAgent(id, state) ? agentKindOf(id) : null),
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
await refreshWorldGraph();
|
|
88
108
|
const lexicon = loadLexicon();
|
|
89
109
|
const sessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
90
110
|
|
|
91
|
-
let focus = null;
|
|
92
|
-
let last = null;
|
|
93
|
-
let planState = { spiderFly: { turn: 0 } };
|
|
94
111
|
// The live, in-page-slider-adjustable knobs (mass-loss-rate/spawn-rate/
|
|
95
112
|
// vision-radius per class, and every other spiderFly tunable) — starts at
|
|
96
113
|
// the shipped defaults, mutated only through setConfig() below, and
|
|
@@ -100,6 +117,19 @@ export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
|
100
117
|
// change only changes what happens FROM HERE ON, same as tmct.toml would.
|
|
101
118
|
let config = { ...DEFAULT_GAME_CONFIG.spiderFly };
|
|
102
119
|
|
|
120
|
+
// The chat dock's turn dispatch — createTurnSession owns the focus/last/
|
|
121
|
+
// planState fold and the throw-safe catch fallback every browser entry
|
|
122
|
+
// needs; tick() below reaches into the SAME planState (via setPlanState)
|
|
123
|
+
// so a raw tick and a chat-driven tick can never disagree about the turn
|
|
124
|
+
// count either lane sees next.
|
|
125
|
+
const turnSession = createTurnSession({
|
|
126
|
+
memoryDir, graph, lexicon, sessionId, vocabHint: "",
|
|
127
|
+
// `graph` is re-read here, not captured above: createTurnSession binds its
|
|
128
|
+
// own once at creation, and this board is rebuilt every turn.
|
|
129
|
+
buildExtraOptions: () => ({ graph, gameConfig: { ...DEFAULT_GAME_CONFIG, spiderFly: config } }),
|
|
130
|
+
});
|
|
131
|
+
turnSession.setPlanState({ spiderFly: { turn: 0 } });
|
|
132
|
+
|
|
103
133
|
return {
|
|
104
134
|
memoryDir,
|
|
105
135
|
sessionId,
|
|
@@ -111,30 +141,18 @@ export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
|
111
141
|
* { turn, agents, ecology } shape unmodified. */
|
|
112
142
|
async tick() {
|
|
113
143
|
const result = await runSpiderFlyTick(memoryDir, { config });
|
|
114
|
-
|
|
144
|
+
turnSession.setPlanState({ spiderFly: { turn: result.turn } });
|
|
115
145
|
return result;
|
|
116
146
|
},
|
|
117
147
|
|
|
118
148
|
/** One dispatched chat turn — the SAME runTurn the CLI and the home
|
|
119
|
-
* page's own chat run, over this session's own memoryDir
|
|
120
|
-
*
|
|
121
|
-
*
|
|
149
|
+
* page's own chat run, over this session's own memoryDir, via the
|
|
150
|
+
* shared turn-dispatch wrapper (createTurnSession above) every browser
|
|
151
|
+
* entry now uses. The board graph is rebuilt first, so a question about
|
|
152
|
+
* where the pieces are reads this turn's positions and not last turn's. */
|
|
122
153
|
async turn(line) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
result = await runTurn(line, {
|
|
126
|
-
config: null, source: null, graph, focus, last, memoryDir, sessionId,
|
|
127
|
-
env: {}, lexicon, vocabHint: "", planState,
|
|
128
|
-
gameConfig: { ...DEFAULT_GAME_CONFIG, spiderFly: config },
|
|
129
|
-
});
|
|
130
|
-
} catch (e) {
|
|
131
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
132
|
-
return { answer: `Something went wrong answering that (${message}). Try rephrasing, or /help.`, end: false, record: null, plan: null };
|
|
133
|
-
}
|
|
134
|
-
focus = result.focus;
|
|
135
|
-
last = result.last;
|
|
136
|
-
if ("planState" in result) planState = result.planState;
|
|
137
|
-
return { answer: result.answer, end: Boolean(result.end), record: result.record ?? null, plan: result.plan ?? null };
|
|
154
|
+
await refreshWorldGraph();
|
|
155
|
+
return turnSession.turn(line);
|
|
138
156
|
},
|
|
139
157
|
|
|
140
158
|
/** A read-only fold of the CURRENT board — no engine advance, no goal
|
|
@@ -143,11 +161,10 @@ export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
|
143
161
|
* chat-driven tick. Web individuals are never listed as agents (that's
|
|
144
162
|
* spider-1/fly-1/... only) — they surface only through activeWebs. */
|
|
145
163
|
async snapshot() {
|
|
146
|
-
const
|
|
147
|
-
const state = foldSpiderFlyState(rows);
|
|
164
|
+
const { state } = await readBoard();
|
|
148
165
|
const agents = {};
|
|
149
166
|
for (const [id, place] of state.placements) {
|
|
150
|
-
if (
|
|
167
|
+
if (!isLiveRenderableAgent(id, state)) continue;
|
|
151
168
|
agents[id] = { cell: place.cell, mass: state.mass.get(id)?.value ?? null };
|
|
152
169
|
}
|
|
153
170
|
return { turn: state.turnCount, agents, activeWebs: liveWebs(state.webs, state.turnCount) };
|
|
@@ -183,5 +200,5 @@ export async function createSpiderFlySession({ flyCount = 1 } = {}) {
|
|
|
183
200
|
globalThis.tmctSpiderFly = {
|
|
184
201
|
createSpiderFlySession, normFactTerm, resolveSpriteForClass, SPRITE_REGISTRY, resolveSpriteAsset,
|
|
185
202
|
cellId, parseCellId, DIRECTION_DELTA, visibleCells, DEFAULT_VISION_RADIUS,
|
|
186
|
-
pillsForSpiderFly, oneStepDirectionBetween, DEFAULT_GAME_CONFIG,
|
|
203
|
+
pillsForSpiderFly, oneStepDirectionBetween, DEFAULT_GAME_CONFIG, registerWinkModel,
|
|
187
204
|
};
|
|
@@ -9,16 +9,22 @@
|
|
|
9
9
|
// (src/domain/sprite-facts.mjs's rows). A question the engine can't ground in
|
|
10
10
|
// those rows gets the same refusal the CLI gives — never a guess.
|
|
11
11
|
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
12
|
+
// Every line the dock takes goes to that session — the page intercepts
|
|
13
|
+
// nothing, so a catalog question is answered by the same membership, count and
|
|
14
|
+
// property lanes chat.mjs runs for any other caller, reading the sprite-facts
|
|
15
|
+
// predicates straight.
|
|
16
|
+
//
|
|
17
|
+
// The scene composer's parser rides along here too: extractSceneItems resolves
|
|
18
|
+
// a typed class name through ask.mjs's resolveObject, so it needs the real
|
|
19
|
+
// resolver in the page rather than a self-contained function the page could
|
|
20
|
+
// splice in as text.
|
|
17
21
|
import { createInMemoryStore, appendFacts, normFactTerm } from "../../adapters/memory/core.mjs";
|
|
22
|
+
import { extractSceneItems } from "../../domain/scene-compose.mjs";
|
|
18
23
|
import { parseEntities } from "../../domain/codegraph.mjs";
|
|
19
24
|
import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
20
25
|
import { SPRITE_FACTS_PROVENANCE } from "../../domain/sprite-facts.mjs";
|
|
21
26
|
import { registerWinkModel } from "../../adapters/wink-model.mjs";
|
|
27
|
+
import { createTurnSession } from "./turn-session.mjs";
|
|
22
28
|
|
|
23
29
|
/** A live in-memory chat session seeded with the embedded sprite-facts rows.
|
|
24
30
|
* Returns { memoryDir, sessionId, factCount, turn }. */
|
|
@@ -32,32 +38,13 @@ export async function createSpriteCatalogSession({ factRows = [] } = {}) {
|
|
|
32
38
|
const lexicon = loadLexicon();
|
|
33
39
|
const sessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
let last = null;
|
|
41
|
+
const session = createTurnSession({ memoryDir, graph, lexicon, sessionId, vocabHint: "" });
|
|
37
42
|
|
|
38
43
|
return {
|
|
39
44
|
memoryDir,
|
|
40
45
|
sessionId,
|
|
41
46
|
factCount: factRows.length,
|
|
42
|
-
|
|
43
|
-
/** One dispatched chat turn — the SAME runTurn the CLI runs, over this
|
|
44
|
-
* session's own memoryDir. A throwing runTurn must never kill the
|
|
45
|
-
* session — the page has no other chance to show this turn's answer. */
|
|
46
|
-
async turn(line) {
|
|
47
|
-
let result;
|
|
48
|
-
try {
|
|
49
|
-
result = await runTurn(line, {
|
|
50
|
-
config: null, source: null, graph, focus, last, memoryDir, sessionId,
|
|
51
|
-
env: {}, lexicon, vocabHint: "",
|
|
52
|
-
});
|
|
53
|
-
} catch (e) {
|
|
54
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
55
|
-
return { answer: `Something went wrong answering that (${message}). Try rephrasing, or /help.`, end: false, record: null };
|
|
56
|
-
}
|
|
57
|
-
focus = result.focus;
|
|
58
|
-
last = result.last;
|
|
59
|
-
return { answer: result.answer, end: Boolean(result.end), record: result.record ?? null };
|
|
60
|
-
},
|
|
47
|
+
turn: session.turn,
|
|
61
48
|
};
|
|
62
49
|
}
|
|
63
50
|
|
|
@@ -65,4 +52,4 @@ export async function createSpriteCatalogSession({ factRows = [] } = {}) {
|
|
|
65
52
|
// the self-hosted wink pair (./vendor/wink.js) exactly the way chat.html/
|
|
66
53
|
// ledger.html/plan.html register theirs — the bundle itself never imports
|
|
67
54
|
// wink-nlp (wink-model.mjs's own header explains why).
|
|
68
|
-
globalThis.tmctSprites = { createSpriteCatalogSession, registerWinkModel, normFactTerm };
|
|
55
|
+
globalThis.tmctSprites = { createSpriteCatalogSession, registerWinkModel, normFactTerm, extractSceneItems };
|