@polycode-projects/the-mechanical-code-talker 4.0.1 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +2 -1
  2. package/corpus/sprites/src/sprite-facts.jsonl +375 -8
  3. package/package.json +1 -1
  4. package/src/adapters/memory/core.mjs +20 -0
  5. package/src/domain/ask-vocab.mjs +71 -0
  6. package/src/domain/ask.mjs +168 -0
  7. package/src/domain/game-config.mjs +11 -0
  8. package/src/domain/mud-facts.mjs +15 -0
  9. package/src/domain/router/drive.mjs +35 -9
  10. package/src/domain/router/registry.mjs +24 -4
  11. package/src/domain/router/resolver.mjs +102 -40
  12. package/src/domain/scene-compose.mjs +117 -0
  13. package/src/domain/spider-fly-world.mjs +36 -0
  14. package/src/domain/sprite-facts.mjs +0 -0
  15. package/src/domain/sprite-request.mjs +156 -0
  16. package/src/domain/sprite-templates.mjs +161 -14
  17. package/src/services/adventure-editor.mjs +8 -14
  18. package/src/services/adventure-viz.mjs +119 -150
  19. package/src/services/adventure.mjs +97 -35
  20. package/src/services/chat-page-viz.mjs +64 -48
  21. package/src/services/chat.mjs +102 -34
  22. package/src/services/code-explorer-viz.mjs +52 -50
  23. package/src/services/ingest-viz.mjs +32 -74
  24. package/src/services/ledger-viz.mjs +87 -70
  25. package/src/services/memory-panel-viz.mjs +38 -0
  26. package/src/services/mud-editor.mjs +10 -15
  27. package/src/services/mud-turn.mjs +6 -6
  28. package/src/services/mud-viz.mjs +119 -225
  29. package/src/services/p2p-room.mjs +90 -23
  30. package/src/services/plan-pddl.mjs +3 -1
  31. package/src/services/plan-viz.mjs +13 -12
  32. package/src/services/research-viz.mjs +25 -67
  33. package/src/services/spider-fly-turn.mjs +14 -22
  34. package/src/services/spider-fly-viz.mjs +97 -136
  35. package/src/services/spider-fly.mjs +69 -11
  36. package/src/services/sprite-catalog-viz.mjs +274 -224
  37. package/src/services/viz-boot.mjs +71 -0
  38. package/src/services/viz-room-graph.mjs +203 -0
  39. package/src/services/viz-theme.mjs +75 -1
  40. package/src/services/viz-ticker.mjs +22 -0
  41. package/src/surfaces/web/adventure-browser-entry.mjs +62 -47
  42. package/src/surfaces/web/chat-browser-entry.mjs +51 -107
  43. package/src/surfaces/web/code-explorer-browser-entry.mjs +192 -35
  44. package/src/surfaces/web/engine-surface.mjs +82 -0
  45. package/src/surfaces/web/ingest-browser-entry.mjs +16 -17
  46. package/src/surfaces/web/ledger-browser-entry.mjs +24 -56
  47. package/src/surfaces/web/memory-ask-browser-entry.mjs +55 -13
  48. package/src/surfaces/web/memory-ask-browser.bundle.js +128 -125
  49. package/src/surfaces/web/memory-stats.mjs +11 -0
  50. package/src/surfaces/web/mud-browser-entry.mjs +70 -49
  51. package/src/surfaces/web/plan-browser-entry.mjs +39 -50
  52. package/src/surfaces/web/research-browser-entry.mjs +48 -46
  53. package/src/surfaces/web/spider-fly-browser-entry.mjs +76 -40
  54. package/src/surfaces/web/sprites-browser-entry.mjs +28 -32
  55. package/src/surfaces/web/tmct-surface.mjs +147 -0
  56. package/src/surfaces/web/turn-session.mjs +124 -0
  57. package/src/tools/definitions.mjs +30 -0
  58. package/src/tools/handlers/index.mjs +6 -3
  59. package/src/tools/handlers/kit.mjs +19 -2
  60. package/src/tools/handlers/tmct-ask.mjs +11 -6
  61. package/src/tools/handlers/tmct-ingest.mjs +5 -1
  62. package/src/tools/handlers/tmct-related.mjs +4 -4
  63. package/src/tools/handlers/tmct-sprite.mjs +147 -0
  64. package/src/tools/memory-fallthrough.mjs +9 -2
  65. package/src/tools/server.mjs +37 -6
@@ -1,14 +1,19 @@
1
1
  // tmct_ask — a plain-English structural question answered from the graph in one
2
2
  // mechanical, zero-model-call round-trip. See src/domain/ask.mjs.
3
3
 
4
- import { ToolError } from "../../adapters/config.mjs";
5
4
  import { ask } from "../../domain/ask.mjs";
6
- import { requiredArg } from "./kit.mjs";
5
+ import { requiredArg, toolResult } from "./kit.mjs";
6
+
7
+ /** The flat string a dispatchTool caller gets carries the envelope in-band, behind this
8
+ * delimiter, because a string is all that entry can hand back. Exported so the surfaces
9
+ * that still read that string split it on the one constant rather than their own copy. */
10
+ export const ASK_ENVELOPE_DELIM = "\n\n---tmct_ask---\n";
7
11
 
8
12
  export function tmct_ask(args, { graph }) {
9
13
  const { content, tmct_ask: envelope } = ask(graph, requiredArg(args, "query"));
10
- // Every dispatchTool caller (the chat surface, the CLI fallback) expects a plain string —
11
- // append the structured envelope as a delimited, machine-parseable block rather than
12
- // changing that shared contract for one tool.
13
- return `${content}\n\n---tmct_ask---\n${JSON.stringify(envelope, null, 2)}`;
14
+ return toolResult({
15
+ content,
16
+ data: envelope,
17
+ text: `${content}${ASK_ENVELOPE_DELIM}${JSON.stringify(envelope, null, 2)}`,
18
+ });
14
19
  }
@@ -16,6 +16,7 @@
16
16
 
17
17
  import { dirname } from "node:path";
18
18
  import { openConfiguredMemoryBackend } from "../../adapters/memory/core.mjs";
19
+ import { toolResult } from "./kit.mjs";
19
20
 
20
21
  export async function tmct_ingest(args, { config, ingest }) {
21
22
  const text = String(args?.text ?? "");
@@ -31,7 +32,10 @@ export async function tmct_ingest(args, { config, ingest }) {
31
32
  const header = `${result.sentences} sentence(s), ${result.recognized} recognized`
32
33
  + (optimistic ? `, ${result.optimistic.length} optimistic candidate(s)` : "")
33
34
  + `, ${result.skipped} skipped — ${grounded} fact(s) grounded.`;
34
- return grounded ? `${header}\n${result.canonical.join("\n")}` : header;
35
+ return toolResult({
36
+ content: grounded ? `${header}\n${result.canonical.join("\n")}` : header,
37
+ data: { ...result, grounded },
38
+ });
35
39
  } finally {
36
40
  await close();
37
41
  }
@@ -6,11 +6,11 @@
6
6
  import { ToolError } from "../../adapters/config.mjs";
7
7
  import { relatedForTerm } from "../../domain/skos-view.mjs";
8
8
  import { memoryFactRows } from "../memory-fallthrough.mjs";
9
- import { requiredArg } from "./kit.mjs";
9
+ import { requiredArg, toolResult } from "./kit.mjs";
10
10
 
11
- export async function tmct_related(args, { config }) {
11
+ export async function tmct_related(args, { config, memoryBackend = null }) {
12
12
  const term = requiredArg(args, "term");
13
- const hit = relatedForTerm(await memoryFactRows(config), term);
13
+ const hit = relatedForTerm(await memoryFactRows(config, memoryBackend), term);
14
14
  if (!hit) {
15
15
  throw new ToolError(
16
16
  `no synonym or related facts for "${term}" in the memory graph. ` +
@@ -21,7 +21,7 @@ export async function tmct_related(args, { config }) {
21
21
  if (hit.synonyms.length) lines.push(`synonyms (skos:altLabel): ${hit.synonyms.join(", ")}`);
22
22
  if (hit.related.length) lines.push(`related (skos:related): ${hit.related.map((c) => c.prefLabel).join(", ")}`);
23
23
  lines.push("(from the memory graph's mgx:synonym / mgx:relatedTo / mgx:similarTo facts)");
24
- return lines.join("\n");
24
+ return toolResult({ content: lines.join("\n"), data: hit });
25
25
  }
26
26
 
27
27
  // The memory graph is this tool's only source — it answers with or without a
@@ -0,0 +1,147 @@
1
+ // tmct_sprite — the sprite markup for a class, resolved through the memory
2
+ // graph's own rdfs:subClassOf taxonomy, with the expression and size the caller
3
+ // asked for and the chain the resolver walked to find it.
4
+ //
5
+ // The resolution itself is sprite-request.mjs's pure core (the same function the
6
+ // spider-and-fly page splices into its script), so this module only does the
7
+ // three things a tool has to: read the real state, hold the miss wall, and say
8
+ // the answer twice — once as a sentence, once as data.
9
+ //
10
+ // The MISS WALL, since the resolver underneath never refuses (it falls through
11
+ // to the root sprite by design, which is right for a page painting a board and
12
+ // wrong for a question):
13
+ // - an expression outside sprite-expressions.mjs's palette, or a size outside
14
+ // sprite-size.mjs's scale table, is refused before anything is resolved;
15
+ // - a class no term of whose ancestor chain carries a sprite is refused rather
16
+ // than answered with the generic root sprite;
17
+ // - an expression the resolved template does not actually take is refused
18
+ // rather than silently dropped from an otherwise plausible answer.
19
+ //
20
+ // WHICH SENSE OF "LARGE" `size` CARRIES: the taught size PROPERTY, resolved to a
21
+ // numeric render scale (sprite-size.mjs's `sizeScaleFor` — small 0.8, large 1.3,
22
+ // long 1.2, tall 1.25), not the template TIER. Two reasons. It is a fact-shaped
23
+ // question: `mgx:hasProperty large` is a real predicate the memory store can
24
+ // carry and the resolver already consults, so the tool's `memoryFacts`
25
+ // precondition and its resolution chain stay one mechanism, where a tier is the
26
+ // caller's render target rather than anything true of the thing being drawn.
27
+ // And the tier a request could name is a packaging accident: data/sprites-large/
28
+ // is excluded from the npm package, so a tier-selecting `size` would answer
29
+ // differently depending on how tmct was installed, while a scale multiplier
30
+ // answers the same everywhere. The tier in play is still REPORTED in `data.tier`
31
+ // — observed, never asked for.
32
+
33
+ import { ToolError } from "../../adapters/config.mjs";
34
+ import { classAncestorChain, SPRITE_REGISTRY } from "../../domain/sprite-map.mjs";
35
+ import { resolveSpriteAsset } from "../../domain/sprite-templates.mjs";
36
+ import { EXPRESSION_PALETTE } from "../../domain/sprite-expressions.mjs";
37
+ import { sizeScaleFor } from "../../domain/sprite-size.mjs";
38
+ import { resolveSpriteRequest } from "../../domain/sprite-request.mjs";
39
+ import { ICON_TIER_NAME, SPRITE_TIER_NAME } from "../../domain/sprite-facts.mjs";
40
+ import { readSpriteTemplateFiles } from "../../adapters/corpus/sprite-template-files.mjs";
41
+ import { readSpriteLargeTemplateFiles } from "../../adapters/corpus/sprite-large-template-files.mjs";
42
+ import { memoryFactRows } from "../memory-fallthrough.mjs";
43
+ import { requiredArg, toolResult } from "./kit.mjs";
44
+
45
+ /** The property predicate sprite-size.mjs reads a size word off. Used to PROBE
46
+ * its own closed scale table rather than restate it: a word is a size word
47
+ * exactly when it moves the scale, so a word that module gains is a word this
48
+ * tool gains. */
49
+ const SIZE_FACT_PREDICATE = "mgx:hasProperty";
50
+
51
+ /** The expression words a sprite can be asked for — sprite-expressions.mjs's own
52
+ * palette, sorted, never a hand-kept copy. */
53
+ export const spriteExpressionWords = () => Object.keys(EXPRESSION_PALETTE).sort();
54
+
55
+ /** True iff `word` is a size sprite-size.mjs's scale table recognises. */
56
+ export const isSpriteSizeWord = (word) =>
57
+ Boolean(word) && sizeScaleFor([{ predicate: SIZE_FACT_PREDICATE, object: String(word) }]) !== 1;
58
+
59
+ /** The template set to resolve against: the sprite tier when this installation
60
+ * can read it (a git checkout, or the demo site's own build), the icon tier
61
+ * otherwise. Reported alongside the answer so a caller never has to guess which
62
+ * one it got. */
63
+ function templateTier() {
64
+ const large = readSpriteLargeTemplateFiles();
65
+ if (large.length) return { templates: large, tier: SPRITE_TIER_NAME };
66
+ return { templates: readSpriteTemplateFiles(), tier: ICON_TIER_NAME };
67
+ }
68
+
69
+ function chainSentence(chain) {
70
+ return chain.map((step) => step.term).join(" -> ");
71
+ }
72
+
73
+ export async function tmct_sprite(args, { config, memoryBackend = null }) {
74
+ const className = requiredArg(args, "class").toLowerCase();
75
+ const expression = String(args?.expression || "").trim().toLowerCase();
76
+ const size = String(args?.size || "").trim().toLowerCase();
77
+
78
+ if (expression && !Object.hasOwn(EXPRESSION_PALETTE, expression)) {
79
+ throw new ToolError(
80
+ `"${expression}" is not a sprite expression. The palette holds: ${spriteExpressionWords().join(", ")}.`,
81
+ );
82
+ }
83
+ if (size && !isSpriteSizeWord(size)) {
84
+ throw new ToolError(
85
+ `"${size}" is not a size the sprite scale recognises — it reads a taught mgx:hasProperty size word off the individual, and has no entry for this one.`,
86
+ );
87
+ }
88
+
89
+ const { templates, tier } = templateTier();
90
+ const factRows = await memoryFactRows(config, memoryBackend);
91
+ const resolution = resolveSpriteRequest({ class: className, expression, size }, {
92
+ factRows,
93
+ templates,
94
+ spriteRegistry: SPRITE_REGISTRY,
95
+ resolveSpriteAsset,
96
+ classAncestorChain,
97
+ sizeScaleFor,
98
+ expressionPalette: EXPRESSION_PALETTE,
99
+ });
100
+
101
+ if (!resolution.svg || resolution.fellBackToRoot) {
102
+ throw new ToolError(
103
+ `no sprite for "${className}" — neither it nor any rdfs:subClassOf ancestor the memory graph knows ` +
104
+ `(${chainSentence(resolution.chain)}) carries one in the ${tier}. Teach the class its superclass first, or ask for a class the catalog draws.`,
105
+ );
106
+ }
107
+ if (expression && !resolution.expressionApplied) {
108
+ throw new ToolError(
109
+ `the ${tier} draws "${className}" (matched at "${resolution.matched.term}") but that template takes no expression, ` +
110
+ `so "${expression}" would not show. Ask for the plain sprite, or a class whose template carries an emotion parameter.`,
111
+ );
112
+ }
113
+
114
+ const asked = [expression ? `expression ${expression}` : "", size ? `size ${size}` : ""].filter(Boolean);
115
+ const lines = [
116
+ `sprite for "${className}"${asked.length ? ` (${asked.join(", ")})` : ""}, from the ${tier}`,
117
+ resolution.matched.hops === 0
118
+ ? `matched at "${resolution.matched.term}", the class itself, via its own ${resolution.matched.via}`
119
+ : `matched at "${resolution.matched.term}", ${resolution.matched.hops} hop(s) up the ancestor chain, via its ${resolution.matched.via}`,
120
+ `ancestor chain walked: ${chainSentence(resolution.chain)}`,
121
+ ];
122
+ if (resolution.matched.template?.parameters.length) {
123
+ lines.push(`template parameters: ${resolution.matched.template.parameters.join(", ")}`);
124
+ }
125
+ if (size) lines.push(`render scale: ${resolution.scale} (from the taught mgx:hasProperty "${size}")`);
126
+ lines.push(`${resolution.svg.length} characters of SVG markup`);
127
+
128
+ return toolResult({
129
+ content: lines.join("\n"),
130
+ data: {
131
+ class: resolution.class,
132
+ expression: resolution.expression,
133
+ size: resolution.size,
134
+ tier,
135
+ scale: resolution.scale,
136
+ svg: resolution.svg,
137
+ chain: resolution.chain,
138
+ matched: resolution.matched,
139
+ expressionApplied: resolution.expressionApplied,
140
+ },
141
+ });
142
+ }
143
+
144
+ // The sprite catalog and the conversational-memory taxonomy are this tool's only
145
+ // sources — it answers with or without a code-map graph, so the shared code-graph
146
+ // load (which refuses an empty graph) must not gate it.
147
+ tmct_sprite.ownsGraphLoad = true;
@@ -17,9 +17,16 @@ const MEMORY_LIST_CAP = 40;
17
17
  * the dir that CONTAINS .tmct/ (graphFile = <repo>/.tmct/graph.json). The read goes through
18
18
  * the repo's CONFIGURED memory backend — the same store chat's taught facts land in — opened
19
19
  * fresh per call and closed before returning, never the retired flat-file store off a raw
20
- * repo path. */
21
- export async function memoryFactRows(config) {
20
+ * repo path.
21
+ *
22
+ * `memoryBackend` is dispatchTool's seam for a caller that already holds an open store
23
+ * handle (a chat session's own `memoryDir`). When it is supplied the read goes straight to
24
+ * that handle and the caller keeps ownership of closing it. That is the only route to the
25
+ * store for a session whose store was never derived from a config at all — a browser page's
26
+ * in-memory one, where `config` is null and deriving a backend would find nothing. */
27
+ export async function memoryFactRows(config, memoryBackend = null) {
22
28
  try {
29
+ if (memoryBackend) return readFactRows(await loadMemory(memoryBackend));
23
30
  const { dir, close } = await openConfiguredMemoryBackend(dirname(dirname(config.graphFile)));
24
31
  try {
25
32
  return readFactRows(await loadMemory(dir));
@@ -9,6 +9,10 @@
9
9
  // typed service object (the Repository Interface) and hands both to the handler.
10
10
  // Each tool answers one question in ONE compact call so the caller need not
11
11
  // Read/Grep. Errors reach the caller as clean tool errors — message only, never a stack.
12
+ //
13
+ // Two entries, one dispatch: dispatchTool hands back the caller-facing string, and
14
+ // dispatchToolStructured hands back { content, data } for a caller that wants the
15
+ // answer's structure rather than its sentence (a page rendering rows, the router).
12
16
 
13
17
  import { readFile } from "node:fs/promises";
14
18
  import { dirname } from "node:path";
@@ -19,6 +23,7 @@ import { ask } from "../domain/ask.mjs";
19
23
  import { createGraphService } from "../adapters/providers/graph-service.mjs";
20
24
  import { loadGraph } from "./graph-load.mjs";
21
25
  import { HANDLERS } from "./handlers/index.mjs";
26
+ import { isToolResult } from "./handlers/kit.mjs";
22
27
  import { setDefaultNlpAdapter } from "../domain/interpret/nlp-registry.mjs";
23
28
  import { setConstructionBanks } from "../domain/interpret/strategies/constructions.mjs";
24
29
  import { nlpAdapter } from "../adapters/ask-nlp.mjs";
@@ -32,6 +37,7 @@ setConstructionBanks(readConstructionFiles);
32
37
 
33
38
  export { loadGraph } from "./graph-load.mjs";
34
39
  export { buildContextBundle } from "./handlers/tmct-context.mjs";
40
+ export { ASK_ENVELOPE_DELIM } from "./handlers/tmct-ask.mjs";
35
41
 
36
42
  // Tiered tool surface: the hot tools carry full descriptions/schemas in this
37
43
  // catalog; every COLD tool (describe/members/impact/history/…) is still served
@@ -44,7 +50,7 @@ export const TOOLS = HOT_TOOLS.map(({ name, agentDescription, inputSchema }) =>
44
50
  inputSchema,
45
51
  }));
46
52
 
47
- export async function dispatchTool(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 } = {}) {
48
54
  // Reject an unknown tool BEFORE touching the graph — an unknown name never
49
55
  // triggers a load. hasOwn, so an inherited name ("constructor", "toString")
50
56
  // is unknown rather than a callable found on the prototype chain.
@@ -58,12 +64,37 @@ export async function dispatchTool(name, args, { config, source = defaultSource,
58
64
  // conversational memory store (tmct_export) prefers it over re-deriving a
59
65
  // backend from config when one is supplied; every other caller leaves it null
60
66
  // and gets today's re-derive-from-config behaviour unchanged.
61
- if (handle.ownsGraphLoad) return handle(args, { config, source, tel, ingest, memoryBackend });
62
- 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);
63
74
  // repo root = the dir containing .tmct/ (graphFile = <repo>/.tmct/graph.json). Passed to
64
75
  // createGraphService so svc.snippet()/svc.context() are usable directly, and on to the
65
- // handlers that do their own safe source reads.
66
- const repoRoot = dirname(dirname(config.graphFile));
67
- 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 });
68
81
  return handle(args, { graph, svc, config, repoRoot, memoryBackend });
69
82
  }
83
+
84
+ /** The caller-facing string for one tool call. A handler that returns a structured
85
+ * result is flattened to its `text` here, so every existing string caller (the chat
86
+ * surface, the CLI `cli <tool>` route, the HTTP shim) is unaffected by a handler
87
+ * gaining structure. */
88
+ export async function dispatchTool(name, args, ctx = {}) {
89
+ const out = await runHandler(name, args, ctx);
90
+ return isToolResult(out) ? out.text : out;
91
+ }
92
+
93
+ /** The same call, answered as `{ content, data }`: `content` is the prose, `data` the
94
+ * render-ready structure the handler already computed on its way to it. `data` is
95
+ * undefined for a tool whose answer is prose and nothing else, which is most of the
96
+ * cold set — an absent `data` is a real answer, not a failure. */
97
+ export async function dispatchToolStructured(name, args, ctx = {}) {
98
+ const out = await runHandler(name, args, ctx);
99
+ return isToolResult(out) ? { content: out.content, data: out.data } : { content: out, data: undefined };
100
+ }