@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
package/src/services/chat.mjs
CHANGED
|
@@ -953,9 +953,35 @@ async function answerMemoryClassQuery(memoryDir, query) {
|
|
|
953
953
|
// literal quantifier lookup. Placed ahead of it in runTurn: HOW_MANY_ARE_RE
|
|
954
954
|
// reads "there" as a second noun and answers "I was never told a quantifier",
|
|
955
955
|
// stealing the phrasing before a member count ever runs.
|
|
956
|
-
const TAUGHT_CLASS_COUNT_RE = /^how\s+many\s+([a-z][\w-]*)\s*(.*)$/i;
|
|
956
|
+
const TAUGHT_CLASS_COUNT_RE = /^how\s+many\s+([a-z][\w-]*(?:\s+[a-z][\w-]*)*)\s*(.*)$/i;
|
|
957
|
+
|
|
958
|
+
/** The longest leading run of `nounRun`'s words that names a class something was
|
|
959
|
+
* actually taught about, as `{asked, tail, members}` — its taught members and
|
|
960
|
+
* whatever words are left over, joined onto `trailing` as the restrictor tail.
|
|
961
|
+
* A class name is a noun PHRASE, not a word ("sprite class", "body of water"),
|
|
962
|
+
* so the run is tried longest-first and the shortest reading wins only when no
|
|
963
|
+
* longer one is on record. That ordering is what keeps a single-word class
|
|
964
|
+
* carrying a restrictor ("list the animals in the graph") reading exactly as it
|
|
965
|
+
* did when only the first word was ever considered. */
|
|
966
|
+
async function longestTaughtClassInRun(memoryDir, nounRun, trailing, biasByBundle, cache) {
|
|
967
|
+
const words = String(nounRun || "").trim().split(/\s+/).filter(Boolean);
|
|
968
|
+
if (!words.length) return null;
|
|
969
|
+
if (COUNT_NOUNS[words[0].toLowerCase()]) return null; // a real graph-countable class — the code lanes own it
|
|
970
|
+
let normFactTerm;
|
|
971
|
+
try { ({ normFactTerm } = await import("../adapters/memory/core.mjs")); } catch { return null; }
|
|
972
|
+
const rows = await factRows(memoryDir, cache);
|
|
973
|
+
const isa = rows.filter((f) => ISA_PREDICATES.has(f.predicate));
|
|
974
|
+
for (let take = words.length; take >= 1; take -= 1) {
|
|
975
|
+
const asked = words.slice(0, take).join(" ").toLowerCase();
|
|
976
|
+
const variants = factTermVariants(normFactTerm, asked);
|
|
977
|
+
const members = rankByBiasThenTrust(isa.filter((f) => variants.has(f.object)), biasByBundle);
|
|
978
|
+
if (!members.length) continue; // nothing taught under this reading — try a shorter one
|
|
979
|
+
return { asked, tail: [words.slice(take).join(" "), String(trailing || "")].filter(Boolean).join(" ").trim(), members };
|
|
980
|
+
}
|
|
981
|
+
return null;
|
|
982
|
+
}
|
|
957
983
|
|
|
958
|
-
/** Count the taught members of a class named by a
|
|
984
|
+
/** Count the taught members of a class named by a noun phrase ("how many animals
|
|
959
985
|
* are there" → every "X is a kind of animal"). Declines (null) for a real
|
|
960
986
|
* code-countable class (answerCount owns it) or a class nothing was taught
|
|
961
987
|
* about, so structural counts and the quantifier lane are unaffected. */
|
|
@@ -963,22 +989,16 @@ async function answerTaughtClassCount(memoryDir, query, biasByBundle = {}, cache
|
|
|
963
989
|
if (!memoryDir) return null;
|
|
964
990
|
const m = String(query).trim().match(TAUGHT_CLASS_COUNT_RE);
|
|
965
991
|
if (!m) return null;
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
if (
|
|
969
|
-
let normFactTerm;
|
|
970
|
-
try { ({ normFactTerm } = await import("../adapters/memory/core.mjs")); } catch { return null; }
|
|
971
|
-
const rows = await factRows(memoryDir, cache);
|
|
972
|
-
const isa = rows.filter((f) => ISA_PREDICATES.has(f.predicate));
|
|
973
|
-
const variants = factTermVariants(normFactTerm, asked);
|
|
974
|
-
const members = rankByBiasThenTrust(isa.filter((f) => variants.has(f.object)), biasByBundle);
|
|
975
|
-
if (!members.length) return null; // nothing taught under this class name — later lanes own it
|
|
992
|
+
const hit = await longestTaughtClassInRun(memoryDir, m[1], m[2], biasByBundle, cache);
|
|
993
|
+
if (!hit) return null;
|
|
994
|
+
if (!DYNAMIC_TAIL_OK_RE.test(hit.tail)) return null;
|
|
976
995
|
// A member whose SUBJECT is itself a countable graph class ("every class is a
|
|
977
996
|
// component") is an asserted-vocabulary cardinality, not a member enumeration —
|
|
978
997
|
// countFromFacts counts the real class, so defer to it rather than tallying the
|
|
979
998
|
// one class-level fact.
|
|
980
|
-
if (members.some((f) => COUNT_NOUNS[String(f.subject).toLowerCase()])) return null;
|
|
981
|
-
|
|
999
|
+
if (hit.members.some((f) => COUNT_NOUNS[String(f.subject).toLowerCase()])) return null;
|
|
1000
|
+
const n = hit.members.length;
|
|
1001
|
+
return `${n} ${n === 1 ? hit.asked.replace(/s$/, "") : hit.asked}.`;
|
|
982
1002
|
}
|
|
983
1003
|
|
|
984
1004
|
// "list all animals" / "list the animals" — enumerate a taught class's members,
|
|
@@ -986,9 +1006,9 @@ async function answerTaughtClassCount(memoryDir, query, biasByBundle = {}, cache
|
|
|
986
1006
|
// leftovers: at scale the definition lane fills its cap with forward corpus facts
|
|
987
1007
|
// before the reverse-membership listing ever shows, and the conversational
|
|
988
1008
|
// orientation lane claims the bare "list …" phrasing before factReadBack runs.
|
|
989
|
-
const MEMBERSHIP_LIST_RE = /^(?:list|show(?:\s+me)?)\s+(?:all\s+|the\s+)?([a-z][\w-]*)\s*(.*)$/i;
|
|
1009
|
+
const MEMBERSHIP_LIST_RE = /^(?:list|show(?:\s+me)?)\s+(?:all\s+|the\s+)?([a-z][\w-]*(?:\s+[a-z][\w-]*)*)\s*(.*)$/i;
|
|
990
1010
|
|
|
991
|
-
/** List the taught members of a class named by a
|
|
1011
|
+
/** List the taught members of a class named by a noun phrase ("list all animals"
|
|
992
1012
|
* → every "X is a kind of animal"). Declines (null) for a code-countable class
|
|
993
1013
|
* or a class nothing was taught about; declines with a message for a real
|
|
994
1014
|
* restrictor tail rather than answering as if it weren't there. */
|
|
@@ -996,16 +1016,9 @@ async function answerMembershipList(memoryDir, query, biasByBundle = {}, cache =
|
|
|
996
1016
|
if (!memoryDir) return null;
|
|
997
1017
|
const m = String(query).trim().match(MEMBERSHIP_LIST_RE);
|
|
998
1018
|
if (!m) return null;
|
|
999
|
-
const
|
|
1000
|
-
if (
|
|
1001
|
-
const tail
|
|
1002
|
-
let normFactTerm;
|
|
1003
|
-
try { ({ normFactTerm } = await import("../adapters/memory/core.mjs")); } catch { return null; }
|
|
1004
|
-
const rows = await factRows(memoryDir, cache);
|
|
1005
|
-
const isa = rows.filter((f) => ISA_PREDICATES.has(f.predicate));
|
|
1006
|
-
const variants = factTermVariants(normFactTerm, asked);
|
|
1007
|
-
const members = rankByBiasThenTrust(isa.filter((f) => variants.has(f.object)), biasByBundle);
|
|
1008
|
-
if (!members.length) return null; // nothing taught under this class name — later lanes own it
|
|
1019
|
+
const hit = await longestTaughtClassInRun(memoryDir, m[1], m[2], biasByBundle, cache);
|
|
1020
|
+
if (!hit) return null;
|
|
1021
|
+
const { asked, tail, members } = hit;
|
|
1009
1022
|
if (!DYNAMIC_TAIL_OK_RE.test(tail)) {
|
|
1010
1023
|
return {
|
|
1011
1024
|
text: `I can list the ${asked}, but not the "${tail}" part of that question — `
|
|
@@ -2114,7 +2127,7 @@ export { moduleCountOf };
|
|
|
2114
2127
|
/** A KNOWN-empty code graph: a loaded graph object with 0 modules. A null graph
|
|
2115
2128
|
* (a bare runTurn that wasn't handed one) is "unknown", NOT empty — the empty
|
|
2116
2129
|
* orientation/greeting only fires when we actually hold an empty graph. */
|
|
2117
|
-
const noCodeGraph = (graph) => !!graph && moduleCountOf(graph) === 0;
|
|
2130
|
+
export const noCodeGraph = (graph) => !!graph && moduleCountOf(graph) === 0;
|
|
2118
2131
|
|
|
2119
2132
|
/** LIVE orientation examples: the example queries on the orientation card name
|
|
2120
2133
|
* entities from the LOADED graph — the sorted-first Module label and the
|
|
@@ -7160,6 +7173,16 @@ const LOCATIVE_FACT_PREDICATE_RE = /^mgx:[a-z]+-(?:on|in|at|inside|under|below|a
|
|
|
7160
7173
|
* to the ordinary BARE_WHATIS_RE handling untouched. */
|
|
7161
7174
|
const WHAT_IS_PREP_FACT_RE = new RegExp(`^what(?:'s|\\s+is|\\s+are)\\s+(${PREP_SRC})\\s+(.+?)\\s*[?.!]*$`, "i");
|
|
7162
7175
|
|
|
7176
|
+
/** "what parameters does a person sprite take" / "what materials does a bed
|
|
7177
|
+
* accept" — the object-fronted property question, where the property noun
|
|
7178
|
+
* leads and the verb closes. It reads the same folded verb-plus-noun predicates
|
|
7179
|
+
* the teach path already mints (mgx:take-parameter, mgx:accept-material,
|
|
7180
|
+
* mgx:offer-variant), so the predicate is recovered from the sentence's own two
|
|
7181
|
+
* ends rather than from a table of known property words: m[1] is the noun,
|
|
7182
|
+
* m[2] the subject, m[3] the verb. Consumed by factAnswer's (a-pre6) reader,
|
|
7183
|
+
* which diverts only on a real stored hit. */
|
|
7184
|
+
const OBJECT_FRONTED_PROPERTY_RE = /^what\s+([a-z][\w-]*)\s+(?:does|do)\s+(?:an?\s+|the\s+)?(.+?)\s+([a-z][a-z-]*)\s*[?.!]*$/i;
|
|
7185
|
+
|
|
7163
7186
|
// CAN_ASK_RE's remaining paraphrase-ladder siblings, all over the same
|
|
7164
7187
|
// mgx:capableOf facts:
|
|
7165
7188
|
// - DO_VERB_ASK_RE: the do-support yes/no ("do birds fly", "does a dog
|
|
@@ -7666,6 +7689,35 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
|
|
|
7666
7689
|
}
|
|
7667
7690
|
}
|
|
7668
7691
|
|
|
7692
|
+
// (a-pre6) "what parameters does a person sprite take" — the object-fronted
|
|
7693
|
+
// property question over a folded verb-plus-noun predicate. Both ends of the
|
|
7694
|
+
// sentence carry half the predicate the teach path minted, so the verb and the
|
|
7695
|
+
// noun are rejoined into mgx:<verb>-<noun> and looked up directly; the noun is
|
|
7696
|
+
// tried through its own plural variants, since a question asks for "parameters"
|
|
7697
|
+
// where the stored predicate names one "parameter". Checked before (a) for the
|
|
7698
|
+
// same reason as its siblings above — the leading "what …" would otherwise be
|
|
7699
|
+
// claimed as one literal term to define — and hit-gated the same way, so a
|
|
7700
|
+
// sentence of this shape with nothing on record falls through untouched.
|
|
7701
|
+
const propertyQ = q.match(OBJECT_FRONTED_PROPERTY_RE);
|
|
7702
|
+
if (propertyQ) {
|
|
7703
|
+
const verb = propertyQ[3].toLowerCase();
|
|
7704
|
+
const subjectVariants = factTermVariants(normFactTerm, propertyQ[2]);
|
|
7705
|
+
const predicates = new Set(
|
|
7706
|
+
[...factTermVariants(normFactTerm, propertyQ[1])].map((noun) => normFactPredicate(`mgx:${verb}-${noun.replace(/\s+/g, "-")}`)),
|
|
7707
|
+
);
|
|
7708
|
+
const hits = (await factRows(memoryDir, cache)).filter(
|
|
7709
|
+
(f) => predicates.has(normFactPredicate(f.predicate)) && subjectVariants.has(normFactTerm(f.subject)),
|
|
7710
|
+
);
|
|
7711
|
+
if (hits.length) {
|
|
7712
|
+
const ranked = rankByBiasThenTrust(uniqueFacts(hits), biasByBundle);
|
|
7713
|
+
const lines = ranked.map(renderFactLine);
|
|
7714
|
+
const shown = lines.slice(0, FACT_ANSWER_CAP);
|
|
7715
|
+
const rest = lines.slice(FACT_ANSWER_CAP);
|
|
7716
|
+
const extra = rest.length ? `\n…and ${rest.length} more — say 'more' to see them.` : "";
|
|
7717
|
+
return { text: shown.join("\n") + extra, replace: true, ...(rest.length ? { pending: { items: rest, noun: "facts" } } : {}) };
|
|
7718
|
+
}
|
|
7719
|
+
}
|
|
7720
|
+
|
|
7669
7721
|
// (a) meta-shaped questions ("what is a module", "what does cache mean") — the
|
|
7670
7722
|
// parsed object term, matched against fact SUBJECTS; consulted for hits (append
|
|
7671
7723
|
// alongside the schema-docs answer) and misses (facts answer alone) alike.
|
|
@@ -12526,11 +12578,16 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
12526
12578
|
let envelope = null;
|
|
12527
12579
|
try {
|
|
12528
12580
|
let text;
|
|
12529
|
-
if (graph && (focus?.id || prev.length)) {
|
|
12530
|
-
// Direct ask()
|
|
12531
|
-
//
|
|
12532
|
-
//
|
|
12533
|
-
//
|
|
12581
|
+
if (graph?.individuals?.length || (graph && (focus?.id || prev.length))) {
|
|
12582
|
+
// Direct ask() whenever the caller HANDED US a graph with something in it.
|
|
12583
|
+
// The focus/prev pair is threaded through it (contextId so "it" binds, prev
|
|
12584
|
+
// for the anaphora node), but neither is what earns the direct call: a
|
|
12585
|
+
// caller that passes a real graph means that graph, and the tmct_ask branch
|
|
12586
|
+
// below reads the CONFIG's graph instead — which an in-process session (a
|
|
12587
|
+
// page's own world facts, say) has no file for. Gating on history alone
|
|
12588
|
+
// refused every cold turn against a perfectly good graph and only started
|
|
12589
|
+
// answering once a focus happened to be set. Builds the SAME delimited
|
|
12590
|
+
// envelope dispatchTool emits, so the parse below is identical either way.
|
|
12534
12591
|
const { ask } = await import("../domain/ask.mjs");
|
|
12535
12592
|
const r = ask(graph, askQuery, { contextId: effectiveContextId, prev });
|
|
12536
12593
|
text = `${r.content}${ASK_ENVELOPE_DELIM}${JSON.stringify(r.tmct_ask, null, 2)}`;
|
|
@@ -14084,9 +14141,20 @@ async function runCommand(line, { config, source, graph, focus, memoryDir, trace
|
|
|
14084
14141
|
if (name === "plan") {
|
|
14085
14142
|
note(trace, "goal: plan/execute a compound or maintenance-goal request over the graph (the capability router)");
|
|
14086
14143
|
if (!argText) return mk("/plan needs a request, e.g. `/plan of the modules impacted by X, which are untested`.", { miss: true });
|
|
14087
|
-
|
|
14144
|
+
// A KNOWN-EMPTY code graph is nothing to plan over, and it is what every
|
|
14145
|
+
// memory-graph page and an un-pointed CLI session actually holds: each of
|
|
14146
|
+
// its parameter slots would bind against an index with no entities in it.
|
|
14147
|
+
// Where there is a memory store, hand the planner that instead — that is
|
|
14148
|
+
// buildCapabilityPlanCtx's memory-only mode, where world facts bind and a
|
|
14149
|
+
// code-graph capability refuses by naming the graph it hasn't got. Only a
|
|
14150
|
+
// graph with something in it plans as a code graph. `source` travels with
|
|
14151
|
+
// it: this turn reuses what it already holds and never loads one mid-turn.
|
|
14152
|
+
const planGraph = graph && !noCodeGraph(graph) ? graph : null;
|
|
14153
|
+
if (!planGraph && !memoryDir) return mk("no graph loaded — /plan needs a code graph or a memory store to plan over.", { miss: true });
|
|
14088
14154
|
const { buildCapabilityPlanCtx, runCapabilityPlan, declaredCapabilityNames } = await import("../domain/router/drive.mjs");
|
|
14089
|
-
const planCtx = await buildCapabilityPlanCtx({
|
|
14155
|
+
const planCtx = await buildCapabilityPlanCtx({
|
|
14156
|
+
...capabilityPlanDeps(), config, source: planGraph ? source : null, tel, graph: planGraph, memoryDir,
|
|
14157
|
+
});
|
|
14090
14158
|
try {
|
|
14091
14159
|
const result = await runCapabilityPlan(argText, declaredCapabilityNames(), planCtx);
|
|
14092
14160
|
if (result.refused) {
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
// a title bar (graph source, Open graph…/Open repo…), an explorer sidebar
|
|
3
3
|
// reading each import/call/contains edge back as a plain sentence, a chat
|
|
4
4
|
// centre over the same graph with a rail of suggested questions, and a status
|
|
5
|
-
// bar carrying the graph's own counts.
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
5
|
+
// bar carrying the graph's own counts. Clicking a term asks the engine what
|
|
6
|
+
// relates to it (askRelatedFacts, over tmct_ask) and renders the answer, so the
|
|
7
|
+
// sidebar and the chat put the same question to the same place.
|
|
8
|
+
//
|
|
9
|
+
// The chat session seeds BOTH the loaded code graph and chat.html's
|
|
10
|
+
// general-knowledge bands (./chat-seed.json, fetched lazily at runtime), so one
|
|
11
|
+
// conversation answers "what is a queue" and "what imports src/core/model.mjs"
|
|
12
|
+
// alike — and degrades to graph-only when the seed asset is unavailable.
|
|
10
13
|
//
|
|
11
14
|
// The derivations are pure so the shell, the packaging scripts, and the unit
|
|
12
15
|
// tests all share one code path; renderCodeExplorerHtml builds one
|
|
@@ -18,24 +21,14 @@
|
|
|
18
21
|
|
|
19
22
|
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml, embedJson, embedScriptText } from "./viz-theme.mjs";
|
|
20
23
|
import { generateCodeHints } from "../domain/code-explorer-hints.mjs";
|
|
24
|
+
import { phraseForRelation } from "../domain/ask-vocab.mjs";
|
|
25
|
+
import { fetchWithProgress } from "./memory-panel-viz.mjs";
|
|
21
26
|
|
|
22
|
-
// Third-person verb for each stored relation kind, symbol grain folded onto
|
|
23
|
-
// coarse sibling
|
|
24
|
-
//
|
|
25
|
-
const EDGE_PHRASE = new Map([
|
|
26
|
-
["imports", "imports"],
|
|
27
|
-
["calls", "calls"], ["callsSymbol", "calls"],
|
|
28
|
-
["contains", "contains"],
|
|
29
|
-
["defines", "defines"],
|
|
30
|
-
["inherits", "inherits from"],
|
|
31
|
-
["tests", "tests"],
|
|
32
|
-
["touches", "touches"], ["touchesSymbol", "touches"],
|
|
33
|
-
["cochange", "co-changes with"],
|
|
34
|
-
["reexports", "re-exports"],
|
|
35
|
-
]);
|
|
36
|
-
|
|
27
|
+
// Third-person verb for each stored relation kind, symbol grain folded onto
|
|
28
|
+
// its coarse sibling — derived from ask-vocab.mjs's own RELATIONS table
|
|
29
|
+
// rather than a second hand-curated relation-verb table.
|
|
37
30
|
export function edgePhrase(kind) {
|
|
38
|
-
return
|
|
31
|
+
return phraseForRelation(kind);
|
|
39
32
|
}
|
|
40
33
|
|
|
41
34
|
// Both packagers (build-electron-app.mjs, build-demo-site.mjs) place
|
|
@@ -117,7 +110,7 @@ export function computeCodeExplorerData(payload, opts = {}) {
|
|
|
117
110
|
const CLIENT_JS = String.raw`
|
|
118
111
|
(function () {
|
|
119
112
|
var DATA = window.__CODE_EXPLORER__;
|
|
120
|
-
var api = window.
|
|
113
|
+
var api = window.tmct ? window.tmct.page : null;
|
|
121
114
|
var els = {
|
|
122
115
|
focus: document.getElementById("focus-name"),
|
|
123
116
|
ledger: document.getElementById("ledger"),
|
|
@@ -133,11 +126,7 @@ const CLIENT_JS = String.raw`
|
|
|
133
126
|
};
|
|
134
127
|
var session = null;
|
|
135
128
|
|
|
136
|
-
|
|
137
|
-
return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) {
|
|
138
|
-
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
|
|
139
|
-
});
|
|
140
|
-
}
|
|
129
|
+
var esc = ${escapeHtml.toString()};
|
|
141
130
|
|
|
142
131
|
function renderStats(data) {
|
|
143
132
|
var s = data.ledger.stats;
|
|
@@ -155,11 +144,37 @@ const CLIENT_JS = String.raw`
|
|
|
155
144
|
els.factTotal.textContent = (edges + seedState.facts).toLocaleString();
|
|
156
145
|
}
|
|
157
146
|
|
|
158
|
-
|
|
147
|
+
// "What relates to the focus", put to the engine. askRelatedFacts asks
|
|
148
|
+
// tmct_ask one question per relation kind the loaded graph carries, in both
|
|
149
|
+
// directions, and hands back the answers' own typed rows — the same ask()
|
|
150
|
+
// this page's chat turns reach, so the panel and the conversation answer one
|
|
151
|
+
// question one way. Null on the static page, which has no engine to ask.
|
|
152
|
+
function askRelated(focus) {
|
|
153
|
+
if (!api || !api.askRelatedFacts || !focus) return null;
|
|
154
|
+
try {
|
|
155
|
+
return api.askRelatedFacts(DATA.payload, focus);
|
|
156
|
+
} catch (e) {
|
|
157
|
+
console.warn("tmct code explorer: the related-facts ask failed, splitting the row list instead", e);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function rowKey(r) { return JSON.stringify([r.s, r.kind, r.o]); }
|
|
163
|
+
|
|
164
|
+
function renderFocusRows(data, related) {
|
|
159
165
|
var focus = data.focus;
|
|
160
166
|
var rows = data.ledger.rows;
|
|
161
|
-
|
|
162
|
-
|
|
167
|
+
// The engine's answer whenever it grounded one. The row list's own split is
|
|
168
|
+
// what is left when there is no engine (the static page) or when every
|
|
169
|
+
// question came back parsed as something else — an identifier that is
|
|
170
|
+
// itself a relation verb reads as a question about the verb.
|
|
171
|
+
var grounded = Boolean(related && related.grounded);
|
|
172
|
+
var near = grounded ? related.rows : rows.filter(function (r) { return r.s === focus || r.o === focus; });
|
|
173
|
+
var nearKeys = {};
|
|
174
|
+
near.forEach(function (r) { nearKeys[rowKey(r)] = true; });
|
|
175
|
+
// Whatever the neighbourhood did not already name, in degree order: a bulk
|
|
176
|
+
// view of the rest of the graph, which is a fold and not a question.
|
|
177
|
+
var rest = rows.filter(function (r) { return !nearKeys[rowKey(r)]; });
|
|
163
178
|
var ordered = near.concat(rest);
|
|
164
179
|
els.ledger.innerHTML = ordered.map(function (r) {
|
|
165
180
|
var hot = (r.s === focus || r.o === focus) ? " row-focus" : "";
|
|
@@ -168,7 +183,9 @@ const CLIENT_JS = String.raw`
|
|
|
168
183
|
+ '<span class="verb">' + esc(r.phrase) + '</span> '
|
|
169
184
|
+ '<button class="term" data-term="' + esc(r.o) + '">' + esc(r.o) + '</button>'
|
|
170
185
|
+ '</li>';
|
|
171
|
-
}).join("") ||
|
|
186
|
+
}).join("") || (grounded
|
|
187
|
+
? '<li class="row muted">nothing in this graph relates to ' + esc(focus) + '.</li>'
|
|
188
|
+
: '<li class="row muted">no edges in this graph.</li>');
|
|
172
189
|
els.focus.textContent = focus || "—";
|
|
173
190
|
}
|
|
174
191
|
|
|
@@ -188,7 +205,7 @@ const CLIENT_JS = String.raw`
|
|
|
188
205
|
|
|
189
206
|
function mountView(data) {
|
|
190
207
|
renderStats(data);
|
|
191
|
-
renderFocusRows(data);
|
|
208
|
+
renderFocusRows(data, askRelated(data.focus));
|
|
192
209
|
renderHints(data);
|
|
193
210
|
}
|
|
194
211
|
|
|
@@ -233,23 +250,7 @@ const CLIENT_JS = String.raw`
|
|
|
233
250
|
function seedNote(text) { if (els.seedStatus) els.seedStatus.textContent = text; }
|
|
234
251
|
function mbText(n) { return (n / 1048576).toFixed(1); }
|
|
235
252
|
|
|
236
|
-
|
|
237
|
-
var res = await fetch(url);
|
|
238
|
-
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
239
|
-
var total = Number(res.headers.get("content-length")) || 0;
|
|
240
|
-
if (!res.body || !res.body.getReader) return res.text();
|
|
241
|
-
var reader = res.body.getReader();
|
|
242
|
-
var chunks = [];
|
|
243
|
-
var loaded = 0;
|
|
244
|
-
for (;;) {
|
|
245
|
-
var step = await reader.read();
|
|
246
|
-
if (step.done) break;
|
|
247
|
-
chunks.push(step.value);
|
|
248
|
-
loaded += step.value.byteLength;
|
|
249
|
-
onProgress(loaded, total);
|
|
250
|
-
}
|
|
251
|
-
return new Blob(chunks).text();
|
|
252
|
-
}
|
|
253
|
+
var fetchWithProgress = ${fetchWithProgress.toString()};
|
|
253
254
|
|
|
254
255
|
async function loadSeed() {
|
|
255
256
|
try {
|
|
@@ -258,9 +259,10 @@ const CLIENT_JS = String.raw`
|
|
|
258
259
|
seedNote("loading general knowledge…");
|
|
259
260
|
text = await window.tmctDesktop.readSeed();
|
|
260
261
|
} else {
|
|
261
|
-
|
|
262
|
+
var seedBlob = await fetchWithProgress("./chat-seed.json" + SEED_QUERY, function (loaded, total) {
|
|
262
263
|
seedNote("loading general knowledge… " + mbText(loaded) + (total ? " of " + mbText(total) : "") + " MB");
|
|
263
264
|
});
|
|
265
|
+
text = await seedBlob.text();
|
|
264
266
|
}
|
|
265
267
|
if (text) {
|
|
266
268
|
seedState.payload = JSON.parse(text);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// self-contained document shaped exactly like chat-page-viz.mjs's own
|
|
3
3
|
// page-builder — one inlined <style> importing viz-theme.mjs's shared tokens,
|
|
4
4
|
// behaviour as an inlined IIFE — running the ingest engine
|
|
5
|
-
// (ingest-browser.bundle.js's globalThis.
|
|
5
|
+
// (ingest-browser.bundle.js's globalThis.tmct) by same-origin relative
|
|
6
6
|
// paths.
|
|
7
7
|
//
|
|
8
8
|
// The page's own chrome is a two-pane translate-tool layout: mode pills
|
|
@@ -26,46 +26,20 @@
|
|
|
26
26
|
// input. scripts/build-demo-site.mjs calls it directly and writes the result
|
|
27
27
|
// to public/ingest.html, after ingest-browser.bundle.js already exists.
|
|
28
28
|
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml } from "./viz-theme.mjs";
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
bandLabelFor,
|
|
31
|
+
statsSummaryLine,
|
|
32
|
+
clearSiteAssetCaches,
|
|
33
|
+
fetchWithProgress,
|
|
34
|
+
renderStatsPanelInto,
|
|
35
|
+
loadProgressLine,
|
|
36
|
+
factTripleParts,
|
|
37
|
+
} from "./memory-panel-viz.mjs";
|
|
38
|
+
import { loadWinkVendor } from "./viz-boot.mjs";
|
|
39
|
+
import { cloneMemoryPayload } from "../adapters/memory/core.mjs";
|
|
30
40
|
|
|
31
41
|
const DEFAULT_TITLE = "the-mechanical-code-talker — ingest";
|
|
32
42
|
|
|
33
|
-
/** One grounded fact as a canonical triple line — subject, predicate, object,
|
|
34
|
-
* each in its own cell so the pane can align them. Pure and
|
|
35
|
-
* `.toString()`-splice safe (no outer refs): the inline script splices this
|
|
36
|
-
* in and calls it per row. */
|
|
37
|
-
export function factTripleParts(fact) {
|
|
38
|
-
return {
|
|
39
|
-
subject: String((fact && fact.subject) || ""),
|
|
40
|
-
predicate: String((fact && fact.predicate) || ""),
|
|
41
|
-
object: String((fact && fact.object) || ""),
|
|
42
|
-
provenance: String((fact && fact.provenance) || ""),
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** The boot statusline while the big assets stream in — the same aggregator
|
|
47
|
-
* chat-page-viz.mjs's own loadProgressLine is (kept as this page's own copy
|
|
48
|
-
* rather than a shared import — the two pages' boot lines diverge slightly
|
|
49
|
-
* and neither is a collaborator the other calls). `parts` is an array of
|
|
50
|
-
* { loaded, total } byte counts (total 0 when the response carried no
|
|
51
|
-
* Content-Length); with no usable total the line shows loaded bytes alone
|
|
52
|
-
* rather than inventing a denominator. Self-contained, `.toString()`-splice
|
|
53
|
-
* safe. */
|
|
54
|
-
export function loadProgressLine(parts) {
|
|
55
|
-
const mb = (n) => (n / 1048576).toFixed(1);
|
|
56
|
-
let loaded = 0;
|
|
57
|
-
let total = 0;
|
|
58
|
-
let totalKnown = true;
|
|
59
|
-
for (const p of parts || []) {
|
|
60
|
-
loaded += (p && p.loaded) || 0;
|
|
61
|
-
if (p && p.total > 0) total += p.total;
|
|
62
|
-
else totalKnown = false;
|
|
63
|
-
}
|
|
64
|
-
return totalKnown && total > 0
|
|
65
|
-
? "loading the engine… " + mb(loaded) + " MB / " + mb(total) + " MB"
|
|
66
|
-
: "loading the engine… " + mb(loaded) + " MB";
|
|
67
|
-
}
|
|
68
|
-
|
|
69
43
|
/** The self-contained ingest page. Pure — the same output for the same
|
|
70
44
|
* `title` every time; every piece of state (the session, each grounded fact)
|
|
71
45
|
* is computed live in the browser once the sibling ingest bundle loads. */
|
|
@@ -159,7 +133,7 @@ ${THEME_TOKENS_CSS}
|
|
|
159
133
|
the right of the ingest column (a real layout column, not an overlay) —
|
|
160
134
|
the same class names and breakpoint chat-page-viz.mjs's own docked panel
|
|
161
135
|
uses, re-rendered after boot and after every ingest from
|
|
162
|
-
window.
|
|
136
|
+
window.tmct's own memoryStats(). */
|
|
163
137
|
.statsPanel { flex: 0 0 300px; max-width: 300px; overflow-y: auto; border-left: 1px solid var(--line); padding: 1.1rem 1.2rem 1.6rem; font-family: ${MONO_STACK}; font-size: .74rem; line-height: 1.55; display: flex; flex-direction: column; }
|
|
164
138
|
.statsPanel h2 { font-size: .66rem; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); margin: 1.3rem 0 .5rem; }
|
|
165
139
|
.statsPanel h2:first-child { margin-top: 0; }
|
|
@@ -385,8 +359,8 @@ ${THEME_TOKENS_CSS}
|
|
|
385
359
|
// ---- memory stats: the docked panel, same convention as chat.html --------
|
|
386
360
|
async function renderStatsPanel(stats) {
|
|
387
361
|
if (!stats) {
|
|
388
|
-
if (!session || !window.
|
|
389
|
-
try { stats = await window.
|
|
362
|
+
if (!session || !window.tmct.page.memoryStats) return;
|
|
363
|
+
try { stats = await window.tmct.page.memoryStats(session.memoryDir); }
|
|
390
364
|
catch { return; }
|
|
391
365
|
}
|
|
392
366
|
factPillValueEl.textContent = Number(stats.total || 0).toLocaleString();
|
|
@@ -440,30 +414,14 @@ ${THEME_TOKENS_CSS}
|
|
|
440
414
|
console.warn("tmct ingest: chat-seed.json unavailable — starting unseeded", err);
|
|
441
415
|
}
|
|
442
416
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
}
|
|
447
|
-
function newSession() {
|
|
448
|
-
return window.tmctIngest.createIngestSession({ seedPayload: cloneSeed(), vocabSeeded: Boolean(seedPayload) });
|
|
417
|
+
const cloneMemoryPayload = ${cloneMemoryPayload.toString()};
|
|
418
|
+
async function newSession() {
|
|
419
|
+
return window.tmct.open({ seedPayload: cloneMemoryPayload(seedPayload), vocabSeeded: Boolean(seedPayload) });
|
|
449
420
|
}
|
|
450
421
|
|
|
451
422
|
// ---- engine boot ---------------------------------------------------------
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
let settled = false;
|
|
455
|
-
const timeout = new Promise((_, reject) => setTimeout(() => { if (!settled) reject(new Error("wink load stalled")); }, WINK_LOAD_TIMEOUT_MS));
|
|
456
|
-
try {
|
|
457
|
-
const mod = await Promise.race([import("./vendor/wink.js"), timeout]);
|
|
458
|
-
settled = true;
|
|
459
|
-
window.tmctIngest.registerWinkModel(() => ({ winkNLP: mod.winkNLP, model: mod.model }));
|
|
460
|
-
return "loaded";
|
|
461
|
-
} catch (err) {
|
|
462
|
-
settled = true;
|
|
463
|
-
console.warn("tmct ingest: the wink vendor asset failed to load; the recognizer needs it to split and parse sentences", err);
|
|
464
|
-
return "unavailable";
|
|
465
|
-
}
|
|
466
|
-
}
|
|
423
|
+
const loadWinkVendor = ${loadWinkVendor.toString()};
|
|
424
|
+
const tryLoadWink = loadWinkVendor({ register: (factory) => window.tmct.page.registerWinkModel(factory) });
|
|
467
425
|
|
|
468
426
|
// The deploy's own version, read off the service worker file the build
|
|
469
427
|
// already stamps — the only same-origin place the number exists at runtime
|
|
@@ -509,10 +467,10 @@ ${THEME_TOKENS_CSS}
|
|
|
509
467
|
clearTimeout(saveTimer);
|
|
510
468
|
saveTimer = null;
|
|
511
469
|
if (persist) await persist.clear();
|
|
512
|
-
session = newSession();
|
|
470
|
+
session = await newSession();
|
|
513
471
|
clearFactsPane();
|
|
514
472
|
updateIngestEnabled();
|
|
515
|
-
const stats = await window.
|
|
473
|
+
const stats = await window.tmct.page.memoryStats(session.memoryDir);
|
|
516
474
|
statusEl.textContent = "forgot everything taught on this device. Back to the fresh seed (" + statsSummaryLine(stats, bandLabelFor) + ").";
|
|
517
475
|
await renderStatsPanel(stats);
|
|
518
476
|
}
|
|
@@ -528,9 +486,9 @@ ${THEME_TOKENS_CSS}
|
|
|
528
486
|
await fetchSeedIfWanted();
|
|
529
487
|
clearTimeout(saveTimer);
|
|
530
488
|
saveTimer = null;
|
|
531
|
-
session = newSession();
|
|
489
|
+
session = await newSession();
|
|
532
490
|
clearFactsPane();
|
|
533
|
-
const stats = await window.
|
|
491
|
+
const stats = await window.tmct.page.memoryStats(session.memoryDir);
|
|
534
492
|
statusEl.textContent = statsSummaryLine(stats, bandLabelFor) + ". Ready.";
|
|
535
493
|
await renderStatsPanel(stats);
|
|
536
494
|
updateIngestEnabled();
|
|
@@ -538,7 +496,7 @@ ${THEME_TOKENS_CSS}
|
|
|
538
496
|
});
|
|
539
497
|
|
|
540
498
|
async function boot() {
|
|
541
|
-
if (!window.
|
|
499
|
+
if (!window.tmct) {
|
|
542
500
|
statusEl.textContent = "the ingest engine didn't load. This page needs its build step (npm run demo:build)";
|
|
543
501
|
return;
|
|
544
502
|
}
|
|
@@ -549,16 +507,16 @@ ${THEME_TOKENS_CSS}
|
|
|
549
507
|
fetchSiteVersion().then((v) => { siteVersion = v; }),
|
|
550
508
|
]);
|
|
551
509
|
progressActive = false;
|
|
552
|
-
if (window.
|
|
553
|
-
persist = window.
|
|
510
|
+
if (window.tmct.page.openPersistedStore) {
|
|
511
|
+
persist = window.tmct.page.openPersistedStore({ storeKey: "ingest", stamp: siteVersion + ":" + seedFacts + ":" + SEED_STAMP });
|
|
554
512
|
}
|
|
555
513
|
const savedRecord = persist ? await persist.load() : null;
|
|
556
514
|
session = savedRecord && savedRecord.payload
|
|
557
|
-
? window.
|
|
558
|
-
: newSession();
|
|
515
|
+
? await window.tmct.open({ seedPayload: savedRecord.payload, vocabSeeded: true })
|
|
516
|
+
: await newSession();
|
|
559
517
|
setMode(false);
|
|
560
518
|
updateIngestEnabled();
|
|
561
|
-
const stats = await window.
|
|
519
|
+
const stats = await window.tmct.page.memoryStats(session.memoryDir);
|
|
562
520
|
const winkPart = winkStatus === "loaded"
|
|
563
521
|
? "wink-nlp: loaded"
|
|
564
522
|
: "wink-nlp unavailable. The recognizer can't split sentences without it";
|
|
@@ -611,10 +569,10 @@ ${THEME_TOKENS_CSS}
|
|
|
611
569
|
|
|
612
570
|
// ---- download the canonical facts as JSONL -------------------------------
|
|
613
571
|
downloadBtn.addEventListener("click", async () => {
|
|
614
|
-
if (!session || !window.
|
|
572
|
+
if (!session || !window.tmct.page.exportFactsJsonl) return;
|
|
615
573
|
let jsonl;
|
|
616
574
|
try {
|
|
617
|
-
jsonl = await window.
|
|
575
|
+
jsonl = await window.tmct.page.exportFactsJsonl(session.memoryDir);
|
|
618
576
|
} catch (err) {
|
|
619
577
|
statusEl.textContent = "couldn't build the download (" + (err && err.message ? err.message : err) + ")";
|
|
620
578
|
return;
|