@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
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
import {
|
|
30
30
|
loadLexicon, lookupNoun, lookupVerb, lookupAdjective, lookupProperName,
|
|
31
|
-
predicateOf, numberOf, classify,
|
|
31
|
+
predicateOf, numberOf, classify, OF_CLASSIFIER_HEADS, OF_PARTITIVE_HEADS,
|
|
32
32
|
} from "./lexicon.mjs";
|
|
33
33
|
import { fuzzyMatchInSet } from "../interpret/fuzzy.mjs";
|
|
34
34
|
|
|
@@ -85,7 +85,15 @@ function local(lexicon, term) {
|
|
|
85
85
|
const stripDet = (tokens) =>
|
|
86
86
|
tokens.length > 1 && DET.has(tokens[0].toLowerCase()) ? tokens.slice(1) : tokens;
|
|
87
87
|
|
|
88
|
-
/**
|
|
88
|
+
/** A token a compound noun phrase may be built out of: a plain word (never a
|
|
89
|
+
* code-shaped ref, a number or a CURIE) that is not a DECLARED proper name —
|
|
90
|
+
* a name in a compound slot ("GitLab pipeline") is a structural miss, not a
|
|
91
|
+
* compound. Shared by both compound arms of resolveNP below. */
|
|
92
|
+
const compoundableWord = (lexicon, token) =>
|
|
93
|
+
/^[a-z][a-z'-]*$/i.test(token) && !lookupProperName(lexicon, token);
|
|
94
|
+
|
|
95
|
+
/** Resolve a noun phrase: PROPERNAME | code-ref | NOUN | ADJ NOUN | NOUN NOUN
|
|
96
|
+
* | NOUN of NOUN (the last two opt-in, see `allowCompound`).
|
|
89
97
|
* Returns { term, individual, extras, unknown } — `term` null on a miss with
|
|
90
98
|
* the undeclared tokens in `unknown` (empty `unknown` = structurally
|
|
91
99
|
* unparseable phrase → the caller returns a hard null). `extras` carries the
|
|
@@ -149,8 +157,7 @@ function resolveNP(lexicon, tokensIn, { allowCompound = false } = {}) {
|
|
|
149
157
|
// DECLARED proper name in either slot ("GitLab pipeline") keeps the
|
|
150
158
|
// structural miss below — a name in the wrong slot, not a compound.
|
|
151
159
|
if (allowCompound
|
|
152
|
-
&&
|
|
153
|
-
&& /^[a-z][a-z'-]*$/i.test(tokens[0]) && /^[a-z][a-z'-]*$/i.test(tokens[1])) {
|
|
160
|
+
&& compoundableWord(lexicon, tokens[0]) && compoundableWord(lexicon, tokens[1])) {
|
|
154
161
|
const n0 = lookupNoun(lexicon, tokens[0], { singularOnly: false });
|
|
155
162
|
const n1 = lookupNoun(lexicon, tokens[1], { singularOnly });
|
|
156
163
|
if (n0 && n1) {
|
|
@@ -162,6 +169,35 @@ function resolveNP(lexicon, tokensIn, { allowCompound = false } = {}) {
|
|
|
162
169
|
const unknown = tokens.filter((t) => !classify(t, lexicon));
|
|
163
170
|
return { term: null, individual: false, extras: [], unknown };
|
|
164
171
|
}
|
|
172
|
+
// "NOUN of NOUN" is ONE compound noun ("unit of work", "chain of command",
|
|
173
|
+
// "conflict of interest"), joined with its own "of" the same way two plain
|
|
174
|
+
// nouns are joined with a space, so the taught fact and the query side
|
|
175
|
+
// unify on one term. Opt-in through the same `allowCompound` gate and the
|
|
176
|
+
// same compoundableWord shape test as the two-noun arm above; the head noun
|
|
177
|
+
// leads, so an "a"/"an" agrees with IT.
|
|
178
|
+
//
|
|
179
|
+
// Which "of" this claims is decided by the head, against the two closed
|
|
180
|
+
// of-frame vocabularies in lexicon.mjs — the same ones the fact extractor
|
|
181
|
+
// reads. A CLASSIFIER head is a rewrite of the inner noun rather than a
|
|
182
|
+
// compound ("a kind of dog" is a dog), and a PARTITIVE head states quantity
|
|
183
|
+
// or composition rather than a class ("a piece of cake", "a lot of dogs");
|
|
184
|
+
// both decline here and land on the miss below. The relational reading is
|
|
185
|
+
// pattern 7's and is claimed before any of this: parseAce routes "the N of N
|
|
186
|
+
// is VALUE" to parseOfForm, so "the version of tmct is 0.2.0" never reaches
|
|
187
|
+
// a copula split at all. An inner determiner is likewise out of the shape —
|
|
188
|
+
// "a piece of the cake" is four tokens and no compound noun carries one.
|
|
189
|
+
if (tokens.length === 3 && allowCompound && tokens[1].toLowerCase() === "of"
|
|
190
|
+
&& compoundableWord(lexicon, tokens[0]) && compoundableWord(lexicon, tokens[2])) {
|
|
191
|
+
const head = tokens[0].toLowerCase();
|
|
192
|
+
const inner = tokens[2].toLowerCase();
|
|
193
|
+
if (!OF_CLASSIFIER_HEADS.has(head) && !OF_PARTITIVE_HEADS.has(head)) {
|
|
194
|
+
const headNoun = lookupNoun(lexicon, head, { singularOnly });
|
|
195
|
+
const innerNoun = lookupNoun(lexicon, inner, { singularOnly: false });
|
|
196
|
+
if (headNoun && innerNoun) {
|
|
197
|
+
return { term: `${ns}${head} of ${inner}`, individual: false, noun: headNoun, extras: [], unknown: [] };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
165
201
|
// 0 or 3+ tokens: not a fragment NP. Name the undeclared words if any.
|
|
166
202
|
return { term: null, individual: false, extras: [], unknown: tokens.filter((t) => !classify(t, lexicon)) };
|
|
167
203
|
}
|
|
@@ -43,6 +43,24 @@ export const QUANTIFIERS = Object.freeze({
|
|
|
43
43
|
exactly: "owl:cardinality",
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
// The two closed head vocabularies that say what an "of" between two nouns is
|
|
47
|
+
// DOING, so every reader of the of-frame decides it the same way instead of
|
|
48
|
+
// each growing its own list. A CLASSIFIER head reads THROUGH to the inner noun
|
|
49
|
+
// — "a kind of dog" is a dog, "a type of mammal" is a mammal — so the phrase
|
|
50
|
+
// is a rewrite of the inner noun, never a term of its own. A PARTITIVE head
|
|
51
|
+
// states quantity or composition — "a piece of cake", "a lot of dogs", "a body
|
|
52
|
+
// of ice" — so the phrase names no class at all. Anything else ("unit of
|
|
53
|
+
// work", "chain of command") is a compound noun the of belongs to.
|
|
54
|
+
/** Of-frame heads that read through to the inner noun. */
|
|
55
|
+
export const OF_CLASSIFIER_HEADS = Object.freeze(new Set([
|
|
56
|
+
"type", "kind", "sort", "form", "class", "variety", "species", "breed", "genus",
|
|
57
|
+
]));
|
|
58
|
+
/** Of-frame heads that state quantity or composition, not a class. */
|
|
59
|
+
export const OF_PARTITIVE_HEADS = Object.freeze(new Set([
|
|
60
|
+
"body", "mass", "group", "collection", "set", "series", "number", "amount",
|
|
61
|
+
"piece", "part", "lot", "pair", "bunch", "pile",
|
|
62
|
+
]));
|
|
63
|
+
|
|
46
64
|
const NUMBER_WORDS = Object.freeze({
|
|
47
65
|
one: 1, two: 2, three: 3, four: 4, five: 5,
|
|
48
66
|
six: 6, seven: 7, eight: 8, nine: 9, ten: 10,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// mud-facts.mjs — the one `mgx:is-predator` reader shared across the
|
|
2
|
+
// client/server boundary: mud-turn.mjs (the server-side turn engine, deciding
|
|
3
|
+
// which rooms to route an animal away from) and mud-viz.mjs (the client-facing
|
|
4
|
+
// view, deciding which room to draw a pounce against) each asked "which
|
|
5
|
+
// subject does the world mark dangerous" their own way, over the exact same
|
|
6
|
+
// filter. One reader here, so the two never drift apart on what counts as a
|
|
7
|
+
// predator.
|
|
8
|
+
|
|
9
|
+
/** Every subject the world marks `mgx:is-predator` true, in fact-row order.
|
|
10
|
+
* Pure. */
|
|
11
|
+
export function predatorSubjects(rows) {
|
|
12
|
+
return (rows || [])
|
|
13
|
+
.filter((r) => r.predicate === "mgx:is-predator" && r.object === "true")
|
|
14
|
+
.map((r) => r.subject);
|
|
15
|
+
}
|
|
@@ -35,25 +35,49 @@ export function isReferenceIndexEntry(e) {
|
|
|
35
35
|
&& Number.isInteger(e.r) && e.r > 0;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
/** A shard row: {term, title, text, summary, url, revid, isa
|
|
38
|
+
/** A shard row: {term, title, text, summary, url, revid, isa?, source?,
|
|
39
|
+
* licence?}. `source`/`licence` are optional per-entry overrides of the
|
|
40
|
+
* citation's source name and licence line (renderReferenceAnswer, below) —
|
|
41
|
+
* absent on every real Simple English Wikipedia row the build pipeline
|
|
42
|
+
* emits, present only on a hand-added synthetic entry (e.g. a demo/test
|
|
43
|
+
* term) whose citation must name its OWN real source, not Wikipedia's. */
|
|
39
44
|
export function isReferenceArticleRow(row) {
|
|
40
45
|
if (!row || typeof row !== "object") return false;
|
|
41
46
|
for (const field of ["term", "title", "text", "summary", "url"]) {
|
|
42
47
|
if (typeof row[field] !== "string" || !row[field]) return false;
|
|
43
48
|
}
|
|
44
49
|
if (!Number.isInteger(row.revid) || row.revid <= 0) return false;
|
|
45
|
-
|
|
50
|
+
for (const field of ["isa", "source", "licence"]) {
|
|
51
|
+
if (row[field] !== undefined && (typeof row[field] !== "string" || !row[field])) return false;
|
|
52
|
+
}
|
|
46
53
|
return true;
|
|
47
54
|
}
|
|
48
55
|
|
|
56
|
+
/** The default citation source/licence — every article the build pipeline
|
|
57
|
+
* (scripts/fetch-reference-pack.mjs) emits from the pinned Simple English
|
|
58
|
+
* Wikipedia dump carries neither field, so this is what renderReferenceAnswer
|
|
59
|
+
* falls back to for the overwhelming majority of rows. */
|
|
60
|
+
export const DEFAULT_REFERENCE_SOURCE = "Simple English Wikipedia";
|
|
61
|
+
export const DEFAULT_REFERENCE_LICENCE = "CC BY-SA 4.0";
|
|
62
|
+
|
|
49
63
|
/** The cited answer for a clean miss the pack could ground: the article's
|
|
50
|
-
* summary with its title, licence and
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
64
|
+
* summary with its title, licence and source always visible, then the grain
|
|
65
|
+
* cue. Without the cue a reader has to recognize the source name to tell
|
|
66
|
+
* this apart from a fact read out of their own graph — both answer the same
|
|
67
|
+
* "what is X" question in the same voice.
|
|
68
|
+
*
|
|
69
|
+
* The source/licence line reads the article's OWN `source`/`licence` fields
|
|
70
|
+
* when present, falling back to the Wikipedia defaults otherwise — every
|
|
71
|
+
* real pack row renders byte-identically to before this existed. Wikipedia's
|
|
72
|
+
* revision-pinned `?oldid=` query is a convention of that source alone, so it
|
|
73
|
+
* is only appended when the article is citing the default source; a
|
|
74
|
+
* non-Wikipedia entry's own `url` is shown bare. */
|
|
54
75
|
export function renderReferenceAnswer(term, article) {
|
|
76
|
+
const source = article.source ?? DEFAULT_REFERENCE_SOURCE;
|
|
77
|
+
const licence = article.licence ?? DEFAULT_REFERENCE_LICENCE;
|
|
78
|
+
const url = source === DEFAULT_REFERENCE_SOURCE ? `${article.url}?oldid=${article.revid}` : article.url;
|
|
55
79
|
return `${term} — ${article.summary} (source: reference article "${article.title}", `
|
|
56
|
-
+
|
|
80
|
+
+ `${source}, ${licence} — ${url})`
|
|
57
81
|
+ ` ${GENERAL_VOCABULARY_CUE}`;
|
|
58
82
|
}
|
|
59
83
|
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
import { resolveOne, backwardChainWorld, resolveMemoryTerm } from "./resolver.mjs";
|
|
32
32
|
import { plan, isMultiStep, decompose, MAX_STEPS } from "./planner.mjs";
|
|
33
33
|
import { goalReason } from "./goal-reasoner.mjs";
|
|
34
|
-
import { capabilities } from "./registry.mjs";
|
|
34
|
+
import { capabilities, preconditionsOf, PRECOND } from "./registry.mjs";
|
|
35
35
|
import { registerTaughtActions } from "./taught.mjs";
|
|
36
36
|
import { intersect, fallbackIfEmpty, guardIfEmpty, memberIndividuals, membersReaching, resultSetOf } from "./results.mjs";
|
|
37
37
|
import { resolveObject } from "../ask.mjs";
|
|
@@ -288,23 +288,49 @@ export async function runCapabilityPlan(request, tools, ctx) {
|
|
|
288
288
|
* same store, re-reading it per request via ctx.readTaughtStore. The new
|
|
289
289
|
* registrations' unregister disposers ride the ctx as `ctx.disposers`; the
|
|
290
290
|
* caller runs them when the ctx is done. The same `memoryDir` also opens
|
|
291
|
-
* `ctx.resolveMemoryTerm` — resolveOne's binding oracle for a
|
|
292
|
-
* (tmct_related's `term`), re-reading the store's fact rows per request
|
|
291
|
+
* `ctx.resolveMemoryTerm` — resolveOne's binding oracle for a KINDS.MemoryTerm
|
|
292
|
+
* slot (tmct_related's `term`), re-reading the store's fact rows per request
|
|
293
293
|
* through resolveMemoryTerm (resolver.mjs), the memory-graph sibling of
|
|
294
|
-
* `resolve` above.
|
|
294
|
+
* `resolve` above.
|
|
295
|
+
*
|
|
296
|
+
* MEMORY-ONLY MODE: pass a `memoryDir` with no `graph` and no `source`, and
|
|
297
|
+
* the ctx builds with no code graph at all — the shape of a page that lives on
|
|
298
|
+
* the memory graph alone. `resolve` then misses every term (a code-graph slot
|
|
299
|
+
* refuses honestly at binding), dispatch refuses any capability whose
|
|
300
|
+
* preconditions need a loaded code graph, and the memory lanes — MemoryTerm
|
|
301
|
+
* binding, the taught world-goal simulation — carry the whole ctx. `config`
|
|
302
|
+
* (if any) is still handed to dispatchTool so a memory-store tool can derive
|
|
303
|
+
* its backend. A call with no graph, no source and no memoryDir is a wiring
|
|
304
|
+
* error and throws. */
|
|
295
305
|
export async function buildCapabilityPlanCtx({
|
|
296
|
-
config, source, tel = null, graph = null, memoryDir = null,
|
|
306
|
+
config, source = null, tel = null, graph = null, memoryDir = null,
|
|
297
307
|
dispatchTool, isToolError = () => false, selectTool = null,
|
|
298
308
|
loadMemory = null, readFactRows = null, readRuleRows = null,
|
|
299
309
|
} = {}) {
|
|
300
|
-
const g = graph || parseEntities(await source.fetchEntities(config));
|
|
301
|
-
|
|
310
|
+
const g = graph || (source ? parseEntities(await source.fetchEntities(config)) : null);
|
|
311
|
+
if (!g && !(memoryDir && loadMemory)) {
|
|
312
|
+
throw new Error("buildCapabilityPlanCtx: pass a graph, a source to load one, or a memoryDir (memory-only mode)");
|
|
313
|
+
}
|
|
314
|
+
const resolve = g ? (term) => resolveObject(g, term) : () => ({ match: null, ambiguous: false, candidates: [] });
|
|
315
|
+
const needsCodeGraph = (name) => preconditionsOf(name).some((p) => p.pred === PRECOND.graphLoaded);
|
|
302
316
|
const dispatch = async (name, input) => {
|
|
317
|
+
if (!g && needsCodeGraph(name)) {
|
|
318
|
+
return { ok: false, error: `${name} queries the code graph, and this memory-only context has none loaded` };
|
|
319
|
+
}
|
|
303
320
|
try {
|
|
304
|
-
|
|
321
|
+
// `source ?? undefined` so a memory-only caller's explicit null never
|
|
322
|
+
// suppresses dispatchTool's own default source for a tool that loads.
|
|
323
|
+
// `memoryBackend` hands a memory-reading tool the SAME open store this
|
|
324
|
+
// ctx binds its MemoryTerm slots against, so binding and dispatch can
|
|
325
|
+
// never answer from two different stores — and so a store that was never
|
|
326
|
+
// derived from a config (a browser page's in-memory one) is reachable at
|
|
327
|
+
// all.
|
|
328
|
+
const text = await dispatchTool(name, input, {
|
|
329
|
+
config, source: source ?? undefined, tel, memoryBackend: memoryDir ?? undefined,
|
|
330
|
+
});
|
|
305
331
|
const primary = input && (input.symbol ?? input.module ?? input.class ?? input.query);
|
|
306
332
|
const resolved = primary ? resolve(String(primary)).match : null;
|
|
307
|
-
const result = resultSetOf(g, name, input, resolved);
|
|
333
|
+
const result = g ? resultSetOf(g, name, input, resolved) : [];
|
|
308
334
|
return { ok: true, text, resolved, result };
|
|
309
335
|
} catch (e) {
|
|
310
336
|
if (isToolError(e)) return { ok: false, error: e.message };
|
|
@@ -26,16 +26,25 @@ export const VOCAB = Object.freeze({
|
|
|
26
26
|
|
|
27
27
|
// Parameter entity-KINDS — the seon/mgx classes a slot ranges over. `Query` and
|
|
28
28
|
// `Kind`/`Package` are free-text / enum slots (no graph resolution); the rest
|
|
29
|
-
// name a graph entity the resolver must prove RESOLVES before the call fires
|
|
29
|
+
// name a graph entity the resolver must prove RESOLVES before the call fires —
|
|
30
|
+
// code-graph kinds through resolveObject (ask.mjs), MemoryTerm through
|
|
31
|
+
// resolveMemoryTerm (resolver.mjs), the conversational-memory sibling oracle.
|
|
30
32
|
export const KINDS = Object.freeze({
|
|
31
33
|
Symbol: "seon:CodeEntity", // any code symbol: function/method/class/module/attribute
|
|
32
34
|
Module: "mgx:Module", // SEON has no JS-module class (its nearest are Namespace/main:File); owned
|
|
33
35
|
Class: "seon:ClassType", // SEON's real class for a class definition
|
|
36
|
+
MemoryTerm: "mgx:MemoryTerm", // owned: a conversational-memory term (a minted skos:Concept or a world-fact subject/object) — binds in the memory graph, never the code graph
|
|
34
37
|
Query: "cap:FreeText", // lexical search string — no resolution precondition
|
|
35
38
|
Kind: "cap:KindFilter", // enum: function|class|method|… (search filter)
|
|
36
39
|
Package: "cap:PackageName", // optional architecture-scope filter
|
|
37
40
|
});
|
|
38
41
|
|
|
42
|
+
// The kinds that bind in the conversational-memory graph (resolveMemoryTerm)
|
|
43
|
+
// rather than the code graph (resolveObject). A consumer that walks KINDS for
|
|
44
|
+
// code-graph-resolvable classes (e.g. the synthbench enumerator's focus
|
|
45
|
+
// classes) must exclude these — a memory term is never a code-graph focus.
|
|
46
|
+
export const MEMORY_KINDS = Object.freeze([KINDS.MemoryTerm]);
|
|
47
|
+
|
|
39
48
|
// Precondition PREDICATE tags (the small closed vocabulary a precondition uses).
|
|
40
49
|
export const PRECOND = Object.freeze({
|
|
41
50
|
graphLoaded: "cap:graph-loaded", // a graph artifact is present + parseable
|
|
@@ -87,7 +96,8 @@ function capability({ name, label, question, params = [], preconditions = [], ad
|
|
|
87
96
|
// callees/tests/history/… take `symbol`; impact/exports take `module`; members/
|
|
88
97
|
// subclasses take `class`; search takes `query` (+ optional kind/name/decorator);
|
|
89
98
|
// architecture takes an optional `package`; untested takes nothing; related
|
|
90
|
-
// takes `term` (a memory-graph concept term)
|
|
99
|
+
// takes `term` (a memory-graph concept term); sprite takes `class` (a memory-graph
|
|
100
|
+
// term) plus optional `expression`/`size`.
|
|
91
101
|
|
|
92
102
|
const CAPABILITIES = Object.freeze([
|
|
93
103
|
capability({
|
|
@@ -187,10 +197,20 @@ const CAPABILITIES = Object.freeze([
|
|
|
187
197
|
}),
|
|
188
198
|
capability({
|
|
189
199
|
name: "tmct_related", label: "related", question: "a term's synonyms and related concepts (the SKOS view over the conversational-memory graph)",
|
|
190
|
-
params: [param("term", KINDS.
|
|
191
|
-
preconditions: [memoryFacts()],
|
|
200
|
+
params: [param("term", KINDS.MemoryTerm, { note: "a concept term, matched against memory relation facts rather than resolved in the code graph" })],
|
|
201
|
+
preconditions: [memoryFacts(), resolves("term", KINDS.MemoryTerm)],
|
|
192
202
|
add: [knows("related", "term")],
|
|
193
203
|
}),
|
|
204
|
+
capability({
|
|
205
|
+
name: "tmct_sprite", label: "sprite", question: "the sprite for a class, with the expression and size asked for",
|
|
206
|
+
params: [
|
|
207
|
+
param("class", KINDS.MemoryTerm, { arg: "class", note: "a world class, matched against the memory graph's own fact rows rather than resolved in the code graph" }),
|
|
208
|
+
param("expression", KINDS.Kind, { required: false, note: "one of sprite-expressions.mjs's palette words, carried as an mgx:feels fact" }),
|
|
209
|
+
param("size", KINDS.Kind, { required: false, note: "a taught mgx:hasProperty size word, resolved to a render scale — not a template tier" }),
|
|
210
|
+
],
|
|
211
|
+
preconditions: [memoryFacts(), resolves("class", KINDS.MemoryTerm)],
|
|
212
|
+
add: [knows("sprite", "class")],
|
|
213
|
+
}),
|
|
194
214
|
]);
|
|
195
215
|
|
|
196
216
|
// The live capability set: the built-in frozen array is the seed; registration
|
|
@@ -11,15 +11,20 @@
|
|
|
11
11
|
// grammar doesn't carry, and a rescue path when the NL parse selects an out-of-set
|
|
12
12
|
// capability).
|
|
13
13
|
//
|
|
14
|
-
// Entity binding is delegated to `resolveObject` (ask.mjs)
|
|
15
|
-
//
|
|
14
|
+
// Entity binding is delegated to `resolveObject` (ask.mjs) for code-graph slots, and to
|
|
15
|
+
// `resolveMemoryTerm` (below) for a slot whose registry kind is KINDS.MemoryTerm — the
|
|
16
|
+
// conversational-memory graph's own binding oracle. An ambiguous or no-match term is an
|
|
17
|
+
// honest refuse, never a guess, on both paths.
|
|
16
18
|
|
|
17
19
|
import { parseQuery } from "../ask.mjs";
|
|
18
20
|
import { SUPERLATIVE_EXTREMES } from "../ask-vocab.mjs";
|
|
19
21
|
import { buildSkosConceptView } from "../skos-view.mjs";
|
|
22
|
+
import { normFactTerm } from "../hash.mjs";
|
|
20
23
|
import { edgesOfKind } from "../codegraph.mjs";
|
|
24
|
+
import { EXPRESSION_PALETTE } from "../sprite-expressions.mjs";
|
|
25
|
+
import { sizeScaleFor } from "../sprite-size.mjs";
|
|
21
26
|
import {
|
|
22
|
-
capabilities, capabilityByName, preconditionsOf, effectsOf, PRECOND,
|
|
27
|
+
capabilities, capabilityByName, parametersOf, preconditionsOf, effectsOf, PRECOND, KINDS, MEMORY_KINDS,
|
|
23
28
|
} from "./registry.mjs";
|
|
24
29
|
import { hallucinationsIn } from "./call-validator.mjs";
|
|
25
30
|
|
|
@@ -64,6 +69,25 @@ export const UNMAPPED_KINDS = Object.freeze({
|
|
|
64
69
|
// via resolveMemoryTerm, the memory-graph sibling of resolveObject's code-graph binding.
|
|
65
70
|
export const NOT_NL_REACHABLE = Object.freeze({});
|
|
66
71
|
|
|
72
|
+
/** The property predicate sprite-size.mjs reads a size word off. Used to PROBE
|
|
73
|
+
* its own closed scale table instead of restating it here: a word is a size
|
|
74
|
+
* word exactly when it moves the scale away from 1. */
|
|
75
|
+
const SIZE_FACT_PREDICATE = "mgx:hasProperty";
|
|
76
|
+
|
|
77
|
+
/** The sprite frame's two OPTIONAL slots, read straight out of the request.
|
|
78
|
+
* Both vocabularies are the real ones — sprite-expressions.mjs's own palette
|
|
79
|
+
* and sprite-size.mjs's own scale table (probed, not copied) — so a word
|
|
80
|
+
* either module gains is a word this frame gains. A request naming neither
|
|
81
|
+
* fills neither, and the sprite resolves plain. */
|
|
82
|
+
function spriteSlots(request) {
|
|
83
|
+
const slots = {};
|
|
84
|
+
for (const word of String(request).toLowerCase().match(/[a-z]+/g) || []) {
|
|
85
|
+
if (!slots.expression && Object.hasOwn(EXPRESSION_PALETTE, word)) slots.expression = word;
|
|
86
|
+
if (!slots.size && sizeScaleFor([{ predicate: SIZE_FACT_PREDICATE, object: word }]) !== 1) slots.size = word;
|
|
87
|
+
}
|
|
88
|
+
return slots;
|
|
89
|
+
}
|
|
90
|
+
|
|
67
91
|
// ---- imperative intent FRAMES (fills what the relational grammar and command register
|
|
68
92
|
// both miss). regex -> { topic, arg | noArg }. Ordered: first match wins.
|
|
69
93
|
export const FRAMES = Object.freeze([
|
|
@@ -86,10 +110,19 @@ export const FRAMES = Object.freeze([
|
|
|
86
110
|
{ re: /\bsignature\b/i, topic: "signature", arg: "symbol" },
|
|
87
111
|
// tmct_related: the SKOS synonym/related-concept surface over the memory graph
|
|
88
112
|
// — "another word for X" / "a synonym for X" / "synonyms of X" / "what's
|
|
89
|
-
// related to X". `
|
|
90
|
-
//
|
|
91
|
-
//
|
|
113
|
+
// related to X". Its `term` slot is declared KINDS.MemoryTerm in the registry,
|
|
114
|
+
// so resolveOne binds it through resolveMemoryTerm (below) rather than the
|
|
115
|
+
// code graph's resolveObject — the registry kind, not this table, decides.
|
|
92
116
|
{ re: /\bsynonyms?\b|\banother\s+word\s+for\b|\brelated\s+(?:words?|concepts?|to)\b/i, topic: "related", arg: "term" },
|
|
117
|
+
// tmct_sprite: the sprite surface over the memory graph's own world facts —
|
|
118
|
+
// "the large sprite for a happy spider", "what does a hungry fly look like",
|
|
119
|
+
// "show me the spider icon". Its `class` slot is the second KINDS.MemoryTerm
|
|
120
|
+
// one, so it binds the same way tmct_related's `term` does. `slots` is what
|
|
121
|
+
// makes this frame different: it fills the two OPTIONAL args itself.
|
|
122
|
+
{
|
|
123
|
+
re: /\bsprite\b|\bicon\b|\bavatar\b|\bpicture\s+of\b|\bwhat\s+does\s+.+\s+look\s+like\b/i,
|
|
124
|
+
topic: "sprite", arg: "class", slots: spriteSlots,
|
|
125
|
+
},
|
|
93
126
|
{ re: /\bdescribe\b|\bexplain\b|what\s+is\b|tell\s+me\s+about\b|definition\s+of\b/i, topic: "description", arg: "symbol" },
|
|
94
127
|
{ re: /\bsearch\b|\bfind\b|look\s+for\b/i, topic: "matches", arg: "query" },
|
|
95
128
|
]);
|
|
@@ -132,13 +165,17 @@ const STOP = new Set([
|
|
|
132
165
|
"untested", "blast", "radius", "change", "changes", "changing", "reach", "reaches", "affect", "affects",
|
|
133
166
|
"explain", "edge", "edges", "graph", "outgoing", "site", "sites", "invoke", "invokes", "run", "runs", "execute", "executes",
|
|
134
167
|
"word", "words", "another", "synonym", "synonyms", "related", "relate", "relates", "like", "concept", "concepts",
|
|
168
|
+
"sprite", "sprites", "icon", "icons", "avatar", "avatars", "picture", "pictures", "image", "images", "looks",
|
|
135
169
|
]);
|
|
136
170
|
|
|
137
171
|
/** Pull one entity token from a request (imperative-frame slot-filling). Prefer a
|
|
138
|
-
* path/dotted/CamelCase token, else the last non-stopword identifier. "" if none.
|
|
139
|
-
|
|
172
|
+
* path/dotted/CamelCase token, else the last non-stopword identifier. "" if none.
|
|
173
|
+
* `alreadyBound` holds the words a frame's own slot-filler has already claimed
|
|
174
|
+
* (the sprite frame's expression/size), so they can't also be read as the entity. */
|
|
175
|
+
export function extractEntity(request, alreadyBound = null) {
|
|
140
176
|
const tokens = String(request).match(/[A-Za-z_][A-Za-z0-9_./-]*/g) || [];
|
|
141
|
-
const
|
|
177
|
+
const claimed = alreadyBound || new Set();
|
|
178
|
+
const pool = tokens.filter((t) => !STOP.has(t.toLowerCase()) && !claimed.has(t.toLowerCase()));
|
|
142
179
|
const strong = pool.filter((t) => /[./]/.test(t) || /^[A-Z][a-z]/.test(t) || /\.[a-z]+$/.test(t));
|
|
143
180
|
const pick = strong.length ? strong : pool;
|
|
144
181
|
return pick.length ? pick[pick.length - 1] : "";
|
|
@@ -187,13 +224,14 @@ export function mapFrame(request) {
|
|
|
187
224
|
const cap = backwardChain(f.topic);
|
|
188
225
|
if (!cap) continue;
|
|
189
226
|
if (f.noArg) return { name: cap.name, noArg: true, topic: f.topic, source: "frame", why: [`imperative frame => goal (knows ${f.topic})`, `backward-chain => ${cap.name}`] };
|
|
190
|
-
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
227
|
+
// A frame's own OPTIONAL slots, filled from the request's vocabulary before
|
|
228
|
+
// the entity is read, so a word a slot claimed can't also be read as the entity.
|
|
229
|
+
const slots = f.slots ? f.slots(request) : null;
|
|
230
|
+
const term = f.arg === "query"
|
|
231
|
+
? searchQuery(request)
|
|
232
|
+
: extractEntity(request, slots ? new Set(Object.values(slots)) : null);
|
|
195
233
|
return {
|
|
196
|
-
name: cap.name, arg: f.arg, term, topic: f.topic, source: "frame",
|
|
234
|
+
name: cap.name, arg: f.arg, term, topic: f.topic, source: "frame", slots,
|
|
197
235
|
why: [`imperative frame => goal (knows ${f.topic} ?${f.arg})`, `backward-chain => ${cap.name}`],
|
|
198
236
|
};
|
|
199
237
|
}
|
|
@@ -215,24 +253,41 @@ export function commandCapability(request, declaredNames, selectTool) {
|
|
|
215
253
|
return { name: sel.name, input, source: "command", why: [`command register: "${String(request).trim().split(/\s+/)[0]}" => ${sel.name}`] };
|
|
216
254
|
}
|
|
217
255
|
|
|
218
|
-
/** Resolve a term against the memory graph
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
256
|
+
/** Resolve a term against the conversational-memory graph — the memory-graph
|
|
257
|
+
* sibling of resolveObject, for a slot whose registry kind is KINDS.MemoryTerm.
|
|
258
|
+
* Two tiers, tried in order, both EXACT after normFactTerm (no fuzzy tier — a
|
|
259
|
+
* near-miss is a miss, never a guess):
|
|
260
|
+
* 1. "memory-concept" — the SKOS concept view (skos-view.mjs) mints a
|
|
261
|
+
* concept for the term, i.e. the store holds synonym/related facts for it;
|
|
262
|
+
* 2. "memory-fact-term" — the term appears as the subject or object of any
|
|
263
|
+
* stored fact row (a world-fact term: a room, a game agent, a taught
|
|
264
|
+
* individual).
|
|
265
|
+
* Same `{ match, ambiguous }` shape resolveObject returns, so resolveOne's
|
|
266
|
+
* generic binding step treats both oracles the same way: `match.label` is the
|
|
267
|
+
* RAW queried term (not a canonicalised prefLabel), because a memory tool's
|
|
268
|
+
* own lookup (e.g. relatedForTerm) re-derives its rows from whatever term it's
|
|
269
|
+
* given — the bound call should carry what the user actually asked about. No
|
|
270
|
+
* code-graph fallback: a term the store holds no facts for at all is an honest
|
|
271
|
+
* miss. `rows` is a loadMemory+readFactRows payload — the same trust-bearing
|
|
272
|
+
* rows skosRelatedAnswer (chat.mjs) and the memory tool handlers read. */
|
|
230
273
|
export function resolveMemoryTerm(rows, term) {
|
|
231
274
|
const t = String(term || "").trim();
|
|
232
275
|
if (!t) return { match: null };
|
|
233
276
|
const view = buildSkosConceptView(rows);
|
|
234
|
-
if (
|
|
235
|
-
|
|
277
|
+
if (view.conceptIdForTerm(t)) return { match: { label: t, class: "skos:Concept" }, ambiguous: false, tier: "memory-concept" };
|
|
278
|
+
const n = normFactTerm(t);
|
|
279
|
+
if (n && rows.some((r) => normFactTerm(r.subject) === n || normFactTerm(r.object) === n)) {
|
|
280
|
+
return { match: { label: t, class: KINDS.MemoryTerm }, ambiguous: false, tier: "memory-fact-term" };
|
|
281
|
+
}
|
|
282
|
+
return { match: null };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** True when capability `capName`'s slot with arg key `arg` is declared a
|
|
286
|
+
* memory-graph kind (KINDS.MemoryTerm) — the registry-driven switch between
|
|
287
|
+
* the two binding oracles: resolveMemoryTerm for memory slots, the code
|
|
288
|
+
* graph's resolveObject for everything else. */
|
|
289
|
+
export function isMemoryTermSlot(capName, arg) {
|
|
290
|
+
return parametersOf(capName).some((p) => p.arg === arg && MEMORY_KINDS.includes(p.kind));
|
|
236
291
|
}
|
|
237
292
|
|
|
238
293
|
// ---- the full single-call resolver (async — binds + grounds) -----------------
|
|
@@ -328,27 +383,29 @@ export async function resolveOne(request, declaredNames, ctx, { execute = true }
|
|
|
328
383
|
if (!declared.has(pick.name)) return REFUSE(`selected ${pick.name} but it is not in the declared toolset`);
|
|
329
384
|
|
|
330
385
|
// A command pick already carries a bound input; an NL/frame pick carries a raw term
|
|
331
|
-
// we bind via resolveObject (code graph) or, for a
|
|
332
|
-
//
|
|
386
|
+
// we bind via resolveObject (code graph) or, for a KINDS.MemoryTerm slot (the
|
|
387
|
+
// registry's own declaration, read through isMemoryTermSlot), resolveMemoryTerm —
|
|
388
|
+
// the two binding oracles never mix on one pick.
|
|
333
389
|
let input = pick.input ? { ...pick.input } : {};
|
|
334
390
|
let resolved = null;
|
|
335
391
|
if (!pick.input && !pick.noArg) {
|
|
336
392
|
const term = String(pick.term || "").trim();
|
|
337
393
|
if (!term) return REFUSE(`the ${pick.topic} intent named no entity to bind`);
|
|
338
|
-
const
|
|
394
|
+
const memoryBound = isMemoryTermSlot(pick.name, pick.arg);
|
|
395
|
+
const r = memoryBound
|
|
339
396
|
? (ctx.resolveMemoryTerm ? await ctx.resolveMemoryTerm(term) : { match: null })
|
|
340
397
|
: (ctx.resolve ? ctx.resolve(term) : { match: { label: term }, ambiguous: false });
|
|
341
398
|
if (!r || !r.match) {
|
|
342
|
-
return REFUSE(
|
|
343
|
-
? `
|
|
399
|
+
return REFUSE(memoryBound
|
|
400
|
+
? `the memory graph holds no facts mentioning "${term}" (honest miss)`
|
|
344
401
|
: `"${term}" does not resolve to any graph entity (honest miss)`);
|
|
345
402
|
}
|
|
346
403
|
// A tied read: either resolveObject's own score-tie (r.ambiguous), or a same-tier
|
|
347
404
|
// candidate the graph's own `tests` edge ties to the match (a source module and
|
|
348
405
|
// its test module — a grain neither side's raw score alone reveals as tied).
|
|
349
|
-
// resolveMemoryTerm's
|
|
350
|
-
// sibling tie-check only applies on the code-graph resolution path.
|
|
351
|
-
const sibling = (!
|
|
406
|
+
// resolveMemoryTerm's exact-match tiers have no code-graph tests-edge notion, so
|
|
407
|
+
// the sibling tie-check only applies on the code-graph resolution path.
|
|
408
|
+
const sibling = (!memoryBound && !r.ambiguous)
|
|
352
409
|
? (r.candidates || []).find((c) => testModuleTie(ctx.graph, r.match, c))
|
|
353
410
|
: null;
|
|
354
411
|
if (r.ambiguous || sibling) {
|
|
@@ -360,10 +417,15 @@ export async function resolveOne(request, declaredNames, ctx, { execute = true }
|
|
|
360
417
|
return REFUSE(`"${term}" is ambiguous (${pool.map((m) => m.label).join(", ")}) — narrow it`, extra);
|
|
361
418
|
}
|
|
362
419
|
resolved = r.match;
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
420
|
+
// The frame's own optional slots ride alongside the bound entity — declared
|
|
421
|
+
// params of the same capability, so hallucinationsIn still validates them.
|
|
422
|
+
input = { [pick.arg]: r.match.label, ...(pick.slots || {}) };
|
|
423
|
+
why = [...why, memoryBound
|
|
424
|
+
? `resolveMemoryTerm: "${term}" binds in the memory graph (${r.match.class || "?"}, tier ${r.tier})`
|
|
366
425
|
: `resolveObject: "${term}" => ${r.match.label} (${r.match.class || "?"}, tier ${r.tier})`];
|
|
426
|
+
if (pick.slots && Object.keys(pick.slots).length) {
|
|
427
|
+
why = [...why, `frame slots bound from the request's own vocabulary: ${Object.entries(pick.slots).map(([k, v]) => `${k}=${v}`).join(", ")}`];
|
|
428
|
+
}
|
|
367
429
|
}
|
|
368
430
|
|
|
369
431
|
const call = { name: pick.name, input };
|