@polycode-projects/the-mechanical-code-talker 4.1.0 → 4.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +31 -18
  2. package/bin/tmct.mjs +3 -0
  3. package/data/templates/responses.jsonl +3 -0
  4. package/package.json +2 -1
  5. package/src/adapters/memory/core.mjs +1358 -196
  6. package/src/adapters/memory/inspect.mjs +11 -0
  7. package/src/adapters/memory/shacl.mjs +38 -0
  8. package/src/adapters/p2p/webrtc-transport.mjs +28 -5
  9. package/src/domain/ask-vocab.mjs +39 -0
  10. package/src/domain/ask.mjs +183 -34
  11. package/src/domain/grammar/assert.mjs +8 -2
  12. package/src/domain/hanoi-board.mjs +232 -0
  13. package/src/domain/ingest-facts.mjs +120 -0
  14. package/src/domain/interpret/normalize.mjs +49 -0
  15. package/src/domain/memory/compaction.mjs +284 -0
  16. package/src/domain/memory/resolution.mjs +171 -0
  17. package/src/domain/memory/trust.mjs +175 -5
  18. package/src/domain/memory-facts.mjs +139 -0
  19. package/src/domain/p2p/facts.mjs +21 -0
  20. package/src/domain/p2p/peer-id.mjs +15 -0
  21. package/src/domain/p2p/provenance-relabel.mjs +13 -2
  22. package/src/domain/p2p/sync-filter.mjs +5 -1
  23. package/src/domain/p2p/wire.mjs +7 -4
  24. package/src/domain/scene-compose.mjs +2 -2
  25. package/src/domain/sprite-facts.mjs +0 -0
  26. package/src/domain/sprite-request.mjs +1 -1
  27. package/src/services/adventure-viz.mjs +43 -39
  28. package/src/services/adventure.mjs +70 -44
  29. package/src/services/chat-page-viz.mjs +403 -332
  30. package/src/services/chat.mjs +274 -156
  31. package/src/services/code-explorer-viz.mjs +142 -55
  32. package/src/services/index.mjs +1 -1
  33. package/src/services/ingest-viz.mjs +153 -28
  34. package/src/services/ledger-viz.mjs +47 -47
  35. package/src/services/memory-panel-viz.mjs +8 -3
  36. package/src/services/mud-turn.mjs +11 -8
  37. package/src/services/mud-viz.mjs +474 -234
  38. package/src/services/p2p-room.mjs +110 -23
  39. package/src/services/plan-viz.mjs +72 -13
  40. package/src/services/research-viz.mjs +35 -24
  41. package/src/services/share-overlay-viz.mjs +623 -0
  42. package/src/services/spider-fly-viz.mjs +24 -24
  43. package/src/services/sprite-catalog-viz.mjs +307 -82
  44. package/src/surfaces/web/adventure-browser-entry.mjs +46 -25
  45. package/src/surfaces/web/chat-browser-entry.mjs +55 -9
  46. package/src/surfaces/web/code-explorer-browser-entry.mjs +30 -16
  47. package/src/surfaces/web/engine-surface.mjs +82 -0
  48. package/src/surfaces/web/ingest-browser-entry.mjs +81 -11
  49. package/src/surfaces/web/ledger-browser-entry.mjs +47 -14
  50. package/src/surfaces/web/memory-ask-browser-entry.mjs +55 -13
  51. package/src/surfaces/web/memory-ask-browser.bundle.js +149 -116
  52. package/src/surfaces/web/mud-browser-entry.mjs +77 -25
  53. package/src/surfaces/web/p2p-browser-entry.mjs +1 -1
  54. package/src/surfaces/web/plan-browser-entry.mjs +49 -11
  55. package/src/surfaces/web/research-browser-entry.mjs +33 -24
  56. package/src/surfaces/web/spider-fly-browser-entry.mjs +32 -13
  57. package/src/surfaces/web/sprites-browser-entry.mjs +51 -11
  58. package/src/surfaces/web/tmct-surface.mjs +159 -0
  59. package/src/surfaces/web/turn-session.mjs +16 -5
  60. package/src/tools/server.mjs +13 -6
@@ -0,0 +1,159 @@
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
+ // Two more members show up on some pages, by convention rather than by
29
+ // anything this function returns: `tmct.ready` is the page's own boot
30
+ // promise (chat, ingest), and `tmct.lastSave` is its last background-save
31
+ // record, `{at, ms}` (chat, adventure). Neither can be threaded through
32
+ // `publishTmctSurface(...)` itself — boot is still running when this
33
+ // function returns, so the page's own script sets each once it reaches
34
+ // that point (see chat-page-viz.mjs, ingest-viz.mjs, adventure-viz.mjs).
35
+ // Each page's older bare global — window.tmctChatReady, tmctIngestReady,
36
+ // tmctChatLastSave, tmctAdventureLastSave — keeps working unchanged;
37
+ // `tmct.ready` / `tmct.lastSave` just reach the same value under the one
38
+ // `tmct.*` name everything else on this page already uses.
39
+ //
40
+ // `ask` and `plan` are supplied per page rather than fixed here, because what
41
+ // a page can ground a question against genuinely differs: code-explorer holds
42
+ // a code graph, spider-fly holds a live board projected into one, the ledger
43
+ // dock holds a memory store and no graph at all. A page that supplies neither
44
+ // gets an honest refusal naming why, never a guess — the same promise the
45
+ // engine itself makes.
46
+
47
+ const NO_SESSION = "no session is open on this page yet — await tmct.open() first";
48
+
49
+ // The demo ledger page carries TWO bundles at once: the committed
50
+ // question-only one inlined into the HTML (memory-ask-browser-entry.mjs) and
51
+ // the demo site's own full turn engine as a separate script tag
52
+ // (ledger-browser-entry.mjs). Both publish this surface, and which one the
53
+ // browser runs first is not something either can promise. So the
54
+ // question-only one publishes as a `fallback` and the full engine always
55
+ // wins, whichever order they load in. Registered globally rather than
56
+ // module-locally because the two bundles carry their own copy of this module.
57
+ const FALLBACK = Symbol.for("tmct.surface.fallback");
58
+
59
+ /**
60
+ * `publishTmctSurface({ open, ask, plan, turn, page, target })` installs
61
+ * `target.tmct` (default `globalThis`) and returns it.
62
+ *
63
+ * `open(...args)` is the page's own session factory and is the only required
64
+ * argument — `createChatSession`, `createMudSession`, `createPlanSession` and
65
+ * the rest, called with whatever arguments they already take. Its result
66
+ * becomes `tmct.session`.
67
+ *
68
+ * `turn(line, options, session)` (optional) overrides how a line reaches the
69
+ * session. The default calls `session.turn(line, options)`, which is every
70
+ * page but mud — mud runs several characters over one world, so it routes on
71
+ * `{ as: character }` to that character's own window.
72
+ *
73
+ * `ask(request, options, session)` and `plan(request, options, session)`
74
+ * (optional) are the page's grounded-question and capability-planner routes.
75
+ * Left out, the matching verb refuses and says which route the page does have.
76
+ * Every verb takes the same `(what, options)` shape, so the research page's
77
+ * source-scoped `tmct.ask(q, { sources })` reads like the plan page's
78
+ * `tmct.turn("solve it", { maxDepth })`.
79
+ *
80
+ * `page` is the residual bag: this page's own rendering helpers, reachable as
81
+ * `tmct.page.*`.
82
+ *
83
+ * `fallback: true` publishes only where no fuller surface is standing, and
84
+ * yields to one that arrives later — the ledger page's two bundles.
85
+ */
86
+ export function publishTmctSurface({
87
+ open, ask = null, plan = null, turn = null, page = {}, fallback = false, target = globalThis,
88
+ } = {}) {
89
+ if (typeof open !== "function") {
90
+ throw new Error("publishTmctSurface: `open` must be this page's session factory");
91
+ }
92
+ const standing = target.tmct;
93
+ if (fallback && standing && !standing[FALLBACK]) return standing;
94
+ let session = null;
95
+
96
+ const liveSession = () => {
97
+ if (!session) throw new Error(NO_SESSION);
98
+ return session;
99
+ };
100
+
101
+ const surface = {
102
+ // Variadic, so each page's factory keeps the signature it already has —
103
+ // adventure and mud both take the world payload first, then their options.
104
+ async open(...args) {
105
+ session = await open(...args);
106
+ return session;
107
+ },
108
+
109
+ get session() { return session; },
110
+
111
+ async turn(line, options) {
112
+ const live = liveSession();
113
+ if (turn) return turn(line, options, live);
114
+ // Not every page holds a conversation. The ingest page grounds pasted
115
+ // prose into facts and answers nothing, so it says so rather than
116
+ // failing on a method it never had.
117
+ if (typeof live.turn !== "function") {
118
+ return {
119
+ answer: "this page runs no conversational turn — see tmct.session for what it does run",
120
+ end: false, record: null, plan: null, research: null,
121
+ };
122
+ }
123
+ return live.turn(line, options);
124
+ },
125
+
126
+ async ask(request, options) {
127
+ if (!ask) {
128
+ return {
129
+ answer: "this page has no separate ask route — it answers questions through tmct.turn()",
130
+ data: undefined,
131
+ miss: true,
132
+ };
133
+ }
134
+ return ask(request, options, liveSession());
135
+ },
136
+
137
+ async plan(request, options) {
138
+ if (!plan) {
139
+ return {
140
+ refused: true,
141
+ why: "this page's bundle carries no capability planner",
142
+ driver: null, calls: [], composed: null, observed: null,
143
+ };
144
+ }
145
+ return plan(request, options, liveSession());
146
+ },
147
+
148
+ page,
149
+
150
+ /** True where this is the question-only stand-in rather than a page's full
151
+ * engine. The ledger page reads it to tell its two docks apart: the live
152
+ * one teaches, the stand-in only answers. */
153
+ fallback,
154
+ };
155
+
156
+ surface[FALLBACK] = fallback;
157
+ target.tmct = surface;
158
+ return surface;
159
+ }
@@ -9,8 +9,15 @@
9
9
  // capture, and a return shape covering every field one caller or another
10
10
  // reads back.
11
11
  //
12
+ // Every caller of this wrapper is a page by construction, so `uiContext:
13
+ // "browser"` is a DEFAULT here rather than something each entry remembers to
14
+ // pass. It is what makes the engine's dead-ends name an exit a page can take
15
+ // ("teach me a fact") instead of the CLI's `tmct index`/`--repo`/`tmct init`.
16
+ // Five of the nine already passed it and four had never got round to it, which
17
+ // is exactly the drift a shared default removes.
18
+ //
12
19
  // The nine differ in what they hand `runTurn` beyond the common core
13
- // (`uiContext`, `synthesisBudget`, `gameConfig`, `researchConfig`,
20
+ // (`synthesisBudget`, `gameConfig`, `researchConfig`,
14
21
  // `actingSubject`, a per-character `sessionId`...) and in what they do with a
15
22
  // turn's result besides the standard focus/last/planState/researchState fold
16
23
  // (sync an externally-held plan holder, grow a visited-rooms set, bump a
@@ -41,7 +48,7 @@ function turnErrorFallback(message) {
41
48
  *
42
49
  * `buildExtraOptions(state, callArgs)` (optional) returns extra `runTurn`
43
50
  * options to merge OVER the defaults below — anything a specific page needs
44
- * (`uiContext: "browser"`, `synthesisBudget`, `gameConfig`, `researchConfig`,
51
+ * (`synthesisBudget`, `gameConfig`, `researchConfig`,
45
52
  * a per-character `actingSubject`/`sessionId`, or a `planState` read from an
46
53
  * external holder instead of this closure's own). `state` is
47
54
  * `{ focus, last, planState, researchState }` as this turn is about to run;
@@ -55,8 +62,11 @@ function turnErrorFallback(message) {
55
62
  * grow a visited-rooms set, bump a turn tally). Never runs on a throw: none
56
63
  * of the nine originals touch their own state when `runTurn` itself failed.
57
64
  *
58
- * Returns `{ memoryDir, sessionId, turn, focus, last, planState,
59
- * setPlanState, researchState, setResearchState }`. `turn(line, callArgs)`
65
+ * Returns `{ memoryDir, sessionId, graph, turn, focus, last, planState,
66
+ * setPlanState, researchState, setResearchState }`. `graph` is the one this
67
+ * session was built over, exposed so a caller can put a question to the same
68
+ * graph its turns run against (engine-surface.mjs's `graphAsk`) without
69
+ * holding a second reference to it. `turn(line, callArgs)`
60
70
  * resolves to `{ answer, end, record, plan, research }` — the union of every
61
71
  * field any of the nine originals reads; a caller ignores what it doesn't
62
72
  * need. `research` is passed through undefined/null/a queue snapshot exactly
@@ -85,7 +95,7 @@ export function createTurnSession({
85
95
  try {
86
96
  result = await runTurn(line, {
87
97
  config: null, source: null, graph, focus, last, memoryDir, sessionId,
88
- env: {}, lexicon, vocabHint, planState, researchState,
98
+ env: {}, lexicon, vocabHint, planState, researchState, uiContext: "browser",
89
99
  ...extra,
90
100
  });
91
101
  } catch (e) {
@@ -109,6 +119,7 @@ export function createTurnSession({
109
119
  return {
110
120
  memoryDir,
111
121
  sessionId,
122
+ graph,
112
123
  get focus() { return focus; },
113
124
  get last() { return last; },
114
125
  get planState() { return planState; },
@@ -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
- const graph = await loadGraph(config, source);
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
- const repoRoot = dirname(dirname(config.graphFile));
73
- const svc = createGraphService(graph, { sourceAccess: true, repoRoot, readFile, tel, ask });
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