@polycode-projects/the-mechanical-code-talker 5.0.5 → 5.0.7
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 +78 -19
- package/bin/tmct.mjs +63 -2
- package/package.json +1 -1
- package/src/adapters/memory/core.mjs +23 -0
- package/src/domain/ask-vocab.mjs +19 -0
- package/src/domain/ask.mjs +10 -5
- package/src/domain/codegraph.mjs +23 -9
- package/src/domain/game-config.mjs +12 -0
- package/src/domain/interpret/strategies/keywords.mjs +30 -1
- package/src/domain/memory/capability.mjs +15 -11
- package/src/domain/router/drive.mjs +36 -17
- package/src/domain/router/resolver.mjs +63 -17
- package/src/domain/spider-fly-world.mjs +2 -2
- package/src/domain/sprite-templates.mjs +19 -7
- package/src/domain/syllogise.mjs +16 -6
- package/src/domain/town-square-world.mjs +1 -1
- package/src/services/adventure-viz.mjs +5 -2
- package/src/services/adventure.mjs +8 -1
- package/src/services/chat-page-viz.mjs +123 -25
- package/src/services/chat-session.mjs +60 -10
- package/src/services/chat.mjs +328 -36
- package/src/services/code-explorer-viz.mjs +3 -2
- package/src/services/extract-facts.mjs +47 -7
- package/src/services/ingest-viz.mjs +113 -29
- package/src/services/ledger-viz.mjs +9 -4
- package/src/services/memory-panel-viz.mjs +44 -0
- package/src/services/mud-viz.mjs +21 -3
- package/src/services/mudiii-scene.mjs +407 -36
- package/src/services/mudiii-turn.mjs +65 -9
- package/src/services/mudiii-viz.mjs +810 -157
- package/src/services/p2p-room.mjs +1 -1
- package/src/services/plan-viz.mjs +26 -4
- package/src/services/predator-prey.mjs +141 -37
- package/src/services/research-viz.mjs +17 -23
- package/src/services/spider-fly-turn.mjs +7 -1
- package/src/services/spider-fly-viz.mjs +13 -5
- package/src/services/sprite-catalog-viz.mjs +3 -2
- package/src/services/viz-theme.mjs +20 -0
- package/src/services/viz-ticker.mjs +15 -2
- package/src/surfaces/http/server-http.mjs +90 -13
- package/src/surfaces/web/memory-ask-browser.bundle.js +125 -125
- package/src/surfaces/web/mud-browser-entry.mjs +33 -1
- package/src/surfaces/web/mudiii-browser-entry.mjs +70 -34
- package/src/surfaces/web/tmct-surface.mjs +18 -6
- package/src/tools/handlers/tmct-ask.mjs +15 -2
- package/src/tools/server.mjs +31 -2
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// HTTP surface.
|
|
16
16
|
|
|
17
17
|
import { createServer } from "node:http";
|
|
18
|
+
import { stat } from "node:fs/promises";
|
|
18
19
|
import { runTurn, selectTool, capabilityPlanDeps } from "../../services/chat.mjs";
|
|
19
20
|
import { TOOLS, dispatchTool } from "../../tools/server.mjs";
|
|
20
21
|
import { runCapabilityPlan, buildCapabilityPlanCtx, declaredCapabilityNames } from "../../domain/router/drive.mjs";
|
|
@@ -143,7 +144,28 @@ function withRestNote(text, rest) {
|
|
|
143
144
|
* - a mapped, declared graph tool → tool_use
|
|
144
145
|
* - otherwise → end_turn text via runTurn
|
|
145
146
|
*/
|
|
146
|
-
|
|
147
|
+
/** How many Fact individuals a store snapshot holds. Facts only: an ordinary
|
|
148
|
+
* turn records an Utterance and a Session too, and counting those made a game
|
|
149
|
+
* move or a cited lookup read as a teach that went nowhere. */
|
|
150
|
+
function countStoredFacts(snapshot) {
|
|
151
|
+
const individuals = snapshot?.payload?.individuals;
|
|
152
|
+
if (!Array.isArray(individuals)) return 0;
|
|
153
|
+
return individuals.reduce((n, i) => n + ((i?.class || "") === "Fact" ? 1 : 0), 0);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The memory store's vocabulary reader over a throwaway copy of `memoryDir` —
|
|
157
|
+
* the seam a cold tool call gets so `tmct_ask` here answers what chat answers
|
|
158
|
+
* over the same repo. Null when the server was started without a store. */
|
|
159
|
+
async function memoryFactLookup(memoryDir) {
|
|
160
|
+
if (!memoryDir) return null;
|
|
161
|
+
const { readOnlyMemorySnapshot } = await import("../../adapters/memory/core.mjs");
|
|
162
|
+
const snapshot = await readOnlyMemorySnapshot(memoryDir);
|
|
163
|
+
if (!snapshot) return null;
|
|
164
|
+
const { factAnswer } = await import("../../services/chat.mjs");
|
|
165
|
+
return (query, envelope) => factAnswer(snapshot, query, envelope, true);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function respondToMessages(body, { config, graph, memoryDir = null, source = defaultSource } = {}) {
|
|
147
169
|
const { model, messages, tools } = body || {};
|
|
148
170
|
const declaredNames = new Set(
|
|
149
171
|
(Array.isArray(tools) ? tools : []).map((t) => t && t.name).filter(Boolean),
|
|
@@ -185,7 +207,7 @@ export async function respondToMessages(body, { config, graph, source = defaultS
|
|
|
185
207
|
return msg;
|
|
186
208
|
}
|
|
187
209
|
try {
|
|
188
|
-
const out = await dispatchTool(name, input, { config, source });
|
|
210
|
+
const out = await dispatchTool(name, input, { config, source, factLookup: await memoryFactLookup(memoryDir) });
|
|
189
211
|
const msg = assistantMessage(model, [{ type: "text", text: withRestNote(out, rest) }], "end_turn");
|
|
190
212
|
msg.tmct_checked_call = { name, input, problems: [] };
|
|
191
213
|
return msg;
|
|
@@ -225,10 +247,25 @@ export async function respondToMessages(body, { config, graph, source = defaultS
|
|
|
225
247
|
}
|
|
226
248
|
}
|
|
227
249
|
|
|
228
|
-
// text answer: the cited, read-only answer the chat surface gives
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
|
|
250
|
+
// text answer: the cited, read-only answer the chat surface gives, over the
|
|
251
|
+
// same repo's memory store — without it, a term chat answers came back from
|
|
252
|
+
// this endpoint as a miss. The store is handed over as a throwaway in-memory
|
|
253
|
+
// COPY, so the endpoint stays PURE: reads see the real facts, and anything a
|
|
254
|
+
// turn would write lands in the copy rather than on disk.
|
|
255
|
+
const { readOnlyMemorySnapshot } = await import("../../adapters/memory/core.mjs");
|
|
256
|
+
const snapshot = await readOnlyMemorySnapshot(memoryDir);
|
|
257
|
+
const factsBefore = countStoredFacts(snapshot);
|
|
258
|
+
const { answer } = await runTurn(userText, { config, graph, source, memoryDir: snapshot });
|
|
259
|
+
// A teach turn lands in the copy and confirms itself. Say plainly that the
|
|
260
|
+
// fact went nowhere, rather than leaving "noted — remembered" as the last
|
|
261
|
+
// word on a write this endpoint never makes.
|
|
262
|
+
const wrote = countStoredFacts(snapshot) > factsBefore;
|
|
263
|
+
// A game's opening move writes board facts the same way a teach writes one,
|
|
264
|
+
// so the advice names the turn rather than assuming a fact was taught.
|
|
265
|
+
const text = wrote
|
|
266
|
+
? `${answer}\n(nothing was stored — this endpoint reads the memory store and never writes to it. Run the same turn in a chat session to keep what it writes.)`
|
|
267
|
+
: answer;
|
|
268
|
+
return assistantMessage(model, [{ type: "text", text }], "end_turn");
|
|
232
269
|
}
|
|
233
270
|
|
|
234
271
|
/** The capability names a /v1/plan request restricts its plan to (its `tools`
|
|
@@ -308,9 +345,48 @@ function sendError(res, status, type, message) {
|
|
|
308
345
|
sendJson(res, status, { type: "error", error: { type, message } });
|
|
309
346
|
}
|
|
310
347
|
|
|
348
|
+
/** A stamp of every graph file's size and mtime. Two equal stamps mean the
|
|
349
|
+
* parsed graph in hand is still the graph on disk. */
|
|
350
|
+
async function graphFileStamp(config) {
|
|
351
|
+
const files = config.graphFiles?.length ? config.graphFiles : [config.graphFile];
|
|
352
|
+
const parts = [];
|
|
353
|
+
for (const f of files) {
|
|
354
|
+
try {
|
|
355
|
+
const s = await stat(f);
|
|
356
|
+
parts.push(`${f}:${s.size}:${s.mtimeMs}`);
|
|
357
|
+
} catch { parts.push(`${f}:absent`); }
|
|
358
|
+
}
|
|
359
|
+
return parts.join("|");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* A graph reader that re-parses when the artifact underneath it changes. The
|
|
364
|
+
* cold tool route loads the graph per call (dispatchTool → loadGraph), so a
|
|
365
|
+
* text answer served from a graph parsed at startup and a tool answer served
|
|
366
|
+
* from the file disagreed about the same repo the moment anything reindexed it
|
|
367
|
+
* mid-run. Stat-guarded, so an unchanged file costs one stat rather than a
|
|
368
|
+
* re-parse.
|
|
369
|
+
*/
|
|
370
|
+
function reloadingGraph(config, source) {
|
|
371
|
+
let stamp = null;
|
|
372
|
+
let graph = null;
|
|
373
|
+
return async () => {
|
|
374
|
+
const now = await graphFileStamp(config);
|
|
375
|
+
if (graph && now === stamp) return graph;
|
|
376
|
+
// source.fetchEntities keys its own per-process cache on the file PATH
|
|
377
|
+
// alone, so a same-path rewrite would still serve the payload parsed at
|
|
378
|
+
// startup. Drop it before re-reading; a stamp only changes when the bytes
|
|
379
|
+
// on disk did.
|
|
380
|
+
if (graph) source.clearCache?.();
|
|
381
|
+
graph = parseEntities(await source.fetchEntities(config));
|
|
382
|
+
stamp = now;
|
|
383
|
+
return graph;
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
311
387
|
/**
|
|
312
|
-
* Start the HTTP server.
|
|
313
|
-
* the empty bootstrap graph, never an error
|
|
388
|
+
* Start the HTTP server. The graph is read tolerantly: a missing artifact is
|
|
389
|
+
* the empty bootstrap graph, never an error. Returns { server, url, host, port,
|
|
314
390
|
* config, close } — `close()` shuts the socket cleanly (no hanging handles).
|
|
315
391
|
*
|
|
316
392
|
* config — { graphFile } (build via configFor(repoPath) in bin/tmct.mjs)
|
|
@@ -319,9 +395,10 @@ function sendError(res, status, type, message) {
|
|
|
319
395
|
*/
|
|
320
396
|
export async function startServer({ config, host = "127.0.0.1", port = 0, source = defaultSource, memoryDir = null } = {}) {
|
|
321
397
|
if (!config || !config.graphFile) throw new Error("startServer requires config.graphFile");
|
|
322
|
-
|
|
323
|
-
//
|
|
324
|
-
|
|
398
|
+
const currentGraph = reloadingGraph(config, source);
|
|
399
|
+
// Parse once up front so a listening server has already paid for the common
|
|
400
|
+
// case, and so a broken artifact surfaces before the first request.
|
|
401
|
+
await currentGraph();
|
|
325
402
|
|
|
326
403
|
const server = createServer(async (req, res) => {
|
|
327
404
|
try {
|
|
@@ -352,7 +429,7 @@ export async function startServer({ config, host = "127.0.0.1", port = 0, source
|
|
|
352
429
|
sendError(res, 400, "invalid_request_error", `unknown tools name(s): ${unknown.join(", ")}; registered capabilities: ${declared.join(", ")}`);
|
|
353
430
|
return;
|
|
354
431
|
}
|
|
355
|
-
const out = await respondToPlan(body, { config, graph, memoryDir, source });
|
|
432
|
+
const out = await respondToPlan(body, { config, graph: await currentGraph(), memoryDir, source });
|
|
356
433
|
sendJson(res, 200, out);
|
|
357
434
|
return;
|
|
358
435
|
}
|
|
@@ -375,7 +452,7 @@ export async function startServer({ config, host = "127.0.0.1", port = 0, source
|
|
|
375
452
|
sendError(res, 400, "invalid_request_error", "`messages` array is required");
|
|
376
453
|
return;
|
|
377
454
|
}
|
|
378
|
-
const out = await respondToMessages(body, { config, graph, source });
|
|
455
|
+
const out = await respondToMessages(body, { config, graph: await currentGraph(), memoryDir, source });
|
|
379
456
|
sendJson(res, 200, out);
|
|
380
457
|
} catch (e) {
|
|
381
458
|
sendError(res, 500, "api_error", e && e.message ? e.message : String(e));
|