@polycode-projects/the-mechanical-code-talker 4.1.0 → 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/package.json +1 -1
- package/src/domain/sprite-request.mjs +1 -1
- package/src/services/adventure-viz.mjs +38 -38
- package/src/services/chat-page-viz.mjs +32 -32
- package/src/services/chat.mjs +1 -1
- package/src/services/code-explorer-viz.mjs +1 -1
- package/src/services/ingest-viz.mjs +19 -19
- package/src/services/ledger-viz.mjs +40 -43
- package/src/services/mud-viz.mjs +33 -28
- package/src/services/plan-viz.mjs +9 -9
- package/src/services/research-viz.mjs +17 -17
- package/src/services/spider-fly-viz.mjs +22 -22
- package/src/services/sprite-catalog-viz.mjs +4 -4
- package/src/surfaces/web/adventure-browser-entry.mjs +21 -22
- package/src/surfaces/web/chat-browser-entry.mjs +21 -2
- package/src/surfaces/web/code-explorer-browser-entry.mjs +28 -15
- package/src/surfaces/web/engine-surface.mjs +82 -0
- package/src/surfaces/web/ingest-browser-entry.mjs +13 -4
- package/src/surfaces/web/ledger-browser-entry.mjs +18 -10
- package/src/surfaces/web/memory-ask-browser-entry.mjs +55 -13
- package/src/surfaces/web/memory-ask-browser.bundle.js +54 -54
- package/src/surfaces/web/mud-browser-entry.mjs +42 -21
- package/src/surfaces/web/plan-browser-entry.mjs +18 -11
- package/src/surfaces/web/research-browser-entry.mjs +22 -5
- package/src/surfaces/web/spider-fly-browser-entry.mjs +32 -13
- package/src/surfaces/web/sprites-browser-entry.mjs +15 -6
- package/src/surfaces/web/tmct-surface.mjs +147 -0
- package/src/surfaces/web/turn-session.mjs +6 -2
- package/src/tools/server.mjs +13 -6
|
@@ -48,6 +48,8 @@ import { worldProvenanceTag } from "../../domain/worlds-pack.mjs";
|
|
|
48
48
|
import { resolveSpriteForClass, SPRITE_REGISTRY, classAncestorChain } from "../../domain/sprite-map.mjs";
|
|
49
49
|
import { resolveSpriteAsset } from "../../domain/sprite-templates.mjs";
|
|
50
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,7 +62,7 @@ import { createTurnSession } from "./turn-session.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
|
|
@@ -270,7 +272,7 @@ export async function createMudSession(worldPayload, { characters = [], epoch =
|
|
|
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
|
+
});
|
|
@@ -34,7 +34,7 @@ import { planToPddl } from "../../services/plan-pddl.mjs";
|
|
|
34
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 { createTurnSession } from "./turn-session.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
|
|
@@ -84,13 +86,18 @@ export async function createPlanSession({ diskCount = 3, maxDepth = DEFAULT_GAME
|
|
|
84
86
|
if (r.plan) plan = r.plan;
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
return { memoryDir, sessionId, diskCount, maxDepth, plan, turn: session.turn };
|
|
89
|
+
return { memoryDir, sessionId, graph, diskCount, maxDepth, plan, turn: session.turn };
|
|
88
90
|
}
|
|
89
91
|
|
|
90
|
-
//
|
|
91
|
-
// script
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
+
});
|
|
@@ -39,6 +39,8 @@ 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
46
|
// The Fact individual's first-write-wins timestamp, read straight off the
|
|
@@ -247,6 +249,7 @@ export function createResearchSession({ seedPayload = null, vocabSeeded = false,
|
|
|
247
249
|
|
|
248
250
|
return {
|
|
249
251
|
memoryDir,
|
|
252
|
+
graph,
|
|
250
253
|
chatSessionId,
|
|
251
254
|
ingestSessionId,
|
|
252
255
|
sessionIds,
|
|
@@ -327,8 +330,22 @@ export function createResearchSession({ seedPayload = null, vocabSeeded = false,
|
|
|
327
330
|
};
|
|
328
331
|
}
|
|
329
332
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
+
});
|
|
@@ -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
|
+
});
|
|
@@ -25,9 +25,11 @@ import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
|
|
|
25
25
|
import { SPRITE_FACTS_PROVENANCE } from "../../domain/sprite-facts.mjs";
|
|
26
26
|
import { registerWinkModel } from "../../adapters/wink-model.mjs";
|
|
27
27
|
import { createTurnSession } from "./turn-session.mjs";
|
|
28
|
+
import { publishTmctSurface } from "./tmct-surface.mjs";
|
|
29
|
+
import { graphAsk, enginePlan } from "./engine-surface.mjs";
|
|
28
30
|
|
|
29
31
|
/** A live in-memory chat session seeded with the embedded sprite-facts rows.
|
|
30
|
-
* Returns { memoryDir, sessionId, factCount, turn }. */
|
|
32
|
+
* Returns { memoryDir, sessionId, graph, factCount, turn }. */
|
|
31
33
|
export async function createSpriteCatalogSession({ factRows = [] } = {}) {
|
|
32
34
|
const memoryDir = createInMemoryStore();
|
|
33
35
|
await appendFacts(memoryDir, factRows.map((f) => ({
|
|
@@ -43,13 +45,20 @@ export async function createSpriteCatalogSession({ factRows = [] } = {}) {
|
|
|
43
45
|
return {
|
|
44
46
|
memoryDir,
|
|
45
47
|
sessionId,
|
|
48
|
+
graph,
|
|
46
49
|
factCount: factRows.length,
|
|
47
50
|
turn: session.turn,
|
|
48
51
|
};
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
|
|
54
|
+
// `tmct.page` keeps the wink seam (the page hands in the self-hosted pair from
|
|
55
|
+
// ./vendor/wink.js, exactly the way chat.html/ledger.html/plan.html register
|
|
56
|
+
// theirs — the bundle itself never imports wink-nlp), the term normalizer, and
|
|
57
|
+
// the scene composer's parser, which reads a typed line into drawable items
|
|
58
|
+
// rather than answering anything.
|
|
59
|
+
publishTmctSurface({
|
|
60
|
+
open: createSpriteCatalogSession,
|
|
61
|
+
ask: graphAsk,
|
|
62
|
+
plan: enginePlan,
|
|
63
|
+
page: { registerWinkModel, normFactTerm, extractSceneItems },
|
|
64
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// tmct-surface.mjs — the one contract every demo page has with the engine.
|
|
2
|
+
//
|
|
3
|
+
// Each browser entry used to publish its own bag of primitives under its own
|
|
4
|
+
// name (tmctChat, tmctMud, tmctLedger, tmctSpiderFly, ...), so a visitor
|
|
5
|
+
// reading one page's source met a different wall of raw functions on every
|
|
6
|
+
// page, wired together by hand. mud-browser-entry.mjs's own header stated the
|
|
7
|
+
// reason for its 25: "so mud-viz.mjs's own inlined script never duplicates
|
|
8
|
+
// sprite resolution or the digest/affordance/knowledge readers". Right
|
|
9
|
+
// instinct, wrong unit — the page still had to know 25 names to say one
|
|
10
|
+
// sentence.
|
|
11
|
+
//
|
|
12
|
+
// This publishes ONE `globalThis.tmct` instead, and every page reaches the
|
|
13
|
+
// engine the same way:
|
|
14
|
+
//
|
|
15
|
+
// await tmct.open(...) open this page's session
|
|
16
|
+
// tmct.session the live session, once opened
|
|
17
|
+
// await tmct.turn(line) one conversational turn, the dock's entry point
|
|
18
|
+
// await tmct.ask(request) one grounded question, prose plus its evidence
|
|
19
|
+
// await tmct.plan(request) one compound request, planned over what's loaded
|
|
20
|
+
//
|
|
21
|
+
// Whatever a page still needs that has NO plain-English form lives on
|
|
22
|
+
// `tmct.page` — canvas geometry, sprite templates, a wink model to register, a
|
|
23
|
+
// digest structure table. The split is the point: `tmct.ask(...)` is tmct
|
|
24
|
+
// answering, `tmct.page.cellId(...)` is this page drawing. A reader can tell
|
|
25
|
+
// them apart at a glance, which is exactly what eleven flat bags made
|
|
26
|
+
// impossible.
|
|
27
|
+
//
|
|
28
|
+
// `ask` and `plan` are supplied per page rather than fixed here, because what
|
|
29
|
+
// a page can ground a question against genuinely differs: code-explorer holds
|
|
30
|
+
// a code graph, spider-fly holds a live board projected into one, the ledger
|
|
31
|
+
// dock holds a memory store and no graph at all. A page that supplies neither
|
|
32
|
+
// gets an honest refusal naming why, never a guess — the same promise the
|
|
33
|
+
// engine itself makes.
|
|
34
|
+
|
|
35
|
+
const NO_SESSION = "no session is open on this page yet — await tmct.open() first";
|
|
36
|
+
|
|
37
|
+
// The demo ledger page carries TWO bundles at once: the committed
|
|
38
|
+
// question-only one inlined into the HTML (memory-ask-browser-entry.mjs) and
|
|
39
|
+
// the demo site's own full turn engine as a separate script tag
|
|
40
|
+
// (ledger-browser-entry.mjs). Both publish this surface, and which one the
|
|
41
|
+
// browser runs first is not something either can promise. So the
|
|
42
|
+
// question-only one publishes as a `fallback` and the full engine always
|
|
43
|
+
// wins, whichever order they load in. Registered globally rather than
|
|
44
|
+
// module-locally because the two bundles carry their own copy of this module.
|
|
45
|
+
const FALLBACK = Symbol.for("tmct.surface.fallback");
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* `publishTmctSurface({ open, ask, plan, turn, page, target })` installs
|
|
49
|
+
* `target.tmct` (default `globalThis`) and returns it.
|
|
50
|
+
*
|
|
51
|
+
* `open(...args)` is the page's own session factory and is the only required
|
|
52
|
+
* argument — `createChatSession`, `createMudSession`, `createPlanSession` and
|
|
53
|
+
* the rest, called with whatever arguments they already take. Its result
|
|
54
|
+
* becomes `tmct.session`.
|
|
55
|
+
*
|
|
56
|
+
* `turn(line, options, session)` (optional) overrides how a line reaches the
|
|
57
|
+
* session. The default calls `session.turn(line, options)`, which is every
|
|
58
|
+
* page but mud — mud runs several characters over one world, so it routes on
|
|
59
|
+
* `{ as: character }` to that character's own window.
|
|
60
|
+
*
|
|
61
|
+
* `ask(request, options, session)` and `plan(request, options, session)`
|
|
62
|
+
* (optional) are the page's grounded-question and capability-planner routes.
|
|
63
|
+
* Left out, the matching verb refuses and says which route the page does have.
|
|
64
|
+
* Every verb takes the same `(what, options)` shape, so the research page's
|
|
65
|
+
* source-scoped `tmct.ask(q, { sources })` reads like the plan page's
|
|
66
|
+
* `tmct.turn("solve it", { maxDepth })`.
|
|
67
|
+
*
|
|
68
|
+
* `page` is the residual bag: this page's own rendering helpers, reachable as
|
|
69
|
+
* `tmct.page.*`.
|
|
70
|
+
*
|
|
71
|
+
* `fallback: true` publishes only where no fuller surface is standing, and
|
|
72
|
+
* yields to one that arrives later — the ledger page's two bundles.
|
|
73
|
+
*/
|
|
74
|
+
export function publishTmctSurface({
|
|
75
|
+
open, ask = null, plan = null, turn = null, page = {}, fallback = false, target = globalThis,
|
|
76
|
+
} = {}) {
|
|
77
|
+
if (typeof open !== "function") {
|
|
78
|
+
throw new Error("publishTmctSurface: `open` must be this page's session factory");
|
|
79
|
+
}
|
|
80
|
+
const standing = target.tmct;
|
|
81
|
+
if (fallback && standing && !standing[FALLBACK]) return standing;
|
|
82
|
+
let session = null;
|
|
83
|
+
|
|
84
|
+
const liveSession = () => {
|
|
85
|
+
if (!session) throw new Error(NO_SESSION);
|
|
86
|
+
return session;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const surface = {
|
|
90
|
+
// Variadic, so each page's factory keeps the signature it already has —
|
|
91
|
+
// adventure and mud both take the world payload first, then their options.
|
|
92
|
+
async open(...args) {
|
|
93
|
+
session = await open(...args);
|
|
94
|
+
return session;
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
get session() { return session; },
|
|
98
|
+
|
|
99
|
+
async turn(line, options) {
|
|
100
|
+
const live = liveSession();
|
|
101
|
+
if (turn) return turn(line, options, live);
|
|
102
|
+
// Not every page holds a conversation. The ingest page grounds pasted
|
|
103
|
+
// prose into facts and answers nothing, so it says so rather than
|
|
104
|
+
// failing on a method it never had.
|
|
105
|
+
if (typeof live.turn !== "function") {
|
|
106
|
+
return {
|
|
107
|
+
answer: "this page runs no conversational turn — see tmct.session for what it does run",
|
|
108
|
+
end: false, record: null, plan: null, research: null,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return live.turn(line, options);
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
async ask(request, options) {
|
|
115
|
+
if (!ask) {
|
|
116
|
+
return {
|
|
117
|
+
answer: "this page has no separate ask route — it answers questions through tmct.turn()",
|
|
118
|
+
data: undefined,
|
|
119
|
+
miss: true,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return ask(request, options, liveSession());
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
async plan(request, options) {
|
|
126
|
+
if (!plan) {
|
|
127
|
+
return {
|
|
128
|
+
refused: true,
|
|
129
|
+
why: "this page's bundle carries no capability planner",
|
|
130
|
+
driver: null, calls: [], composed: null, observed: null,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return plan(request, options, liveSession());
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
page,
|
|
137
|
+
|
|
138
|
+
/** True where this is the question-only stand-in rather than a page's full
|
|
139
|
+
* engine. The ledger page reads it to tell its two docks apart: the live
|
|
140
|
+
* one teaches, the stand-in only answers. */
|
|
141
|
+
fallback,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
surface[FALLBACK] = fallback;
|
|
145
|
+
target.tmct = surface;
|
|
146
|
+
return surface;
|
|
147
|
+
}
|
|
@@ -55,8 +55,11 @@ function turnErrorFallback(message) {
|
|
|
55
55
|
* grow a visited-rooms set, bump a turn tally). Never runs on a throw: none
|
|
56
56
|
* of the nine originals touch their own state when `runTurn` itself failed.
|
|
57
57
|
*
|
|
58
|
-
* Returns `{ memoryDir, sessionId, turn, focus, last, planState,
|
|
59
|
-
* setPlanState, researchState, setResearchState }`. `
|
|
58
|
+
* Returns `{ memoryDir, sessionId, graph, turn, focus, last, planState,
|
|
59
|
+
* setPlanState, researchState, setResearchState }`. `graph` is the one this
|
|
60
|
+
* session was built over, exposed so a caller can put a question to the same
|
|
61
|
+
* graph its turns run against (engine-surface.mjs's `graphAsk`) without
|
|
62
|
+
* holding a second reference to it. `turn(line, callArgs)`
|
|
60
63
|
* resolves to `{ answer, end, record, plan, research }` — the union of every
|
|
61
64
|
* field any of the nine originals reads; a caller ignores what it doesn't
|
|
62
65
|
* need. `research` is passed through undefined/null/a queue snapshot exactly
|
|
@@ -109,6 +112,7 @@ export function createTurnSession({
|
|
|
109
112
|
return {
|
|
110
113
|
memoryDir,
|
|
111
114
|
sessionId,
|
|
115
|
+
graph,
|
|
112
116
|
get focus() { return focus; },
|
|
113
117
|
get last() { return last; },
|
|
114
118
|
get planState() { return planState; },
|
package/src/tools/server.mjs
CHANGED
|
@@ -50,7 +50,7 @@ export const TOOLS = HOT_TOOLS.map(({ name, agentDescription, inputSchema }) =>
|
|
|
50
50
|
inputSchema,
|
|
51
51
|
}));
|
|
52
52
|
|
|
53
|
-
async function runHandler(name, args, { config, source = defaultSource, tel = null, ingest = null, memoryBackend = null } = {}) {
|
|
53
|
+
async function runHandler(name, args, { config, source = defaultSource, tel = null, ingest = null, memoryBackend = null, graph: suppliedGraph = null } = {}) {
|
|
54
54
|
// Reject an unknown tool BEFORE touching the graph — an unknown name never
|
|
55
55
|
// triggers a load. hasOwn, so an inherited name ("constructor", "toString")
|
|
56
56
|
// is unknown rather than a callable found on the prototype chain.
|
|
@@ -64,13 +64,20 @@ async function runHandler(name, args, { config, source = defaultSource, tel = nu
|
|
|
64
64
|
// conversational memory store (tmct_export) prefers it over re-deriving a
|
|
65
65
|
// backend from config when one is supplied; every other caller leaves it null
|
|
66
66
|
// and gets today's re-derive-from-config behaviour unchanged.
|
|
67
|
-
if (handle.ownsGraphLoad) return handle(args, { config, source, tel, ingest, memoryBackend });
|
|
68
|
-
|
|
67
|
+
if (handle.ownsGraphLoad) return handle(args, { config, source, tel, ingest, memoryBackend, graph: suppliedGraph });
|
|
68
|
+
// `graph` is the third seam of the same kind: a caller that ALREADY holds a
|
|
69
|
+
// parsed graph hands it over instead of making the tool layer load one. A
|
|
70
|
+
// browser session is the case that needs it — its graph is built in memory
|
|
71
|
+
// (a seed payload, or a live board projected through worldRelationGraphPayload)
|
|
72
|
+
// and there is no config or file behind it to load from.
|
|
73
|
+
const graph = suppliedGraph || await loadGraph(config, source);
|
|
69
74
|
// repo root = the dir containing .tmct/ (graphFile = <repo>/.tmct/graph.json). Passed to
|
|
70
75
|
// createGraphService so svc.snippet()/svc.context() are usable directly, and on to the
|
|
71
|
-
// handlers that do their own safe source reads.
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
// handlers that do their own safe source reads. A supplied graph has no repo on
|
|
77
|
+
// disk behind it, so those reads are off rather than pointed at a path that
|
|
78
|
+
// isn't there.
|
|
79
|
+
const repoRoot = config?.graphFile ? dirname(dirname(config.graphFile)) : null;
|
|
80
|
+
const svc = createGraphService(graph, { sourceAccess: Boolean(repoRoot), repoRoot, readFile, tel, ask });
|
|
74
81
|
return handle(args, { graph, svc, config, repoRoot, memoryBackend });
|
|
75
82
|
}
|
|
76
83
|
|