@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
|
@@ -32,7 +32,7 @@ import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml } from "./viz-the
|
|
|
32
32
|
import { provBucketFor } from "./ledger-viz.mjs";
|
|
33
33
|
import { createTicker, prefersReducedMotion } from "./viz-ticker.mjs";
|
|
34
34
|
import { sessionLogTimeOfDay, sessionLogHeaderMarkdown, sessionLogTurnMarkdown } from "./session-log-format.mjs";
|
|
35
|
-
import { bandLabelFor, statsSummaryLine, fetchWithProgress, renderStatsPanelInto } from "./memory-panel-viz.mjs";
|
|
35
|
+
import { bandLabelFor, statsSummaryLine, clearSiteAssetCaches, fetchWithProgress, renderStatsPanelInto } from "./memory-panel-viz.mjs";
|
|
36
36
|
|
|
37
37
|
const DEFAULT_TITLE = "the-mechanical-code-talker — talk to it";
|
|
38
38
|
|
|
@@ -185,6 +185,18 @@ export function tapeRowFor(direction, message) {
|
|
|
185
185
|
return { type: type, direction: direction, detail: detail, family: FAMILIES[type] || "link" };
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
/**
|
|
189
|
+
* The wire tape's own clock stamp for a message as it arrives —
|
|
190
|
+
* `HH:MM:SS.mmm`, local time, zero-padded.
|
|
191
|
+
*
|
|
192
|
+
* Self-contained (no outer refs), `.toString()`-splice safe.
|
|
193
|
+
*/
|
|
194
|
+
export function tapeClock() {
|
|
195
|
+
const at = new Date();
|
|
196
|
+
const pad = (n, width) => String(n).padStart(width, "0");
|
|
197
|
+
return pad(at.getHours(), 2) + ":" + pad(at.getMinutes(), 2) + ":" + pad(at.getSeconds(), 2) + "." + pad(at.getMilliseconds(), 3);
|
|
198
|
+
}
|
|
199
|
+
|
|
188
200
|
/**
|
|
189
201
|
* The node list: every peer this graph knows about, each with the node name it
|
|
190
202
|
* chose and the timestamp of the most recent fact it contributed, most
|
|
@@ -256,6 +268,24 @@ export function nodeInitials(name) {
|
|
|
256
268
|
return "??";
|
|
257
269
|
}
|
|
258
270
|
|
|
271
|
+
/**
|
|
272
|
+
* How long ago a node's most recent fact landed, relative to `nowMs`: "now"
|
|
273
|
+
* under 5s, otherwise the whole seconds/minutes/hours/days, coarsest unit
|
|
274
|
+
* that still reads as one number. `at` null/undefined (a peer with no
|
|
275
|
+
* activity yet) reads as an em dash rather than a bogus duration.
|
|
276
|
+
*
|
|
277
|
+
* Self-contained (no outer refs), `.toString()`-splice safe.
|
|
278
|
+
*/
|
|
279
|
+
export function relativeWhen(at, nowMs) {
|
|
280
|
+
if (at === null || at === undefined) return "—";
|
|
281
|
+
const seconds = Math.max(0, Math.round((nowMs - at) / 1000));
|
|
282
|
+
if (seconds < 5) return "now";
|
|
283
|
+
if (seconds < 60) return seconds + "s";
|
|
284
|
+
if (seconds < 3600) return Math.round(seconds / 60) + "m";
|
|
285
|
+
if (seconds < 86400) return Math.round(seconds / 3600) + "h";
|
|
286
|
+
return Math.round(seconds / 86400) + "d";
|
|
287
|
+
}
|
|
288
|
+
|
|
259
289
|
/**
|
|
260
290
|
* The invite link: this page's own address carrying the offer blob, the world
|
|
261
291
|
* id and the world's name. Any query or fragment the current address already
|
|
@@ -379,7 +409,7 @@ export function transcriptMarkdown(turns, meta, headerMd, turnMd) {
|
|
|
379
409
|
* live digest-bank twin (see chat-browser-entry.mjs) rather than to a
|
|
380
410
|
* client-side digest panel of this page's own; an empty list degrades to the
|
|
381
411
|
* flat list exactly as before this page could digest at all. */
|
|
382
|
-
export function renderChatHtml({ title = DEFAULT_TITLE, digestStructures = [] } = {}) {
|
|
412
|
+
export function renderChatHtml({ title = DEFAULT_TITLE, digestStructures = [], seedStamp = "" } = {}) {
|
|
383
413
|
const digestStructuresJson = JSON.stringify(Array.isArray(digestStructures) ? digestStructures : []);
|
|
384
414
|
const legendHtml = PROV_LEGEND.map(
|
|
385
415
|
([key, label]) => `<span class="legend-item"><i class="dot dot-${provKey(key)}"></i>${escapeHtml(label)}</span>`,
|
|
@@ -549,6 +579,13 @@ ${THEME_TOKENS_CSS}
|
|
|
549
579
|
input and a round send button. The chrome's controls take the same shape,
|
|
550
580
|
so the page's networking reads as ordinary chat furniture rather than as
|
|
551
581
|
an instrument bolted on. */
|
|
582
|
+
/* the live fact count, in the topbar rather than the statusline: it is the
|
|
583
|
+
one number that says what this session actually knows, and a small line of
|
|
584
|
+
grey mono under the composer is where a wrong one goes unnoticed. Reads as
|
|
585
|
+
a pill like its neighbours, with the number itself at reading size. */
|
|
586
|
+
.fact-pill { display: inline-flex; align-items: baseline; gap: .34rem; font-family: ${MONO_STACK}; font-size: .68rem; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); border: 1px solid var(--corpus-t1); border-radius: 99px; padding: .2rem .8rem; background: var(--corpus-soft); white-space: nowrap; }
|
|
587
|
+
.fact-pill .fact-pill-value { font-size: .96rem; letter-spacing: 0; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink); }
|
|
588
|
+
|
|
552
589
|
.chrome { display: flex; align-items: center; gap: .4rem; }
|
|
553
590
|
.chrome-btn { font-family: ${SERIF_STACK}; font-size: .8rem; color: var(--muted); border: 1px solid var(--line); border-radius: 99px; padding: .22rem .75rem; background: var(--card); text-decoration: none; display: inline-flex; align-items: center; gap: .32rem; white-space: nowrap; line-height: 1.35; }
|
|
554
591
|
.chrome-btn:hover { color: var(--ink); border-color: var(--ink); }
|
|
@@ -789,6 +826,10 @@ ${THEME_TOKENS_CSS}
|
|
|
789
826
|
<span class="eyebrow">the-mechanical-code-talker</span>
|
|
790
827
|
</div>
|
|
791
828
|
<div class="chrome">
|
|
829
|
+
<span class="fact-pill" id="factPill" aria-live="polite"
|
|
830
|
+
title="every fact this session's memory holds right now — the starter memory it shipped with plus anything you have taught, researched or ingested">
|
|
831
|
+
<span class="fact-pill-value" id="factPillValue">—</span> facts
|
|
832
|
+
</span>
|
|
792
833
|
<button type="button" class="state-pill" id="statePill" data-tone="idle"
|
|
793
834
|
title="the shared-world connection; click to open the network panel">
|
|
794
835
|
<i class="pill-dot"></i><span id="statePillWord">not shared</span>
|
|
@@ -880,6 +921,7 @@ ${THEME_TOKENS_CSS}
|
|
|
880
921
|
const transcriptMarkdown = ${transcriptMarkdown.toString()};
|
|
881
922
|
const bandLabelFor = ${bandLabelFor.toString()};
|
|
882
923
|
const statsSummaryLine = ${statsSummaryLine.toString()};
|
|
924
|
+
const clearSiteAssetCaches = ${clearSiteAssetCaches.toString()};
|
|
883
925
|
const fetchWithProgress = ${fetchWithProgress.toString()};
|
|
884
926
|
const renderStatsPanelInto = ${renderStatsPanelInto.toString()};
|
|
885
927
|
const createTicker = ${createTicker.toString()};
|
|
@@ -890,6 +932,8 @@ ${THEME_TOKENS_CSS}
|
|
|
890
932
|
const nodeInitials = ${nodeInitials.toString()};
|
|
891
933
|
const inviteLinkFor = ${inviteLinkFor.toString()};
|
|
892
934
|
const inviteParamsFrom = ${inviteParamsFrom.toString()};
|
|
935
|
+
const tapeClock = ${tapeClock.toString()};
|
|
936
|
+
const relativeWhen = ${relativeWhen.toString()};
|
|
893
937
|
const DIGEST_STRUCTURES = ${digestStructuresJson};
|
|
894
938
|
const el = (id) => document.getElementById(id);
|
|
895
939
|
|
|
@@ -900,6 +944,7 @@ ${THEME_TOKENS_CSS}
|
|
|
900
944
|
const inputEl = el("composerInput");
|
|
901
945
|
const sendBtn = el("composerSend");
|
|
902
946
|
const statusEl = el("status");
|
|
947
|
+
const factPillValueEl = el("factPillValue");
|
|
903
948
|
const statsPanelEl = el("statsPanelStats");
|
|
904
949
|
const researchedPanelEl = el("researchedPanel");
|
|
905
950
|
const wikiModeFieldset = el("wikiMode");
|
|
@@ -1102,11 +1147,18 @@ ${THEME_TOKENS_CSS}
|
|
|
1102
1147
|
}
|
|
1103
1148
|
}
|
|
1104
1149
|
|
|
1150
|
+
// The build's own content hash for the seed. It rides in the URL this page
|
|
1151
|
+
// fetches the seed by, so the service worker's cache-first read can only
|
|
1152
|
+
// ever return the copy this page asked for. Empty in a build that had no
|
|
1153
|
+
// seed to hash (the desktop shell's own render).
|
|
1154
|
+
const SEED_STAMP = ${JSON.stringify(seedStamp)};
|
|
1155
|
+
const SEED_QUERY = SEED_STAMP ? "?b=" + SEED_STAMP : "";
|
|
1156
|
+
|
|
1105
1157
|
let seedPayload = null;
|
|
1106
1158
|
let seedFacts = 0;
|
|
1107
1159
|
async function fetchSeed() {
|
|
1108
1160
|
try {
|
|
1109
|
-
const blob = await fetchWithProgress("./chat-seed.json", (loaded, total) => noteProgress("seed", loaded, total));
|
|
1161
|
+
const blob = await fetchWithProgress("./chat-seed.json" + SEED_QUERY, (loaded, total) => noteProgress("seed", loaded, total));
|
|
1110
1162
|
seedPayload = JSON.parse(await blob.text());
|
|
1111
1163
|
seedFacts = (seedPayload.individuals || []).filter((i) => i.class === "Fact").length;
|
|
1112
1164
|
} catch (err) {
|
|
@@ -1156,8 +1208,9 @@ ${THEME_TOKENS_CSS}
|
|
|
1156
1208
|
// Best-effort IndexedDB (window.tmctChat.openPersistedStore): the whole
|
|
1157
1209
|
// Backend-B payload snapshots after each teach turn, debounced so a burst
|
|
1158
1210
|
// of teaching costs one multi-MB write, not one per fact. The stamp ties a
|
|
1159
|
-
// snapshot to this deploy (site version) AND this seed (fact count
|
|
1160
|
-
// changing discards the snapshot in favour of
|
|
1211
|
+
// snapshot to this deploy (site version) AND this seed (its fact count and
|
|
1212
|
+
// content hash) — any of them changing discards the snapshot in favour of
|
|
1213
|
+
// the fresh seed.
|
|
1161
1214
|
let persist = null;
|
|
1162
1215
|
let saveTimer = null;
|
|
1163
1216
|
let restoredCount = 0;
|
|
@@ -1217,6 +1270,7 @@ ${THEME_TOKENS_CSS}
|
|
|
1217
1270
|
try { stats = await window.tmctChat.memoryStats(window.tmctChatSession.memoryDir); }
|
|
1218
1271
|
catch { return; }
|
|
1219
1272
|
}
|
|
1273
|
+
factPillValueEl.textContent = Number(stats.total || 0).toLocaleString();
|
|
1220
1274
|
renderStatsPanelInto(statsPanelEl, stats, {
|
|
1221
1275
|
bandLabel: bandLabelFor,
|
|
1222
1276
|
onForget: persist ? forgetEverything : null,
|
|
@@ -1598,14 +1652,20 @@ ${THEME_TOKENS_CSS}
|
|
|
1598
1652
|
inputEl.focus();
|
|
1599
1653
|
});
|
|
1600
1654
|
|
|
1601
|
-
// "reset to seed" is the full re-initialisation: drop
|
|
1602
|
-
//
|
|
1603
|
-
//
|
|
1655
|
+
// "reset to seed" is the full re-initialisation: drop everything this device
|
|
1656
|
+
// holds and reload, so boot re-seeds from the page's shipped seed as if on a
|
|
1657
|
+
// first visit. Harder than "forget everything", which only swaps the live
|
|
1604
1658
|
// session — this trusts nothing in memory and re-fetches the seed asset.
|
|
1659
|
+
//
|
|
1660
|
+
// Both stores go, not just the payload: what you taught lives in IndexedDB,
|
|
1661
|
+
// and the seed asset itself lives in the service worker's Cache Storage. A
|
|
1662
|
+
// reset that cleared only the first would re-seed straight back out of a
|
|
1663
|
+
// cached copy of an older seed, which is exactly what it promises not to do.
|
|
1605
1664
|
el("reinitStore").addEventListener("click", async () => {
|
|
1606
1665
|
clearTimeout(saveTimer);
|
|
1607
1666
|
saveTimer = null;
|
|
1608
1667
|
if (persist) await persist.clear();
|
|
1668
|
+
await clearSiteAssetCaches();
|
|
1609
1669
|
window.location.reload();
|
|
1610
1670
|
});
|
|
1611
1671
|
|
|
@@ -1799,12 +1859,6 @@ ${THEME_TOKENS_CSS}
|
|
|
1799
1859
|
const tapeCounts = new Map();
|
|
1800
1860
|
let wireMessageCount = 0;
|
|
1801
1861
|
|
|
1802
|
-
function tapeClock() {
|
|
1803
|
-
const at = new Date();
|
|
1804
|
-
const pad = function (n, width) { return String(n).padStart(width, "0"); };
|
|
1805
|
-
return pad(at.getHours(), 2) + ":" + pad(at.getMinutes(), 2) + ":" + pad(at.getSeconds(), 2) + "." + pad(at.getMilliseconds(), 3);
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
1862
|
function pushTape(entry) {
|
|
1809
1863
|
// The meter counts real wire messages only; the tape below shows those
|
|
1810
1864
|
// plus the local notes (state changes, a channel opening) that explain
|
|
@@ -1885,16 +1939,6 @@ ${THEME_TOKENS_CSS}
|
|
|
1885
1939
|
answerPanelEl.hidden = !replyOutEl.value || (room && room.state === "connected");
|
|
1886
1940
|
}
|
|
1887
1941
|
|
|
1888
|
-
function relativeWhen(at, nowMs) {
|
|
1889
|
-
if (at === null || at === undefined) return "—";
|
|
1890
|
-
const seconds = Math.max(0, Math.round((nowMs - at) / 1000));
|
|
1891
|
-
if (seconds < 5) return "now";
|
|
1892
|
-
if (seconds < 60) return seconds + "s";
|
|
1893
|
-
if (seconds < 3600) return Math.round(seconds / 60) + "m";
|
|
1894
|
-
if (seconds < 86400) return Math.round(seconds / 3600) + "h";
|
|
1895
|
-
return Math.round(seconds / 86400) + "d";
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
1942
|
function renderNodes() {
|
|
1899
1943
|
if (!room || !p2p) {
|
|
1900
1944
|
nodeCountEl.textContent = "";
|
|
@@ -2186,7 +2230,7 @@ ${THEME_TOKENS_CSS}
|
|
|
2186
2230
|
progressActive = false;
|
|
2187
2231
|
window.tmctChat.registerReferencePackProvider(fetchPackProvider);
|
|
2188
2232
|
if (window.tmctChat.openPersistedStore) {
|
|
2189
|
-
persist = window.tmctChat.openPersistedStore({ storeKey: "chat", stamp: siteVersion + ":" + seedFacts });
|
|
2233
|
+
persist = window.tmctChat.openPersistedStore({ storeKey: "chat", stamp: siteVersion + ":" + seedFacts + ":" + SEED_STAMP });
|
|
2190
2234
|
}
|
|
2191
2235
|
const savedRecord = persist ? await persist.load() : null;
|
|
2192
2236
|
const initialMode = readWikiMode();
|
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 — `
|
|
@@ -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.
|
|
@@ -7758,6 +7810,13 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
|
|
|
7758
7810
|
// teach frame, in a store written before the spellings converged) is
|
|
7759
7811
|
// found by the curated spelling it means.
|
|
7760
7812
|
let hits = predicate ? subjectHits.filter((f) => normFactPredicate(f.predicate) === predicate) : subjectHits;
|
|
7813
|
+
// A bare "what is X" answered ONLY by corpus-weak ConceptNet association(s)
|
|
7814
|
+
// is the hedged "possibly: X is related to Y" guess, not a definition — treated
|
|
7815
|
+
// as no hit at all so the honest-miss cascade below (teach lane, reference-pack/
|
|
7816
|
+
// Wikipedia fallback) still gets a turn, instead of the hedge silently standing
|
|
7817
|
+
// in as "already answered". A predicate-specific ask ("X used for") is untouched:
|
|
7818
|
+
// scoped to the SAME undifferentiated shape the reference-pack fallback serves.
|
|
7819
|
+
if (!predicate && hits.length && !hits.some(isRealGrounding)) hits = [];
|
|
7761
7820
|
if (!hits.length) {
|
|
7762
7821
|
// The subject itself is known — as a fact subject, or as the standing
|
|
7763
7822
|
// focus a pronoun just resolved to — but not under this specific
|
|
@@ -7777,7 +7836,9 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
|
|
|
7777
7836
|
// ishmael"): surface those reverse relations rather than missing, the same
|
|
7778
7837
|
// facts "what do you know about X" would list.
|
|
7779
7838
|
if (!predicate) {
|
|
7780
|
-
|
|
7839
|
+
let objectHits = rankByBiasThenTrust((await factRows(memoryDir, cache)).filter((f) => variants.has(f.object)), biasByBundle);
|
|
7840
|
+
// Same corpus-weak-only demotion as the subject-side hits above.
|
|
7841
|
+
if (objectHits.length && !objectHits.some(isRealGrounding)) objectHits = [];
|
|
7781
7842
|
if (objectHits.length) {
|
|
7782
7843
|
const objLines = objectHits.map(renderFactLine);
|
|
7783
7844
|
const objShown = objLines.slice(0, FACT_ANSWER_CAP);
|
|
@@ -8343,6 +8404,16 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
|
|
|
8343
8404
|
// besides. The adventure's own where/openness readers answer the legitimate
|
|
8344
8405
|
// in-game questions from the world fold.
|
|
8345
8406
|
hits = hits.filter((f) => !WORLD_INTERNAL_PREDICATES.has(f.predicate));
|
|
8407
|
+
// A corpus-weak-only result set (every hit resolved ONLY through
|
|
8408
|
+
// ConceptNet's /r/RelatedTo tier) still composes its hedged "possibly"
|
|
8409
|
+
// lines below exactly as before — this reader's whole job is showing
|
|
8410
|
+
// those, weak or not, so they're never suppressed from the text. But the
|
|
8411
|
+
// result is flagged `weakOnly` so the shared honest-miss cascade (the
|
|
8412
|
+
// reference-pack/Wikipedia fallback, below in runAsk) still gets a turn
|
|
8413
|
+
// instead of the hedge silently standing in as "already answered" —
|
|
8414
|
+
// mirroring isRealGrounding's other two call sites without breaking this
|
|
8415
|
+
// one's own job.
|
|
8416
|
+
const weakOnly = hits.length > 0 && !hits.some(isRealGrounding);
|
|
8346
8417
|
// A genuinely empty result here is a real miss: "what do you know about
|
|
8347
8418
|
// the last commit" needs a TEACH-OFFER, not a bare wall — added as a LATE
|
|
8348
8419
|
// runTurn-level addition, below, alongside the sibling "what is X" offer,
|
|
@@ -8393,11 +8464,16 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
|
|
|
8393
8464
|
const header = `${hits.length} remembered fact${hits.length === 1 ? "" : "s"} about ${term}`
|
|
8394
8465
|
+ `${viaSubtype ? " (including its known subtypes)" : ""}:`;
|
|
8395
8466
|
const { lines, grouped } = senseSplitFactList(hits, rows, variants, { indent: " " });
|
|
8396
|
-
if (grouped) return { ...grouped, text: `${header}\n${grouped.text}
|
|
8467
|
+
if (grouped) return { ...grouped, text: `${header}\n${grouped.text}`, ...(weakOnly ? { weakOnly: true } : {}) };
|
|
8397
8468
|
const shown = lines.slice(0, FACT_ANSWER_CAP);
|
|
8398
8469
|
const rest = lines.slice(FACT_ANSWER_CAP);
|
|
8399
8470
|
const extra = rest.length ? `\n …and ${rest.length} more — say 'more' to see them.` : "";
|
|
8400
|
-
return {
|
|
8471
|
+
return {
|
|
8472
|
+
text: `${header}\n${shown.join("\n")}${extra}`,
|
|
8473
|
+
replace: true,
|
|
8474
|
+
...(rest.length ? { pending: { items: rest.map((l) => l.trim()), noun: "facts" } } : {}),
|
|
8475
|
+
...(weakOnly ? { weakOnly: true } : {}),
|
|
8476
|
+
};
|
|
8401
8477
|
}
|
|
8402
8478
|
return null;
|
|
8403
8479
|
}
|
|
@@ -10674,14 +10750,37 @@ async function curatedDefinitionAnswer(query, envelope, { memoryDir, lexicon })
|
|
|
10674
10750
|
return { text: `${def} (source: corpus/seon)`, term };
|
|
10675
10751
|
}
|
|
10676
10752
|
|
|
10753
|
+
/** True when `f` carries a source better than the ConceptNet `/r/RelatedTo`
|
|
10754
|
+
* corpus-weak tier — or no recorded source type at all (the conservative,
|
|
10755
|
+
* pre-existing read for legacy rows). A fact resolved ONLY by corpus-weak
|
|
10756
|
+
* source(s) is the hedged "possibly: X is related to Y" guess, not an
|
|
10757
|
+
* actual definition; callers treat it as no grounding at all rather than an
|
|
10758
|
+
* answer, so it can never by itself skip the honest-miss cascade below
|
|
10759
|
+
* (the teach lane, the reference-pack/Wikipedia fallback). Shared by the
|
|
10760
|
+
* bare "what is X" reader, the "what do you know about X" reader (both in
|
|
10761
|
+
* factAnswerReaders) and the learn-on-miss gate (cleanMissPackKey/
|
|
10762
|
+
* cleanMissLiveKey) so none of them can ever disagree. */
|
|
10763
|
+
function isRealGrounding(f) {
|
|
10764
|
+
const types = f.sourceTypes || [];
|
|
10765
|
+
return types.length === 0 || types.some((t) => t !== "corpusWeak");
|
|
10766
|
+
}
|
|
10767
|
+
|
|
10677
10768
|
// ---- learn-on-miss: the shipped child + reference packs behind the cleanest miss ----
|
|
10678
10769
|
|
|
10770
|
+
/** A remembered fact naming `term` is real grounding only when SOME source
|
|
10771
|
+
* behind it outranks the ConceptNet `/r/RelatedTo` corpus-weak tier — see
|
|
10772
|
+
* isRealGrounding just above. */
|
|
10773
|
+
function hasNonWeakGrounding(rows, variants) {
|
|
10774
|
+
return rows.some((f) => (variants.has(f.subject) || variants.has(f.object)) && isRealGrounding(f));
|
|
10775
|
+
}
|
|
10776
|
+
|
|
10679
10777
|
/** The learn-on-miss gate, shared by the child-pack hook, the articled
|
|
10680
10778
|
* reference hook and the bare-form fallback so the three can never disagree.
|
|
10681
10779
|
* Passes only on the CLEANEST miss: a definition-shaped term the lexicon
|
|
10682
|
-
* knows, resolving to no graph entity and no
|
|
10683
|
-
* only then, may a pack provider be
|
|
10684
|
-
* byte-identically to a pack-less
|
|
10780
|
+
* knows, resolving to no graph entity and no fact grounded above the
|
|
10781
|
+
* corpus-weak tier — then, and only then, may a pack provider be
|
|
10782
|
+
* consulted. Null means the turn proceeds byte-identically to a pack-less
|
|
10783
|
+
* run. */
|
|
10685
10784
|
async function cleanMissPackKey(term, { graph, memoryDir, lexicon, cache }) {
|
|
10686
10785
|
if (!term || !memoryDir) return null;
|
|
10687
10786
|
let key = null;
|
|
@@ -10695,7 +10794,7 @@ async function cleanMissPackKey(term, { graph, memoryDir, lexicon, cache }) {
|
|
|
10695
10794
|
const variants = factTermVariants(normFactTerm, term);
|
|
10696
10795
|
variants.add(key);
|
|
10697
10796
|
const rows = await factRows(memoryDir, cache);
|
|
10698
|
-
if (rows
|
|
10797
|
+
if (hasNonWeakGrounding(rows, variants)) return null;
|
|
10699
10798
|
// A taught RULE that owns this term outranks any pack load: surfacing
|
|
10700
10799
|
// unrelated conceptnet content over the user's own taught concept is worse
|
|
10701
10800
|
// than the honest miss the decline leaves standing.
|
|
@@ -10723,10 +10822,10 @@ async function referencePackMissAnswer(term, { graph, memoryDir, lexicon, env, c
|
|
|
10723
10822
|
}
|
|
10724
10823
|
|
|
10725
10824
|
/** The LIVE variant of cleanMissPackKey — the same resolveEntity and
|
|
10726
|
-
*
|
|
10727
|
-
* lexicon-membership wall: a word the lexicon has never met is
|
|
10728
|
-
* the live lookup exists for. Null means the turn proceeds
|
|
10729
|
-
* to a live-off run. */
|
|
10825
|
+
* above-corpus-weak grounding checks, but through cleanMissLiveTerm, which
|
|
10826
|
+
* drops the lexicon-membership wall: a word the lexicon has never met is
|
|
10827
|
+
* exactly what the live lookup exists for. Null means the turn proceeds
|
|
10828
|
+
* byte-identically to a live-off run. */
|
|
10730
10829
|
async function cleanMissLiveKey(term, { graph, memoryDir, lexicon, cache }) {
|
|
10731
10830
|
if (!term || !memoryDir) return null;
|
|
10732
10831
|
let key = null;
|
|
@@ -10738,7 +10837,7 @@ async function cleanMissLiveKey(term, { graph, memoryDir, lexicon, cache }) {
|
|
|
10738
10837
|
const variants = factTermVariants(normFactTerm, term);
|
|
10739
10838
|
variants.add(key);
|
|
10740
10839
|
const rows = await factRows(memoryDir, cache);
|
|
10741
|
-
if (rows
|
|
10840
|
+
if (hasNonWeakGrounding(rows, variants)) return null;
|
|
10742
10841
|
return key;
|
|
10743
10842
|
}
|
|
10744
10843
|
|
|
@@ -12479,11 +12578,16 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
12479
12578
|
let envelope = null;
|
|
12480
12579
|
try {
|
|
12481
12580
|
let text;
|
|
12482
|
-
if (graph && (focus?.id || prev.length)) {
|
|
12483
|
-
// Direct ask()
|
|
12484
|
-
//
|
|
12485
|
-
//
|
|
12486
|
-
//
|
|
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.
|
|
12487
12591
|
const { ask } = await import("../domain/ask.mjs");
|
|
12488
12592
|
const r = ask(graph, askQuery, { contextId: effectiveContextId, prev });
|
|
12489
12593
|
text = `${r.content}${ASK_ENVELOPE_DELIM}${JSON.stringify(r.tmct_ask, null, 2)}`;
|
|
@@ -12641,6 +12745,13 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
12641
12745
|
let via = "composed";
|
|
12642
12746
|
let recordMiss = miss;
|
|
12643
12747
|
let factPending = null; // a truncated fact listing's held remainder (for "more" paging)
|
|
12748
|
+
// Set when the memory-facts lane (W4, below) answered from a corpus-weak-only
|
|
12749
|
+
// hit set (factAnswer's KNOW_ABOUT_RE reader, `weakOnly`) — the hedged
|
|
12750
|
+
// "possibly" answer still stands as `answer`, but `via` is deliberately left
|
|
12751
|
+
// at "composed" so the honest-miss cascade further down still gets a turn,
|
|
12752
|
+
// and this flag lets the LEARN-ON-MISS PACKS lane resolve a reference-pack
|
|
12753
|
+
// term for THIS query shape (which metaTermOf alone never recognizes).
|
|
12754
|
+
let factLaneWeakOnly = false;
|
|
12644
12755
|
// scm-svf1/cardinality-monotonicity/
|
|
12645
12756
|
// cax-maxc0's LIVE proof chases (factReadBack) have no persisted Fact to
|
|
12646
12757
|
// attach trust.mjs's entailed hook to, so they compute
|
|
@@ -13156,9 +13267,18 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
13156
13267
|
// the empty-graph orientation pointer below stays off it — a pronoun
|
|
13157
13268
|
// decline with an index pointer under it is two answers to one turn.
|
|
13158
13269
|
if (fact.selfContainedMiss) selfContainedMiss = true;
|
|
13159
|
-
|
|
13270
|
+
// A `weakOnly` hit (the "what do you know about X" reader's
|
|
13271
|
+
// corpus-weak-only hedge) is shown as `answer` above like any other
|
|
13272
|
+
// fact-lane hit, but doesn't count as REAL grounding — via/recordMiss
|
|
13273
|
+
// stay untouched so the honest-miss cascade below (the reference-pack
|
|
13274
|
+
// lane in particular) still gets a turn at replacing the hedge with a
|
|
13275
|
+
// real answer, exactly the discipline isRealGrounding's other two call
|
|
13276
|
+
// sites already apply.
|
|
13277
|
+
if (!fact.miss && !fact.weakOnly) {
|
|
13160
13278
|
via = "fact";
|
|
13161
13279
|
recordMiss = false;
|
|
13280
|
+
} else if (fact.weakOnly) {
|
|
13281
|
+
factLaneWeakOnly = true;
|
|
13162
13282
|
}
|
|
13163
13283
|
if (fact.pending) factPending = fact.pending; // a truncated fact list → paginable remainder
|
|
13164
13284
|
if (typeof fact.trust === "number") entailedTrust = fact.trust; // live-chase trust (see the `entailedTrust` declaration above)
|
|
@@ -13465,13 +13585,19 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
13465
13585
|
// pack's article speak, cited as before. Both packs missing leaves the
|
|
13466
13586
|
// honest miss byte-identical.
|
|
13467
13587
|
if (miss && recordMiss && via === "composed" && memoryDir) {
|
|
13468
|
-
|
|
13588
|
+
// metaTermOf only recognizes the "what is X"/"define X" shapes — a
|
|
13589
|
+
// "what do you know about X" query only reaches this lane at all when
|
|
13590
|
+
// its own reader (above) answered with a corpus-weak-only hedge
|
|
13591
|
+
// (factLaneWeakOnly), so the KNOW_ABOUT_RE term is resolved directly the
|
|
13592
|
+
// same way the TEACH-OFFER lane below already does for that shape.
|
|
13593
|
+
const refTerm = metaTermOf(query, envelope)
|
|
13594
|
+
|| (factLaneWeakOnly ? expandContractions(String(query).trim()).match(KNOW_ABOUT_RE)?.[1]?.trim() : null);
|
|
13469
13595
|
const key = refTerm ? await cleanMissPackKey(refTerm, { graph, memoryDir, lexicon, cache }) : null;
|
|
13470
13596
|
const learned = key ? await childPackFactsForKey(key, { memoryDir, env, cache, synthesisBudget }) : null;
|
|
13471
13597
|
if (learned) {
|
|
13472
13598
|
const fact = (await factAnswer(memoryDir, query, envelope, miss, biasByBundle, cache, newFocus?.label))
|
|
13473
13599
|
?? (await factReadBack(memoryDir, query, envelope, miss, graph, newFocus?.label, biasByBundle, cache));
|
|
13474
|
-
if (fact && !fact.miss) {
|
|
13600
|
+
if (fact && !fact.miss && !fact.weakOnly) {
|
|
13475
13601
|
answer = fact.replace ? fact.text : `${answer}\n${fact.text}`;
|
|
13476
13602
|
via = "fact";
|
|
13477
13603
|
recordMiss = false;
|
|
@@ -13558,7 +13684,12 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
13558
13684
|
const ent = knowAboutTerm ? null : await resolveEntity(graph, offerTerm);
|
|
13559
13685
|
if (!ent) {
|
|
13560
13686
|
const variants = factTermVariants(normFactTerm, offerTerm);
|
|
13561
|
-
|
|
13687
|
+
// A corpus-weak-only match (ConceptNet's /r/RelatedTo hedge) is not
|
|
13688
|
+
// real grounding — same isRealGrounding discipline as the bare
|
|
13689
|
+
// "what is X" reader and the learn-on-miss gate, so a term with
|
|
13690
|
+
// nothing but a loose association still gets the "teach me" offer
|
|
13691
|
+
// rather than being silently counted as already known.
|
|
13692
|
+
const known = hasNonWeakGrounding(await factRows(memoryDir, cache), variants);
|
|
13562
13693
|
if (!known) teachOffer = unknownVocabTermOffer(cleanTerm);
|
|
13563
13694
|
}
|
|
13564
13695
|
}
|
|
@@ -14010,9 +14141,20 @@ async function runCommand(line, { config, source, graph, focus, memoryDir, trace
|
|
|
14010
14141
|
if (name === "plan") {
|
|
14011
14142
|
note(trace, "goal: plan/execute a compound or maintenance-goal request over the graph (the capability router)");
|
|
14012
14143
|
if (!argText) return mk("/plan needs a request, e.g. `/plan of the modules impacted by X, which are untested`.", { miss: true });
|
|
14013
|
-
|
|
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 });
|
|
14014
14154
|
const { buildCapabilityPlanCtx, runCapabilityPlan, declaredCapabilityNames } = await import("../domain/router/drive.mjs");
|
|
14015
|
-
const planCtx = await buildCapabilityPlanCtx({
|
|
14155
|
+
const planCtx = await buildCapabilityPlanCtx({
|
|
14156
|
+
...capabilityPlanDeps(), config, source: planGraph ? source : null, tel, graph: planGraph, memoryDir,
|
|
14157
|
+
});
|
|
14016
14158
|
try {
|
|
14017
14159
|
const result = await runCapabilityPlan(argText, declaredCapabilityNames(), planCtx);
|
|
14018
14160
|
if (result.refused) {
|
|
@@ -14698,11 +14840,18 @@ const LIST_KNOWN_KINDS_RE = /^list\s+(?:the\s+|all\s+(?:the\s+)?)?(?!facts?\b|th
|
|
|
14698
14840
|
// conditional over a NAMED subject or an arbitrary property is a rule, not a
|
|
14699
14841
|
// subclass fact, and stays outside this frame.
|
|
14700
14842
|
const UNIVERSAL_CONDITIONAL_RE = /^if\s+(?:something|somebody|someone|anything)\s+is\s+an?\s+([\w-]+)\s*,?\s*(?:then\s+)?(?:it|they)\s+(?:is|are)\s+an?\s+([\w-]+)[.!?\s]*$/i;
|
|
14701
|
-
function rewriteVocabOpener(line) {
|
|
14843
|
+
function rewriteVocabOpener(line, { adventureLive = false } = {}) {
|
|
14702
14844
|
let m = line.match(DESIRE_ABOUT_RE) || line.match(TELL_ABOUT_VARIANT_RE);
|
|
14703
14845
|
if (m) return `tell me about ${m[1].trim()}`;
|
|
14704
14846
|
m = line.match(UNIVERSAL_CONDITIONAL_RE);
|
|
14705
14847
|
if (m) return `every ${m[1]} is ${indefiniteArticleFor(m[2])} ${m[2]}`;
|
|
14848
|
+
// "what <noun> do you know [about]" shares its surface form with a live
|
|
14849
|
+
// adventure's own personal-knowledge asides ("what food do you know
|
|
14850
|
+
// about") — those ask what the CHARACTER has learned in-world, not for
|
|
14851
|
+
// the taught CLASS's members, so this closed-set enumeration rewrite
|
|
14852
|
+
// stands down while a world is live and leaves the line for the
|
|
14853
|
+
// adventure lane to recognise (or not) on its own terms.
|
|
14854
|
+
if (adventureLive) return null;
|
|
14706
14855
|
m = line.match(KNOWN_KINDS_RE) || line.match(LIST_KNOWN_KINDS_RE);
|
|
14707
14856
|
if (m) {
|
|
14708
14857
|
const noun = teachableSubjectOf(m[1]);
|
|
@@ -14977,7 +15126,7 @@ async function dispatchTurn(input, { config, source = defaultSource, graph = nul
|
|
|
14977
15126
|
const indirectMatch = line.match(INDIRECT_REQUEST_RE);
|
|
14978
15127
|
const indirectLine = indirectMatch ? indirectMatch[1].trim() : line;
|
|
14979
15128
|
const preRewriteLine = rewriteEntryPointQuestion(indirectLine) || rewriteProveThat(indirectLine)
|
|
14980
|
-
|| rewriteVocabOpener(indirectLine) || indirectLine;
|
|
15129
|
+
|| rewriteVocabOpener(indirectLine, { adventureLive: Boolean(planState?.adventure) }) || indirectLine;
|
|
14981
15130
|
// rewriteUsesAsBaseFrame's discontiguous-frame rewrite: applied here, once,
|
|
14982
15131
|
// before ANY dispatch lane sees the text. Null (no-op) for every turn that
|
|
14983
15132
|
// doesn't match one of the four discontiguous shapes.
|