@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.
@@ -83,7 +83,7 @@ function webCellIds() {
83
83
  * `geometry` is `{ parseCellId, cellId, directionDelta }` — the exact three
84
84
  * grid-geometry primitives spider-fly-world.mjs exports, injected rather
85
85
  * than imported so this function stays `.toString()`-splice safe (the
86
- * inlined page calls it with `window.tmctSpiderFly`'s own re-exports of the
86
+ * inlined page calls it with `window.tmct`'s own re-exports of the
87
87
  * same three). Returns the ordered cell-id array (length > 1) or null.
88
88
  */
89
89
  export function threadCellsForSpiderPlan(agents, geometry) {
@@ -558,7 +558,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
558
558
  // lanes read exactly as much better with wink loaded as they do on every
559
559
  // other page. Fired eagerly here so it is likely settled well before a
560
560
  // visitor's first ordinary (non-game) question.
561
- loadWinkVendor({ register: (factory) => tmctSpiderFly.registerWinkModel(factory) })();
561
+ loadWinkVendor({ register: (factory) => tmct.page.registerWinkModel(factory) })();
562
562
  const el = (id) => document.getElementById(id);
563
563
  const boardFrame = el("boardFrame");
564
564
  const boardCanvas = el("board");
@@ -661,12 +661,12 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
661
661
  // takes, answered by the same pure resolver the tool wraps (sprite-request.mjs,
662
662
  // spliced in above) — the page holds the state and nothing else.
663
663
  function resolveSpriteFace(spriteRequest) {
664
- if (!window.tmctSpiderFly) return "";
664
+ if (!window.tmct) return "";
665
665
  return resolveSpriteRequest(spriteRequest, {
666
666
  factRows: (session && session.taxonomyRows) || [],
667
667
  templates: SPIDERFLY.spriteTemplates,
668
- spriteRegistry: tmctSpiderFly.SPRITE_REGISTRY,
669
- resolveSpriteAsset: tmctSpiderFly.resolveSpriteAsset,
668
+ spriteRegistry: tmct.page.SPRITE_REGISTRY,
669
+ resolveSpriteAsset: tmct.page.resolveSpriteAsset,
670
670
  }).svg || "";
671
671
  }
672
672
 
@@ -718,7 +718,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
718
718
  for (const [id, a] of Object.entries(agents)) {
719
719
  const cls = agentKindOf(id);
720
720
  const node = ensureSpriteEl(id, cls, a);
721
- const parsed = tmctSpiderFly.parseCellId(a.cell);
721
+ const parsed = tmct.page.parseCellId(a.cell);
722
722
  if (!parsed) continue;
723
723
  const pct = cellCenterPct(parsed.x, parsed.y);
724
724
  node.style.left = pct.leftPct + "%";
@@ -778,7 +778,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
778
778
  spriteLayer.appendChild(node);
779
779
  corpseEls[id] = node;
780
780
  }
781
- const deathCell = tmctSpiderFly.parseCellId(corpse.cell);
781
+ const deathCell = tmct.page.parseCellId(corpse.cell);
782
782
  if (!deathCell) continue;
783
783
  const pct = cellCenterPct(deathCell.x, SPIDERFLY.gridSize);
784
784
  node.style.left = pct.leftPct + "%";
@@ -863,9 +863,9 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
863
863
  });
864
864
 
865
865
  const threadGeometry = {
866
- parseCellId: (id) => tmctSpiderFly.parseCellId(id),
867
- cellId: (x, y) => tmctSpiderFly.cellId(x, y),
868
- directionDelta: tmctSpiderFly.DIRECTION_DELTA,
866
+ parseCellId: (id) => tmct.page.parseCellId(id),
867
+ cellId: (x, y) => tmct.page.cellId(x, y),
868
+ directionDelta: tmct.page.DIRECTION_DELTA,
869
869
  };
870
870
 
871
871
  function drawBoard(agents, activeWebs) {
@@ -873,7 +873,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
873
873
  boardCtx.clearRect(0, 0, w, h);
874
874
  boardCtx.fillStyle = cssVar("--taught-soft") || "rgba(46,125,79,.12)";
875
875
  for (const wc of SPIDERFLY.webCells) {
876
- const p = tmctSpiderFly.parseCellId(wc);
876
+ const p = tmct.page.parseCellId(wc);
877
877
  boardCtx.fillRect((p.x - 1) * cellSize, (p.y - 1) * cellSize, cellSize, cellSize);
878
878
  }
879
879
  // A spider-built dynamic web is a distinct color from the always-on
@@ -884,7 +884,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
884
884
  boardCtx.lineWidth = 1;
885
885
  boardCtx.setLineDash([3, 2]);
886
886
  for (const web of activeWebs || []) {
887
- const p = tmctSpiderFly.parseCellId(web.cell);
887
+ const p = tmct.page.parseCellId(web.cell);
888
888
  if (!p) continue;
889
889
  boardCtx.fillRect((p.x - 1) * cellSize, (p.y - 1) * cellSize, cellSize, cellSize);
890
890
  boardCtx.strokeRect((p.x - 1) * cellSize + 0.5, (p.y - 1) * cellSize + 0.5, cellSize - 1, cellSize - 1);
@@ -904,7 +904,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
904
904
  boardCtx.beginPath();
905
905
  let prev = null;
906
906
  thread.forEach((c, i) => {
907
- const p = tmctSpiderFly.parseCellId(c);
907
+ const p = tmct.page.parseCellId(c);
908
908
  const px = (p.x - 0.5) * cellSize, py = (p.y - 0.5) * cellSize;
909
909
  if (i === 0) boardCtx.moveTo(px, py); else boardCtx.lineTo(px, py);
910
910
  if (prev) threadHits.push({ x: (prev.x + px) / 2, y: (prev.y + py) / 2, step: i });
@@ -919,19 +919,19 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
919
919
  if (!povAgentId) return;
920
920
  const agent = lastAgents[povAgentId];
921
921
  if (!agent) { povAgentId = null; return; }
922
- const p = tmctSpiderFly.parseCellId(agent.cell);
922
+ const p = tmct.page.parseCellId(agent.cell);
923
923
  // The live, slider-adjustable per-class radius (falling back to the
924
924
  // engine's own shipped default before the session has finished
925
925
  // booting) — a POV toggle always reflects whatever vision range this
926
926
  // class currently actually has, not a fixed constant.
927
927
  const radius = agentKindOf(povAgentId) === "spider"
928
- ? (liveConfig.spiderVisionRadius ?? tmctSpiderFly.DEFAULT_VISION_RADIUS)
929
- : (liveConfig.flyVisionRadius ?? tmctSpiderFly.DEFAULT_VISION_RADIUS);
930
- const visible = new Set(tmctSpiderFly.visibleCells(p.x, p.y, radius));
928
+ ? (liveConfig.spiderVisionRadius ?? tmct.page.DEFAULT_VISION_RADIUS)
929
+ : (liveConfig.flyVisionRadius ?? tmct.page.DEFAULT_VISION_RADIUS);
930
+ const visible = new Set(tmct.page.visibleCells(p.x, p.y, radius));
931
931
  povCtx.fillStyle = "rgba(0,0,0,.55)";
932
932
  for (let gy = 1; gy <= SPIDERFLY.gridSize; gy += 1) {
933
933
  for (let gx = 1; gx <= SPIDERFLY.gridSize; gx += 1) {
934
- if (visible.has(tmctSpiderFly.cellId(gx, gy))) continue;
934
+ if (visible.has(tmct.page.cellId(gx, gy))) continue;
935
935
  povCtx.fillRect((gx - 1) * cellSize, (gy - 1) * cellSize, cellSize, cellSize);
936
936
  }
937
937
  }
@@ -983,7 +983,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
983
983
  chatqEl.value = "";
984
984
  addChatLine("u", esc(q));
985
985
  withLock(async () => {
986
- const result = await session.turn(q);
986
+ const result = await tmct.turn(q);
987
987
  addChatLine("a", esc(result.answer).replace(/\\n/g, "<br>"));
988
988
  const snap = await session.snapshot();
989
989
  redraw(snap.agents, snap.turn, snap.activeWebs);
@@ -1027,14 +1027,14 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1027
1027
  refreshPills();
1028
1028
 
1029
1029
  // ---- deception pills (§A.2.4): a SEPARATE dynamic rail, alongside (never
1030
- // replacing) the static one above. tmctSpiderFly.pillsForSpiderFly is the
1030
+ // replacing) the static one above. tmct.page.pillsForSpiderFly is the
1031
1031
  // exact same pure function spider-fly-turn.mjs exports — this page never
1032
1032
  // reimplements the true/false claim logic, only renders its output and
1033
1033
  // fills #chatq on click, same click-to-fill discipline as every other
1034
1034
  // pill on this page (never auto-submits).
1035
1035
  function renderDynamicPills() {
1036
1036
  if (!session || !Object.keys(lastAgents).length) { dynamicPillsEl.innerHTML = ""; return; }
1037
- const result = tmctSpiderFly.pillsForSpiderFly(lastAgents, selectedAddresseeId, {});
1037
+ const result = tmct.page.pillsForSpiderFly(lastAgents, selectedAddresseeId, {});
1038
1038
  selectedAddresseeId = result.addresseeId;
1039
1039
  const addrHtml = result.addressPills.map((p) =>
1040
1040
  '<button type="button" class="pill" data-role="dyn-addr" data-id="' + esc(p.id) + '"'
@@ -1099,7 +1099,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
1099
1099
 
1100
1100
  let tuningInitialized = false;
1101
1101
  async function boot() {
1102
- session = await tmctSpiderFly.createSpiderFlySession();
1102
+ session = await tmct.open();
1103
1103
  // A reset mints a brand-new session (fresh board, fresh config) — the
1104
1104
  // FIRST boot seeds the sliders from the engine's own shipped defaults;
1105
1105
  // every boot after that re-applies whatever the visitor already had the
@@ -631,7 +631,7 @@ const SPRITE_CHAT = ${embedJson({ rows: dockRows })};
631
631
  import("./vendor/wink.js"),
632
632
  winkTimeout(WINK_LOAD_TIMEOUT_MS, "wink vendor asset load timed out"),
633
633
  ]);
634
- tmctSprites.registerWinkModel(() => ({ winkNLP: mod.winkNLP, model: mod.model }));
634
+ tmct.page.registerWinkModel(() => ({ winkNLP: mod.winkNLP, model: mod.model }));
635
635
  } catch (err) {
636
636
  console.warn("tmct sprites: the wink vendor asset failed to load, continuing without the lemma/POS tier", err);
637
637
  }
@@ -661,7 +661,7 @@ const SPRITE_CHAT = ${embedJson({ rows: dockRows })};
661
661
  addDockLine("u", q);
662
662
  if (!session) { addDockLine("a miss", "the engine is still loading. Try again in a moment."); return; }
663
663
  withLock(async () => {
664
- const result = await session.turn(q);
664
+ const result = await tmct.turn(q);
665
665
  const missed = !!(result.record && result.record.miss);
666
666
  addDockLine(missed ? "a miss" : "a grounded", result.answer);
667
667
  });
@@ -677,7 +677,7 @@ const SPRITE_CHAT = ${embedJson({ rows: dockRows })};
677
677
  (async () => {
678
678
  try {
679
679
  await tryLoadWink();
680
- session = await tmctSprites.createSpriteCatalogSession({ factRows: SPRITE_CHAT.rows });
680
+ session = await tmct.open({ factRows: SPRITE_CHAT.rows });
681
681
  dockqEl.disabled = false;
682
682
  dockStatusEl.textContent = SPRITE_CHAT.rows.length + " sprite facts on record. Ask away, or use a quick question.";
683
683
  } catch (err) {
@@ -865,7 +865,7 @@ ${spriteBundleScript}
865
865
  function wireSceneComposer() {
866
866
  const composeqEl = document.getElementById("composeq");
867
867
  if (!composeqEl) return;
868
- const extractSceneItems = tmctSprites.extractSceneItems;
868
+ const extractSceneItems = tmct.page.extractSceneItems;
869
869
  const composeFormEl = document.getElementById("composeForm");
870
870
  const composePillsEl = document.getElementById("composePills");
871
871
  const sceneRowEl = document.getElementById("sceneRow");
@@ -35,6 +35,8 @@
35
35
  // game-shaped falls through to the ordinary conversational layer, exactly
36
36
  // like a real CLI session.
37
37
  import { createTurnSession } from "./turn-session.mjs";
38
+ import { publishTmctSurface } from "./tmct-surface.mjs";
39
+ import { graphAsk, enginePlan } from "./engine-surface.mjs";
38
40
  import {
39
41
  createInMemoryStore, appendFacts, appendRule, loadMemory, readFactRows, removeFacts,
40
42
  } from "../../adapters/memory/core.mjs";
@@ -127,6 +129,7 @@ export async function createAdventureSession(worldPayload, { restoredPayload = n
127
129
 
128
130
  return {
129
131
  memoryDir,
132
+ graph,
130
133
 
131
134
  /** One auto-play tick: infer the goal, execute exactly one move through
132
135
  * adventureTurn (adventure-autoplay.mjs's own contract), thread the
@@ -192,25 +195,21 @@ export async function createAdventureSession(worldPayload, { restoredPayload = n
192
195
  };
193
196
  }
194
197
 
195
- // Re-exported so the page's own rendering script (adventure-viz.mjs) never
196
- // has to duplicate sprite resolution, the digest reader, the room
197
- // affordances the chat dock's own pills read from, or (foldWorldState,
198
- // exposedFacts) the exposure-filtered fold the goal-status panel mirrors
199
- // the same posture spider-fly-browser-entry.mjs's own
200
- // globalThis.tmctSpiderFly re-export takes. `relatedForTerm`/
201
- // `classAncestorChain` back the edit mode's own cursor-suggestion pills
202
- // (adventure-viz.mjs's suggestionsForTerm mirrors this same pairing against
203
- // the global, the same reach-through-the-global pattern captionFor/pillsFor
204
- // already use for their own adventure.mjs calls). `directedGridLayout`/
205
- // `roomGraphSvg` (viz-room-graph.mjs's shared BFS-grid room-map layout and
206
- // SVG renderer) back the manor-map panel, its lightbox and its edit-mode
207
- // whole-map twin the same way — neither is `.toString()`-splice-safe
208
- // (roomGraphSvg needs escapeHtml, directedGridLayout needs its own
209
- // module-level exit-delta table), so the page reaches them here instead of
210
- // re-implementing the layout a second time.
211
- globalThis.tmctAdventure = {
212
- createAdventureSession, resolveSpriteForClass, SPRITE_REGISTRY, resolveSpriteAsset,
213
- worldDigestRows, roomAffordances, foldWorldState, exposedFacts,
214
- relatedForTerm, classAncestorChain, openPersistedStore,
215
- directedGridLayout, roomGraphSvg,
216
- };
198
+ // `tmct.page` keeps what the page draws with and the engine has no
199
+ // plain-English form for: sprite resolution, the digest reader, the room
200
+ // affordances the chat pills read from, the exposure-filtered fold the
201
+ // goal-status panel mirrors, the SKOS neighbourhood and is-a chain behind the
202
+ // edit mode's cursor pills, the persisted-store wrapper, and the manor map's
203
+ // own BFS-grid layout and SVG renderer (neither is `.toString()`-splice-safe:
204
+ // roomGraphSvg needs escapeHtml, directedGridLayout its own exit-delta table).
205
+ publishTmctSurface({
206
+ open: createAdventureSession,
207
+ ask: graphAsk,
208
+ plan: enginePlan,
209
+ page: {
210
+ resolveSpriteForClass, SPRITE_REGISTRY, resolveSpriteAsset,
211
+ worldDigestRows, roomAffordances, foldWorldState, exposedFacts,
212
+ relatedForTerm, classAncestorChain, openPersistedStore,
213
+ directedGridLayout, roomGraphSvg,
214
+ },
215
+ });
@@ -51,6 +51,8 @@ import { registerLiveReferenceProvider, registerResearchProvider } from "../../a
51
51
  // decides when to save/load/clear; this entry only carries the wrapper
52
52
  // across the bundle boundary.
53
53
  import { openPersistedStore } from "./idb-persist.mjs";
54
+ import { publishTmctSurface } from "./tmct-surface.mjs";
55
+ import { graphAsk, enginePlan } from "./engine-surface.mjs";
54
56
 
55
57
  /**
56
58
  * A browser chat session over the real turn engine.
@@ -70,7 +72,7 @@ import { openPersistedStore } from "./idb-persist.mjs";
70
72
  * is module-scope, last write wins, and every session created after this one
71
73
  * shares it.
72
74
  *
73
- * Returns { memoryDir, sessionId, turn }. `turn(line)` resolves to
75
+ * Returns { memoryDir, sessionId, graph, turn }. `turn(line)` resolves to
74
76
  * { answer, end, record, plan } and threads focus/last/planState between
75
77
  * calls exactly as the CLI session does.
76
78
  */
@@ -112,6 +114,7 @@ export function createChatSession({ seedPayload = null, vocabSeeded = false, liv
112
114
  return {
113
115
  memoryDir,
114
116
  sessionId,
117
+ graph,
115
118
  get liveReference() { return liveReferenceOn; },
116
119
  /** The page's toggle seam: set the live Wikipedia mode for every later turn
117
120
  * (the `/wiki on|off|supplement|always` command sets the same state). */
@@ -144,4 +147,20 @@ export async function researchedFactRows(memoryDir) {
144
147
  .map((row) => ({ subject: row.subject, predicate: row.predicate, object: row.object }));
145
148
  }
146
149
 
147
- globalThis.tmctChat = { createChatSession, registerWinkModel, registerReferencePackProvider, registerLiveReferenceProvider, registerResearchProvider, normFactTerm, vocabExampleHint, memoryStats, openPersistedStore, exportFactsJsonl, researchedFactRows, splitSentences: splitSentencesPreservingPaths };
150
+ // The page reaches the engine through the one shared surface: `tmct.open()`
151
+ // opens this session, `tmct.turn()` runs the dock, `tmct.ask()` puts a
152
+ // question to the session's own graph. What stays on `tmct.page` is what has
153
+ // no plain-English form — the vendor/provider seams the page registers before
154
+ // the first turn, its IndexedDB wrapper, and the two serializers its export
155
+ // and paste-ingest controls run.
156
+ publishTmctSurface({
157
+ open: createChatSession,
158
+ ask: graphAsk,
159
+ plan: enginePlan,
160
+ page: {
161
+ registerWinkModel, registerReferencePackProvider, registerLiveReferenceProvider,
162
+ registerResearchProvider, normFactTerm, vocabExampleHint, memoryStats,
163
+ openPersistedStore, exportFactsJsonl, researchedFactRows,
164
+ splitSentences: splitSentencesPreservingPaths,
165
+ },
166
+ });
@@ -22,6 +22,8 @@ import * as source from "../../adapters/source.mjs";
22
22
  import { computeCodeExplorerData, computeCodeLedger, edgePhrase } from "../../services/code-explorer-viz.mjs";
23
23
  import { generateCodeHints } from "../../domain/code-explorer-hints.mjs";
24
24
  import { createTurnSession } from "./turn-session.mjs";
25
+ import { publishTmctSurface } from "./tmct-surface.mjs";
26
+ import { graphAsk, enginePlan } from "./engine-surface.mjs";
25
27
  import { tmct_ask } from "../../tools/handlers/tmct-ask.mjs";
26
28
  import { RELATIONS } from "../../domain/ask-vocab.mjs";
27
29
 
@@ -37,7 +39,7 @@ import { RELATIONS } from "../../domain/ask-vocab.mjs";
37
39
  * payload carries the starter vocabulary. Teaches land in the same in-memory
38
40
  * store, so a taught fact never touches disk.
39
41
  *
40
- * Returns { memoryDir, sessionId, turn }, the createChatSession shape the
42
+ * Returns { memoryDir, sessionId, graph, turn }, the createChatSession shape the
41
43
  * page's chat expects.
42
44
  */
43
45
  export function createCodeExplorerSession({ graphPayload, seedPayload = null, vocabSeeded = false } = {}) {
@@ -69,6 +71,7 @@ export function createCodeExplorerSession({ graphPayload, seedPayload = null, vo
69
71
  return {
70
72
  memoryDir,
71
73
  sessionId,
74
+ graph,
72
75
  turn: turnSession.turn,
73
76
  };
74
77
  }
@@ -223,17 +226,27 @@ export function askRelatedFacts(graphPayload, term) {
223
226
  return { term, rows, asked, grounded };
224
227
  }
225
228
 
226
- // Exposed for the page's inline client: the re-derivation helpers so a graph
227
- // swapped through the desktop picker re-renders without duplicating logic, the
228
- // engine's own answer to "what relates to the focus", plus the wink loader hook
229
- // registerWinkModel and normFactTerm the chat shares with the ledger page.
230
- globalThis.tmctCodeExplorer = {
231
- createCodeExplorerSession,
232
- askRelatedFacts,
233
- computeCodeExplorerData,
234
- computeCodeLedger,
235
- generateCodeHints,
236
- parseEntities,
237
- normFactTerm,
238
- registerWinkModel,
239
- };
229
+ // This page holds a REAL code graph, so `tmct.ask` is the engine answering
230
+ // over it the same round trip askRelatedFacts already makes twice per
231
+ // relation kind to build the sidebar's rows.
232
+ //
233
+ // `tmct.page` keeps the re-derivation helpers a graph swapped through the
234
+ // desktop picker re-renders with, plus the wink and term-normalizing seams the
235
+ // chat shares with the ledger page. `createCodeExplorerSession` sits there
236
+ // too: this page's client script is still one raw-text block, and it reaches
237
+ // its factory through the bag rather than through `tmct.open()`.
238
+ publishTmctSurface({
239
+ open: createCodeExplorerSession,
240
+ ask: graphAsk,
241
+ plan: enginePlan,
242
+ page: {
243
+ createCodeExplorerSession,
244
+ askRelatedFacts,
245
+ computeCodeExplorerData,
246
+ computeCodeLedger,
247
+ generateCodeHints,
248
+ parseEntities,
249
+ normFactTerm,
250
+ registerWinkModel,
251
+ },
252
+ });
@@ -0,0 +1,82 @@
1
+ // engine-surface.mjs — the standard `ask` and `plan` routes a browser page
2
+ // wires into publishTmctSurface (tmct-surface.mjs).
3
+ //
4
+ // Both run the SAME machinery the CLI runs, over the session's own in-memory
5
+ // state rather than a repo on disk: `graphAsk` dispatches `tmct_ask` through
6
+ // the tool layer and hands back the structured envelope beside the prose
7
+ // (dispatchToolStructured's `{ content, data }`); `enginePlan` builds the
8
+ // capability planner's context from whatever the session holds and runs one
9
+ // request through it.
10
+ //
11
+ // Kept out of tmct-surface.mjs on purpose. That module is pure wiring and
12
+ // imports nothing, so memory-ask-browser-entry.mjs — the one bundle that is
13
+ // committed and published, and is deliberately small — can publish the same
14
+ // `globalThis.tmct` without pulling the tool layer and the router into its
15
+ // bundle.
16
+ import { dispatchToolStructured } from "../../tools/server.mjs";
17
+ import { capabilityPlanDeps, noCodeGraph } from "../../services/chat.mjs";
18
+
19
+ /**
20
+ * One plain-English question, answered against the graph this session holds,
21
+ * through the same `tmct_ask` tool the CLI dispatches. Returns
22
+ * `{ answer, data, miss }` — `data` is ask()'s own envelope (the parse, the
23
+ * matches it found, the traversal it walked), which is what a panel renders
24
+ * rows from and what makes an answer auditable.
25
+ *
26
+ * A session whose graph is empty answers with an honest miss. That is the real
27
+ * state of a memory-only page: its rows are facts in a store, and ask() reads
28
+ * a graph. spider-fly is the page that closed that gap for its own world, by
29
+ * projecting its live board into a graph (worldRelationGraphPayload) before
30
+ * every turn — the same route is open to any page whose rows have a shape
31
+ * worth asking over.
32
+ */
33
+ export async function graphAsk(request, options, session) {
34
+ const { content, data } = await dispatchToolStructured("tmct_ask", { query: request }, {
35
+ graph: session.graph,
36
+ memoryBackend: session.memoryDir,
37
+ });
38
+ return { answer: content, data, miss: Boolean(data?.miss) };
39
+ }
40
+
41
+ /**
42
+ * One compound or maintenance-goal request, planned and executed over whatever
43
+ * this session holds — the same capability router `/plan` reaches, with the
44
+ * same memory-only mode a page with no code graph needs (a code-graph
45
+ * capability there refuses by naming the graph it hasn't got).
46
+ *
47
+ * Returns the planner's own result: `{ refused, why, driver, calls, composed,
48
+ * observed }`. No prose is composed here — a page that wants the written
49
+ * answer asks for it in words through `tmct.turn("/plan …")`, which is the
50
+ * same split `dispatchTool` and `dispatchToolStructured` already draw.
51
+ */
52
+ export async function enginePlan(request, options, session) {
53
+ const graph = session.graph && !noCodeGraph(session.graph) ? session.graph : null;
54
+ const memoryDir = session.memoryDir ?? null;
55
+ if (!graph && !memoryDir) {
56
+ return {
57
+ refused: true,
58
+ why: "nothing to plan over — this session holds neither a code graph nor a memory store",
59
+ driver: null, calls: [], composed: null, observed: null,
60
+ };
61
+ }
62
+ const { buildCapabilityPlanCtx, runCapabilityPlan, declaredCapabilityNames } =
63
+ await import("../../domain/router/drive.mjs");
64
+ const ctx = await buildCapabilityPlanCtx({
65
+ ...capabilityPlanDeps(), config: null, source: null, graph, memoryDir,
66
+ });
67
+ try {
68
+ const result = await runCapabilityPlan(request, declaredCapabilityNames(), ctx);
69
+ return {
70
+ refused: Boolean(result.refused),
71
+ why: Array.isArray(result.why) ? result.why.join("; ") : (result.why ?? null),
72
+ driver: result.driver ?? null,
73
+ calls: result.calls ?? [],
74
+ composed: result.composed ?? null,
75
+ observed: result.observed ?? null,
76
+ };
77
+ } finally {
78
+ // The taught registrations are per-ctx: dispose them so the next call
79
+ // re-reads the store instead of meeting a stale name collision.
80
+ for (const dispose of ctx.disposers || []) dispose();
81
+ }
82
+ }
@@ -32,6 +32,7 @@ import { loadLexicon } from "../../domain/grammar/lexicon.mjs";
32
32
  import { registerWinkModel, winkInstance } from "../../adapters/wink-model.mjs";
33
33
  import { memoryStats, exportFactsJsonl } from "./memory-stats.mjs";
34
34
  import { openPersistedStore } from "./idb-persist.mjs";
35
+ import { publishTmctSurface } from "./tmct-surface.mjs";
35
36
 
36
37
  // The pronoun subjects a bounded carry substitutes with the last unique
37
38
  // grounded subject in the SAME paragraph. Reset at every blank line, so a
@@ -192,7 +193,15 @@ export function createIngestSession({ seedPayload = null, vocabSeeded = false }
192
193
  };
193
194
  }
194
195
 
195
- globalThis.tmctIngest = {
196
- createIngestSession, groundTextToFacts, exportFactsJsonl, registerWinkModel, normFactTerm,
197
- memoryStats, openPersistedStore,
198
- };
196
+ // This page grounds pasted prose into facts; it holds no conversation, so
197
+ // `tmct.turn` is unwired and says so, and the one call the page makes is
198
+ // `tmct.session.ingest(text)`. `tmct.page` keeps the recognizer entry point
199
+ // the page also drives directly, the wink seam, the stats fold behind its
200
+ // counters, its IndexedDB wrapper and its JSONL export.
201
+ publishTmctSurface({
202
+ open: createIngestSession,
203
+ page: {
204
+ groundTextToFacts, exportFactsJsonl, registerWinkModel, normFactTerm,
205
+ memoryStats, openPersistedStore,
206
+ },
207
+ });
@@ -27,6 +27,8 @@ import { computeLedgerDataFromPayload } from "../../services/ledger-viz.mjs";
27
27
  import { digestTermFromPayloadBrowser } from "./digest-client.mjs";
28
28
  import { createTurnSession } from "./turn-session.mjs";
29
29
  import { exportFactsJsonl } from "./memory-stats.mjs";
30
+ import { publishTmctSurface } from "./tmct-surface.mjs";
31
+ import { graphAsk, enginePlan } from "./engine-surface.mjs";
30
32
 
31
33
  /**
32
34
  * A browser ledger-dock session over the real turn engine — createChatSession's
@@ -35,7 +37,7 @@ import { exportFactsJsonl } from "./memory-stats.mjs";
35
37
  * a fact taught through the dock extends the SAME graph the page renders
36
38
  * from, never a disconnected one.
37
39
  *
38
- * Returns { memoryDir, sessionId, turn }, identical to createChatSession.
40
+ * Returns { memoryDir, sessionId, graph, turn }, identical to createChatSession.
39
41
  * ledger-viz.mjs's own inline script calls computeLedgerDataFromPayload
40
42
  * (re-exported below) on `memoryDir.payload` after a turn whose record shows
41
43
  * a successful write (`via: "assert"` or `via: "retract"`, `miss: false`) to
@@ -52,14 +54,20 @@ export function createLedgerSession({ seedPayload = null, vocabSeeded = false }
52
54
  const sessionId = globalThis.crypto?.randomUUID?.() ?? String(Date.now());
53
55
 
54
56
  const session = createTurnSession({ memoryDir, graph, lexicon, sessionId, vocabHint });
55
- return { memoryDir, sessionId, turn: session.turn };
57
+ return { memoryDir, sessionId, graph, turn: session.turn };
56
58
  }
57
59
 
58
- // Re-exported so ledger-viz.mjs's own inline script never has to duplicate
59
- // the derivation logic that builds rows/terms/edges/contradictions/
60
- // worthALook/stats from a payload the same posture chat-browser-entry.mjs
61
- // takes re-exporting registerWinkModel for its own page's CDN wink load.
62
- // splitSentences + exportFactsJsonl carry the dock's paste-and-drop ingest and
63
- // its JSONL export across the bundle boundary, the same one-serializer posture
64
- // chat-browser-entry.mjs holds for its own page.
65
- globalThis.tmctLedger = { createLedgerSession, computeLedgerDataFromPayload, normFactTerm, registerWinkModel, registerResearchProvider, splitSentences: splitSentencesPreservingPaths, exportFactsJsonl, digestTermFromPayloadBrowser };
60
+ // `tmct.page` keeps what the page's own script renders with and the engine has
61
+ // no plain-English form for: the payload-to-rows/terms/edges/stats derivation
62
+ // the ledger view re-mounts from after a successful teach, the digest reader,
63
+ // the wink and research seams, and the two serializers behind the dock's
64
+ // paste-ingest and JSONL export.
65
+ publishTmctSurface({
66
+ open: createLedgerSession,
67
+ ask: graphAsk,
68
+ plan: enginePlan,
69
+ page: {
70
+ computeLedgerDataFromPayload, normFactTerm, registerWinkModel, registerResearchProvider,
71
+ splitSentences: splitSentencesPreservingPaths, exportFactsJsonl, digestTermFromPayloadBrowser,
72
+ },
73
+ });
@@ -1,20 +1,62 @@
1
1
  // memory-ask-browser-entry.mjs — the esbuild entry for the ledger page's
2
2
  // chat dock (`tmct viz`, src/services/ledger-viz.mjs).
3
3
  //
4
- // Re-exports `factAnswer` (src/services/chat.mjs) and `createInMemoryStore`
5
- // (src/adapters/memory/core.mjs), which lets the dock hand `factAnswer` the page's
6
- // already-embedded payload with zero fs I/O. Called with `envelope: null,
7
- // miss: true` to arm factAnswer's bare-question regex fallbacks directly,
8
- // bypassing the structural-graph parse pipeline this dock has no use for.
4
+ // The one COMMITTED browser bundle: `tmct viz` is a real CLI command run
5
+ // against a user's own local .tmct store, so this ships with the package. It
6
+ // deliberately carries the question lanes only (factAnswer/factReadBack) and
7
+ // not the full turn engine — see ledger-browser-entry.mjs's header for the
8
+ // contrast with the demo site's own 1.5MB dock.
9
+ //
10
+ // It publishes the SAME `globalThis.tmct` every other page does, through
11
+ // tmct-surface.mjs, which imports nothing — so this bundle gets the shared
12
+ // contract without the tool layer or the capability router coming with it.
13
+ // `tmct.turn` and `tmct.plan` are unwired here and say so: this dock asks
14
+ // questions, it does not hold a conversation or plan anything.
9
15
  import { factAnswer, factReadBack } from "../../services/chat.mjs";
10
16
  import { createInMemoryStore, normFactTerm } from "../../adapters/memory/core.mjs";
11
17
  import { digestTermFromPayloadBrowser } from "./digest-client.mjs";
18
+ import { publishTmctSurface } from "./tmct-surface.mjs";
19
+
20
+ /** A question-only session over a payload the page already embeds — no fs I/O,
21
+ * no turn engine. Returns `{ memoryDir }`. */
22
+ export function createMemoryAskSession({ payload = null } = {}) {
23
+ const memoryDir = createInMemoryStore();
24
+ if (payload) memoryDir.payload = payload;
25
+ return { memoryDir };
26
+ }
27
+
28
+ /**
29
+ * One question, answered from the page's own embedded store. Runs the same
30
+ * cascade `runAsk` runs — factAnswer first, then factReadBack, which is where
31
+ * the taught-relation chases live ("who is the grandfather of ishmael") that
32
+ * factAnswer's own lanes don't reach.
33
+ *
34
+ * Both are called with `envelope: null, miss: true`, which arms factAnswer's
35
+ * bare-question fallbacks directly and skips the structural-graph parse
36
+ * pipeline this dock has no use for.
37
+ *
38
+ * Returns `{ answer, data, miss }`. `data` is the reader's own result, so a
39
+ * caller still sees the inferred `goal` line it computed on the way.
40
+ */
41
+ export async function askMemory(request, options, session) {
42
+ let fact = null;
43
+ try { fact = await factAnswer(session.memoryDir, request, null, true, {}); } catch { fact = null; }
44
+ if (!(fact && fact.text)) {
45
+ try { fact = await factReadBack(session.memoryDir, request, null, true, null); } catch { fact = null; }
46
+ }
47
+ if (!(fact && fact.text)) return { answer: null, data: null, miss: true };
48
+ return { answer: fact.text, data: fact, miss: false };
49
+ }
12
50
 
13
- // normFactTerm is re-exported too, for the page's client-side term normalization.
14
- // factReadBack carries the taught-relation chases (grandfather-style questions)
15
- // that factAnswer's own lanes don't reach.
16
- // digestTermFromPayloadBrowser lets the CLI `tmct viz` page read a refocused
17
- // term back as a digest client-side, from the structure table the page embeds
18
- // the same narrative the demo ledger's live bundle produces, over the same
19
- // static store this page already carries.
20
- globalThis.tmctMemoryAsk = { factAnswer, factReadBack, createInMemoryStore, normFactTerm, digestTermFromPayloadBrowser };
51
+ // `tmct.page` keeps the term normalizer the dock refocuses the ledger with,
52
+ // and the client-side digest reader — the narrative the demo ledger's live
53
+ // bundle produces, over the static structure table this page embeds.
54
+ // `fallback`, because the demo ledger page carries this bundle AND the full
55
+ // turn engine at once: this one answers where it is alone, and steps aside
56
+ // wherever the live dock is loaded, whichever order the two arrive in.
57
+ publishTmctSurface({
58
+ open: createMemoryAskSession,
59
+ ask: askMemory,
60
+ page: { normFactTerm, digestTermFromPayloadBrowser },
61
+ fallback: true,
62
+ });