@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.
- package/README.md +2 -1
- package/corpus/sprites/src/sprite-facts.jsonl +375 -8
- package/package.json +1 -1
- 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/mud-facts.mjs +15 -0
- 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 +36 -0
- package/src/domain/sprite-facts.mjs +0 -0
- package/src/domain/sprite-request.mjs +156 -0
- package/src/domain/sprite-templates.mjs +161 -14
- package/src/services/adventure-editor.mjs +8 -14
- package/src/services/adventure-viz.mjs +119 -150
- package/src/services/adventure.mjs +97 -35
- package/src/services/chat-page-viz.mjs +64 -48
- package/src/services/chat.mjs +102 -34
- package/src/services/code-explorer-viz.mjs +52 -50
- package/src/services/ingest-viz.mjs +32 -74
- package/src/services/ledger-viz.mjs +87 -70
- package/src/services/memory-panel-viz.mjs +38 -0
- package/src/services/mud-editor.mjs +10 -15
- package/src/services/mud-turn.mjs +6 -6
- package/src/services/mud-viz.mjs +119 -225
- package/src/services/p2p-room.mjs +90 -23
- package/src/services/plan-pddl.mjs +3 -1
- package/src/services/plan-viz.mjs +13 -12
- package/src/services/research-viz.mjs +25 -67
- package/src/services/spider-fly-turn.mjs +14 -22
- package/src/services/spider-fly-viz.mjs +97 -136
- package/src/services/spider-fly.mjs +69 -11
- package/src/services/sprite-catalog-viz.mjs +274 -224
- package/src/services/viz-boot.mjs +71 -0
- package/src/services/viz-room-graph.mjs +203 -0
- package/src/services/viz-theme.mjs +75 -1
- package/src/services/viz-ticker.mjs +22 -0
- package/src/surfaces/web/adventure-browser-entry.mjs +62 -47
- package/src/surfaces/web/chat-browser-entry.mjs +51 -107
- package/src/surfaces/web/code-explorer-browser-entry.mjs +192 -35
- package/src/surfaces/web/engine-surface.mjs +82 -0
- package/src/surfaces/web/ingest-browser-entry.mjs +16 -17
- package/src/surfaces/web/ledger-browser-entry.mjs +24 -56
- package/src/surfaces/web/memory-ask-browser-entry.mjs +55 -13
- package/src/surfaces/web/memory-ask-browser.bundle.js +128 -125
- package/src/surfaces/web/memory-stats.mjs +11 -0
- package/src/surfaces/web/mud-browser-entry.mjs +70 -49
- package/src/surfaces/web/plan-browser-entry.mjs +39 -50
- package/src/surfaces/web/research-browser-entry.mjs +48 -46
- package/src/surfaces/web/spider-fly-browser-entry.mjs +76 -40
- package/src/surfaces/web/sprites-browser-entry.mjs +28 -32
- package/src/surfaces/web/tmct-surface.mjs +147 -0
- package/src/surfaces/web/turn-session.mjs +124 -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 +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
|
-
|
|
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, 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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
+
}
|