@polycode-projects/the-mechanical-code-talker 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -9
- package/corpus/reference/index.json.gz +0 -0
- package/corpus/reference/manifest.json +8 -8
- package/corpus/reference/shards/ref-00.jsonl.gz +0 -0
- package/corpus/sprites/src/sprite-facts.jsonl +401 -0
- package/corpus/worlds/index.json.gz +0 -0
- package/corpus/worlds/manifest.json +49 -9
- package/corpus/worlds/shards/greyvale-museum.jsonl.gz +0 -0
- package/corpus/worlds/shards/lantern-cottage.jsonl.gz +0 -0
- package/corpus/worlds/shards/mud-hollow.jsonl.gz +0 -0
- package/corpus/worlds/shards/mud-warren.jsonl.gz +0 -0
- package/corpus/worlds/shards/spider-fly.jsonl.gz +0 -0
- package/corpus/worlds/src/greyvale-museum.jsonl +136 -0
- package/corpus/worlds/src/lantern-cottage.jsonl +60 -0
- package/corpus/worlds/src/mud-hollow.jsonl +82 -0
- package/corpus/worlds/src/mud-warren.jsonl +124 -0
- package/corpus/worlds/src/spider-fly.jsonl +1 -1
- package/data/sprites/animal-icon.toml +11 -5
- package/data/sprites/book-icon.toml +9 -5
- package/data/sprites/cabinet-icon.toml +10 -5
- package/data/sprites/cellar-icon.toml +16 -6
- package/data/sprites/container-icon.toml +7 -3
- package/data/sprites/desk-icon.toml +8 -5
- package/data/sprites/dog-icon.toml +8 -5
- package/data/sprites/dog-with-colour-icon.toml +13 -12
- package/data/sprites/drawing-room-icon.toml +14 -5
- package/data/sprites/egg-icon.toml +6 -3
- package/data/sprites/fly-icon.toml +10 -5
- package/data/sprites/furniture-icon.toml +5 -3
- package/data/sprites/garden-icon.toml +10 -3
- package/data/sprites/key-icon.toml +3 -1
- package/data/sprites/kitchen-icon.toml +15 -6
- package/data/sprites/lamp-icon.toml +9 -4
- package/data/sprites/letter-icon.toml +7 -4
- package/data/sprites/library-icon.toml +14 -3
- package/data/sprites/pan-icon.toml +7 -3
- package/data/sprites/person-icon.toml +6 -2
- package/data/sprites/poodle-icon.toml +1 -1
- package/data/sprites/portable-icon.toml +6 -4
- package/data/sprites/portrait-icon.toml +7 -4
- package/data/sprites/room-icon.toml +11 -2
- package/data/sprites/spider-icon.toml +9 -3
- package/data/sprites/study-icon.toml +10 -2
- package/package.json +5 -4
- package/src/adapters/memory/core.mjs +20 -0
- package/src/domain/ask-vocab.mjs +71 -0
- package/src/domain/ask.mjs +168 -0
- package/src/domain/game-config.mjs +11 -0
- package/src/domain/grammar/ace.mjs +40 -4
- package/src/domain/grammar/lexicon-core.json +2 -1
- package/src/domain/grammar/lexicon.mjs +18 -0
- package/src/domain/mud-facts.mjs +15 -0
- package/src/domain/reference-pack.mjs +31 -7
- package/src/domain/router/drive.mjs +35 -9
- package/src/domain/router/registry.mjs +24 -4
- package/src/domain/router/resolver.mjs +102 -40
- package/src/domain/scene-compose.mjs +117 -0
- package/src/domain/spider-fly-world.mjs +37 -1
- package/src/domain/sprite-facts.mjs +0 -0
- package/src/domain/sprite-request.mjs +156 -0
- package/src/domain/sprite-templates.mjs +169 -20
- package/src/services/adventure-editor.mjs +8 -14
- package/src/services/adventure-viz.mjs +209 -157
- package/src/services/adventure.mjs +526 -391
- package/src/services/chat-page-viz.mjs +69 -25
- package/src/services/chat.mjs +200 -51
- package/src/services/code-explorer-viz.mjs +102 -62
- package/src/services/extract-facts.mjs +4 -7
- package/src/services/ingest-viz.mjs +68 -82
- package/src/services/ledger-viz.mjs +136 -67
- package/src/services/memory-panel-viz.mjs +62 -0
- package/src/services/mud-editor.mjs +10 -15
- package/src/services/mud-turn.mjs +6 -6
- package/src/services/mud-viz.mjs +1016 -208
- package/src/services/p2p-room.mjs +90 -23
- package/src/services/plan-pddl.mjs +3 -1
- package/src/services/plan-viz.mjs +123 -64
- package/src/services/research-viz.mjs +160 -108
- package/src/services/spider-fly-turn.mjs +15 -23
- package/src/services/spider-fly-viz.mjs +146 -161
- package/src/services/spider-fly.mjs +69 -11
- package/src/services/sprite-catalog-viz.mjs +414 -240
- package/src/services/viz-boot.mjs +71 -0
- package/src/services/viz-room-graph.mjs +203 -0
- package/src/services/viz-theme.mjs +90 -1
- package/src/services/viz-ticker.mjs +22 -0
- package/src/surfaces/web/adventure-browser-entry.mjs +49 -33
- package/src/surfaces/web/chat-browser-entry.mjs +30 -105
- package/src/surfaces/web/code-explorer-browser-entry.mjs +168 -24
- package/src/surfaces/web/ingest-browser-entry.mjs +3 -13
- package/src/surfaces/web/ledger-browser-entry.mjs +7 -47
- package/src/surfaces/web/memory-ask-browser.bundle.js +127 -124
- package/src/surfaces/web/memory-stats.mjs +11 -0
- package/src/surfaces/web/mud-browser-entry.mjs +71 -29
- package/src/surfaces/web/plan-browser-entry.mjs +22 -40
- package/src/surfaces/web/research-browser-entry.mjs +26 -41
- package/src/surfaces/web/spider-fly-browser-entry.mjs +45 -28
- package/src/surfaces/web/sprites-browser-entry.mjs +14 -27
- package/src/surfaces/web/turn-session.mjs +120 -0
- package/src/tools/definitions.mjs +30 -0
- package/src/tools/handlers/index.mjs +6 -3
- package/src/tools/handlers/kit.mjs +19 -2
- package/src/tools/handlers/tmct-ask.mjs +11 -6
- package/src/tools/handlers/tmct-ingest.mjs +5 -1
- package/src/tools/handlers/tmct-related.mjs +4 -4
- package/src/tools/handlers/tmct-sprite.mjs +147 -0
- package/src/tools/memory-fallthrough.mjs +9 -2
- package/src/tools/server.mjs +25 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// turn-session.mjs — the turn-dispatch wrapper nine near-identical browser
|
|
2
|
+
// sessions each hand-rolled (chat-browser-entry.mjs, code-explorer-browser-
|
|
3
|
+
// entry.mjs, ledger-browser-entry.mjs, adventure-browser-entry.mjs, spider-
|
|
4
|
+
// fly-browser-entry.mjs, plan-browser-entry.mjs, research-browser-entry.mjs,
|
|
5
|
+
// mud-browser-entry.mjs, sprites-browser-entry.mjs): a closure over
|
|
6
|
+
// focus/last/planState/researchState, a `try { runTurn(...) } catch` that
|
|
7
|
+
// turns a throw into a safe answer instead of ending the session (the page
|
|
8
|
+
// has no other chance to show that turn's reply), the post-call state
|
|
9
|
+
// capture, and a return shape covering every field one caller or another
|
|
10
|
+
// reads back.
|
|
11
|
+
//
|
|
12
|
+
// The nine differ in what they hand `runTurn` beyond the common core
|
|
13
|
+
// (`uiContext`, `synthesisBudget`, `gameConfig`, `researchConfig`,
|
|
14
|
+
// `actingSubject`, a per-character `sessionId`...) and in what they do with a
|
|
15
|
+
// turn's result besides the standard focus/last/planState/researchState fold
|
|
16
|
+
// (sync an externally-held plan holder, grow a visited-rooms set, bump a
|
|
17
|
+
// per-character turn tally). `buildExtraOptions`/`captureExtraState` below
|
|
18
|
+
// are exactly those two seams — everything else is common enough to live
|
|
19
|
+
// here once.
|
|
20
|
+
//
|
|
21
|
+
// The catch fallback text converges on the FULLEST of the nine: several
|
|
22
|
+
// callers had already drifted to a shorter one (mud-browser-entry.mjs drops
|
|
23
|
+
// "or /help"; research-browser-entry.mjs drops "or /help" AND "answering
|
|
24
|
+
// that") — proof, not just a risk, that duplicating this string invites
|
|
25
|
+
// exactly this drift.
|
|
26
|
+
import { runTurn as defaultRunTurn } from "../../services/chat.mjs";
|
|
27
|
+
|
|
28
|
+
function turnErrorFallback(message) {
|
|
29
|
+
return `Something went wrong answering that (${message}). Try rephrasing, or /help.`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* `createTurnSession({ memoryDir, graph, lexicon, sessionId, vocabHint,
|
|
34
|
+
* buildExtraOptions, captureExtraState })` — a live turn-dispatch session
|
|
35
|
+
* over the real engine (`runTurn`), threading focus/last/planState/
|
|
36
|
+
* researchState across calls the way every browser chat dock does.
|
|
37
|
+
*
|
|
38
|
+
* `vocabHint` is the session-lifetime hint every caller computes once at
|
|
39
|
+
* creation (a fixed string, or `vocabExampleHint(vocabSeeded)`'s result) —
|
|
40
|
+
* never recomputed per turn.
|
|
41
|
+
*
|
|
42
|
+
* `buildExtraOptions(state, callArgs)` (optional) returns extra `runTurn`
|
|
43
|
+
* options to merge OVER the defaults below — anything a specific page needs
|
|
44
|
+
* (`uiContext: "browser"`, `synthesisBudget`, `gameConfig`, `researchConfig`,
|
|
45
|
+
* a per-character `actingSubject`/`sessionId`, or a `planState` read from an
|
|
46
|
+
* external holder instead of this closure's own). `state` is
|
|
47
|
+
* `{ focus, last, planState, researchState }` as this turn is about to run;
|
|
48
|
+
* `callArgs` is whatever extra argument `turn(line, callArgs)` was given
|
|
49
|
+
* (plan-browser-entry.mjs's own per-call `{ maxDepth }` override, for one).
|
|
50
|
+
*
|
|
51
|
+
* `captureExtraState(result, state, callArgs)` (optional, may be async) runs
|
|
52
|
+
* once after a SUCCESSFUL turn, with `state` already folded forward
|
|
53
|
+
* (focus/last/planState/researchState updated) — the caller's own chance to
|
|
54
|
+
* do further bookkeeping (sync an external plan holder, reload memory to
|
|
55
|
+
* grow a visited-rooms set, bump a turn tally). Never runs on a throw: none
|
|
56
|
+
* of the nine originals touch their own state when `runTurn` itself failed.
|
|
57
|
+
*
|
|
58
|
+
* Returns `{ memoryDir, sessionId, turn, focus, last, planState,
|
|
59
|
+
* setPlanState, researchState, setResearchState }`. `turn(line, callArgs)`
|
|
60
|
+
* resolves to `{ answer, end, record, plan, research }` — the union of every
|
|
61
|
+
* field any of the nine originals reads; a caller ignores what it doesn't
|
|
62
|
+
* need. `research` is passed through undefined/null/a queue snapshot exactly
|
|
63
|
+
* as `runTurn` returned it (a caller may distinguish "not a research turn"
|
|
64
|
+
* from "a research turn that just ended").
|
|
65
|
+
*
|
|
66
|
+
* `runTurn` (default: the real engine) is injectable, purely so a test can
|
|
67
|
+
* force the catch path deterministically without needing an input that
|
|
68
|
+
* happens to make the real, deliberately crash-resistant engine throw.
|
|
69
|
+
*/
|
|
70
|
+
export function createTurnSession({
|
|
71
|
+
memoryDir, graph, lexicon, sessionId, vocabHint = "",
|
|
72
|
+
buildExtraOptions = () => ({}),
|
|
73
|
+
captureExtraState = async () => {},
|
|
74
|
+
runTurn = defaultRunTurn,
|
|
75
|
+
} = {}) {
|
|
76
|
+
let focus = null;
|
|
77
|
+
let last = null;
|
|
78
|
+
let planState = null;
|
|
79
|
+
let researchState = null;
|
|
80
|
+
|
|
81
|
+
async function turn(line, callArgs) {
|
|
82
|
+
const before = { focus, last, planState, researchState };
|
|
83
|
+
const extra = buildExtraOptions(before, callArgs) || {};
|
|
84
|
+
let result;
|
|
85
|
+
try {
|
|
86
|
+
result = await runTurn(line, {
|
|
87
|
+
config: null, source: null, graph, focus, last, memoryDir, sessionId,
|
|
88
|
+
env: {}, lexicon, vocabHint, planState, researchState,
|
|
89
|
+
...extra,
|
|
90
|
+
});
|
|
91
|
+
} catch (e) {
|
|
92
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
93
|
+
return { answer: turnErrorFallback(message), end: false, record: null, plan: null, research: null };
|
|
94
|
+
}
|
|
95
|
+
focus = result.focus;
|
|
96
|
+
last = result.last;
|
|
97
|
+
if ("planState" in result) planState = result.planState;
|
|
98
|
+
if ("researchState" in result) researchState = result.researchState;
|
|
99
|
+
await captureExtraState(result, { focus, last, planState, researchState }, callArgs);
|
|
100
|
+
return {
|
|
101
|
+
answer: result.answer,
|
|
102
|
+
end: Boolean(result.end),
|
|
103
|
+
record: result.record ?? null,
|
|
104
|
+
plan: result.plan ?? null,
|
|
105
|
+
research: result.research,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
memoryDir,
|
|
111
|
+
sessionId,
|
|
112
|
+
get focus() { return focus; },
|
|
113
|
+
get last() { return last; },
|
|
114
|
+
get planState() { return planState; },
|
|
115
|
+
setPlanState(next) { planState = next; },
|
|
116
|
+
get researchState() { return researchState; },
|
|
117
|
+
setResearchState(next) { researchState = next; },
|
|
118
|
+
turn,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -20,12 +20,19 @@
|
|
|
20
20
|
// restated here, so a verb the parser learns is a verb the documentation gains.
|
|
21
21
|
|
|
22
22
|
import { RELATIONS, WHERE_MARKERS } from "../domain/ask-vocab.mjs";
|
|
23
|
+
import { EXPRESSION_PALETTE } from "../domain/sprite-expressions.mjs";
|
|
23
24
|
|
|
24
25
|
/** The relation kinds tmct_ask traverses, each with the opening verbs a question can
|
|
25
26
|
* use for it. Read from the parser's RELATIONS table — never a hand-kept copy. */
|
|
26
27
|
export const askLexicon = () =>
|
|
27
28
|
Object.entries(RELATIONS).map(([kind, { verbs }]) => ({ kind, verbs: verbs.slice(0, 4) }));
|
|
28
29
|
|
|
30
|
+
/** The expression words tmct_sprite's schema offers. Read from the face-fragment
|
|
31
|
+
* palette sprite-expressions.mjs already ships — the same derivation askLexicon
|
|
32
|
+
* does for the ask verbs, so an expression the palette gains is an expression
|
|
33
|
+
* the schema gains. */
|
|
34
|
+
export const spriteExpressionEnum = () => Object.keys(EXPRESSION_PALETTE).sort();
|
|
35
|
+
|
|
29
36
|
const symbolArg = (description) => ({
|
|
30
37
|
type: "object",
|
|
31
38
|
required: ["symbol"],
|
|
@@ -193,6 +200,29 @@ export const TOOL_DEFINITIONS = Object.freeze([
|
|
|
193
200
|
},
|
|
194
201
|
example: { term: "sofa" },
|
|
195
202
|
},
|
|
203
|
+
{
|
|
204
|
+
name: "tmct_sprite",
|
|
205
|
+
tier: "cold",
|
|
206
|
+
summary:
|
|
207
|
+
"The sprite markup for a class, resolved up the taught rdfs:subClassOf chain — with the expression and size asked for, and the ancestor chain the resolver walked to find it.",
|
|
208
|
+
inputSchema: {
|
|
209
|
+
type: "object",
|
|
210
|
+
required: ["class"],
|
|
211
|
+
properties: {
|
|
212
|
+
class: { type: "string", description: "The class to draw, e.g. spider. Resolved through the memory graph's own rdfs:subClassOf taxonomy." },
|
|
213
|
+
expression: {
|
|
214
|
+
type: "string",
|
|
215
|
+
enum: spriteExpressionEnum(),
|
|
216
|
+
description: "The face the sprite wears, as an mgx:feels value. A class whose template takes no expression parameter misses rather than dropping it.",
|
|
217
|
+
},
|
|
218
|
+
size: {
|
|
219
|
+
type: "string",
|
|
220
|
+
description: "A taught size property word (mgx:hasProperty), resolved to a numeric render SCALE — the size of the thing drawn, not a choice of template tier.",
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
example: { class: "spider", expression: "happy", size: "large" },
|
|
225
|
+
},
|
|
196
226
|
{
|
|
197
227
|
name: "tmct_architecture",
|
|
198
228
|
tier: "cold",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// The tool-name → handler registry dispatchTool maps over. One module per tool, so a
|
|
2
2
|
// tool's behaviour has exactly one home and this file only wires names to it.
|
|
3
3
|
//
|
|
4
|
-
// A handler takes (args, ctx) and returns the caller-facing string
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// A handler takes (args, ctx) and returns the caller-facing string, or kit.mjs's
|
|
5
|
+
// toolResult({ content, data }) when it already holds the structured form of what that
|
|
6
|
+
// string says. ctx carries the already-loaded { graph, svc, config, repoRoot }. A handler
|
|
7
|
+
// marked `ownsGraphLoad` gets { config, source, tel } instead and loads what it needs.
|
|
7
8
|
|
|
8
9
|
import { tmct_context } from "./tmct-context.mjs";
|
|
9
10
|
import { tmct_context_more } from "./tmct-context-more.mjs";
|
|
@@ -15,6 +16,7 @@ import { tmct_search } from "./tmct-search.mjs";
|
|
|
15
16
|
import { tmct_members } from "./tmct-members.mjs";
|
|
16
17
|
import { tmct_subclasses } from "./tmct-subclasses.mjs";
|
|
17
18
|
import { tmct_related } from "./tmct-related.mjs";
|
|
19
|
+
import { tmct_sprite } from "./tmct-sprite.mjs";
|
|
18
20
|
import { tmct_architecture } from "./tmct-architecture.mjs";
|
|
19
21
|
import { tmct_exports } from "./tmct-exports.mjs";
|
|
20
22
|
import { tmct_untested } from "./tmct-untested.mjs";
|
|
@@ -42,6 +44,7 @@ export const HANDLERS = Object.freeze({
|
|
|
42
44
|
tmct_members,
|
|
43
45
|
tmct_subclasses,
|
|
44
46
|
tmct_related,
|
|
47
|
+
tmct_sprite,
|
|
45
48
|
tmct_architecture,
|
|
46
49
|
tmct_exports,
|
|
47
50
|
tmct_untested,
|
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
// What every per-tool handler module shares: the argument contract, the
|
|
2
|
-
// contract, and the one shape most cold tools have (take a symbol,
|
|
1
|
+
// What every per-tool handler module shares: the argument contract, the result contract,
|
|
2
|
+
// the resolve-or-miss contract, and the one shape most cold tools have (take a symbol,
|
|
3
|
+
// resolve it, render it).
|
|
3
4
|
|
|
4
5
|
import { ToolError } from "../../adapters/config.mjs";
|
|
5
6
|
import { resolveSymbol } from "../../domain/codegraph.mjs";
|
|
6
7
|
|
|
7
8
|
export const SNIPPET_MAX_LINES = 200;
|
|
8
9
|
|
|
10
|
+
/** A handler's answer when it already holds the structured form of what its prose says.
|
|
11
|
+
* `content` is the prose. `data` is that structure, handed to dispatchToolStructured
|
|
12
|
+
* callers as-is so a page renders it instead of parsing the sentence back apart.
|
|
13
|
+
* `text` is what the string-only callers (dispatchTool, the CLI `cli <tool>` route)
|
|
14
|
+
* receive; it defaults to `content`, and a tool sets it only where its flat string
|
|
15
|
+
* already carried the structure in-band. A handler with nothing structured to add
|
|
16
|
+
* keeps returning a bare string, and `data` is absent for it. */
|
|
17
|
+
export function toolResult({ content, data, text = null }) {
|
|
18
|
+
return { content, data, text: text ?? content };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** A handler may return either shape, so both dispatch paths have to tell them apart. */
|
|
22
|
+
export const isToolResult = (value) =>
|
|
23
|
+
!!value && typeof value === "object"
|
|
24
|
+
&& typeof value.content === "string" && typeof value.text === "string";
|
|
25
|
+
|
|
9
26
|
/** A required string argument, trimmed. Missing or blank raises "<key> is required". */
|
|
10
27
|
export function requiredArg(args, key) {
|
|
11
28
|
const value = String(args?.[key] || "").trim();
|
|
@@ -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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|
-
|
|
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));
|
package/src/tools/server.mjs
CHANGED
|
@@ -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
|
-
|
|
53
|
+
async function runHandler(name, args, { config, source = defaultSource, tel = null, ingest = null, memoryBackend = 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.
|
|
@@ -67,3 +73,21 @@ export async function dispatchTool(name, args, { config, source = defaultSource,
|
|
|
67
73
|
const svc = createGraphService(graph, { sourceAccess: true, repoRoot, readFile, tel, ask });
|
|
68
74
|
return handle(args, { graph, svc, config, repoRoot, memoryBackend });
|
|
69
75
|
}
|
|
76
|
+
|
|
77
|
+
/** The caller-facing string for one tool call. A handler that returns a structured
|
|
78
|
+
* result is flattened to its `text` here, so every existing string caller (the chat
|
|
79
|
+
* surface, the CLI `cli <tool>` route, the HTTP shim) is unaffected by a handler
|
|
80
|
+
* gaining structure. */
|
|
81
|
+
export async function dispatchTool(name, args, ctx = {}) {
|
|
82
|
+
const out = await runHandler(name, args, ctx);
|
|
83
|
+
return isToolResult(out) ? out.text : out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The same call, answered as `{ content, data }`: `content` is the prose, `data` the
|
|
87
|
+
* render-ready structure the handler already computed on its way to it. `data` is
|
|
88
|
+
* undefined for a tool whose answer is prose and nothing else, which is most of the
|
|
89
|
+
* cold set — an absent `data` is a real answer, not a failure. */
|
|
90
|
+
export async function dispatchToolStructured(name, args, ctx = {}) {
|
|
91
|
+
const out = await runHandler(name, args, ctx);
|
|
92
|
+
return isToolResult(out) ? { content: out.content, data: out.data } : { content: out, data: undefined };
|
|
93
|
+
}
|