@polycode-projects/the-mechanical-code-talker 2.10.5 → 2.11.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 -2
- package/corpus/sprites/src/sprite-facts.jsonl +18 -0
- package/corpus/worlds/manifest.json +5 -5
- package/corpus/worlds/shards/ashcombe-hall.jsonl.gz +0 -0
- package/corpus/worlds/src/ashcombe-hall.jsonl +27 -0
- package/data/sprites/book-icon.toml +12 -0
- package/data/sprites/cellar-icon.toml +12 -0
- package/data/sprites/drawing-room-icon.toml +13 -0
- package/data/sprites/garden-icon.toml +12 -0
- package/data/sprites/kitchen-icon.toml +13 -0
- package/data/sprites/library-icon.toml +12 -0
- package/data/sprites/pan-icon.toml +11 -0
- package/data/sprites/study-icon.toml +12 -0
- package/data/templates/responses.jsonl +1 -0
- package/package.json +5 -2
- package/src/adapters/corpus/wikipedia-live.mjs +182 -26
- package/src/adapters/corpus/worlds-pack.mjs +8 -2
- package/src/adapters/toml-config.mjs +6 -0
- package/src/domain/ask-vocab.mjs +17 -0
- package/src/domain/ask.mjs +51 -1
- package/src/domain/grammar/ace.mjs +43 -3
- package/src/domain/interpret/normalize.mjs +6 -2
- package/src/domain/interpret/strategies/grammar.mjs +47 -17
- package/src/domain/interpret/strategies/keywords.mjs +53 -4
- package/src/domain/memory/trust.mjs +11 -0
- package/src/domain/router/registry.mjs +8 -1
- package/src/domain/worlds-pack.mjs +50 -0
- package/src/services/adventure-autoplay.mjs +5 -2
- package/src/services/adventure-viz.mjs +301 -33
- package/src/services/adventure.mjs +162 -14
- package/src/services/chat-page-viz.mjs +265 -189
- package/src/services/chat-session.mjs +15 -5
- package/src/services/chat.mjs +471 -79
- package/src/services/code-explorer-viz.mjs +183 -75
- package/src/services/extract-facts.mjs +118 -28
- package/src/services/ingest-viz.mjs +328 -79
- package/src/services/ledger-viz.mjs +99 -0
- package/src/services/memory-panel-viz.mjs +159 -0
- package/src/services/research.mjs +266 -0
- package/src/services/sentences.mjs +19 -0
- package/src/services/spider-fly-viz.mjs +21 -5
- package/src/surfaces/web/adventure-browser-entry.mjs +9 -5
- package/src/surfaces/web/chat-browser-entry.mjs +28 -11
- package/src/surfaces/web/code-explorer-browser-entry.mjs +27 -11
- package/src/surfaces/web/ingest-browser-entry.mjs +123 -41
- package/src/surfaces/web/ledger-browser-entry.mjs +10 -4
- package/src/surfaces/web/memory-ask-browser.bundle.js +116 -116
- package/src/surfaces/web/memory-stats.mjs +53 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import {
|
|
9
9
|
VERB_TO_KIND, ENTITY_TO_TYPE, MODIFIER_TO_KIND,
|
|
10
10
|
WHERE_MARKERS, MENTION_MARKERS, PLACEHOLDER_NOUNS, PASSIVE_PARTICIPLE_TO_KIND,
|
|
11
|
-
INHERITS_REVERSE_VERBS,
|
|
11
|
+
INHERITS_REVERSE_VERBS, HAS_FAMILY_VERBS,
|
|
12
12
|
} from "../../ask-vocab.mjs";
|
|
13
13
|
import { STOPWORDS } from "../normalize.mjs";
|
|
14
14
|
import { VOCAB_WORDS, eligibleForCanon, fuzzyVocabWord } from "../fuzzy.mjs";
|
|
@@ -19,6 +19,10 @@ import { VOCAB_WORDS, eligibleForCanon, fuzzyVocabWord } from "../fuzzy.mjs";
|
|
|
19
19
|
const PASSIVE_AUX = new Set(["is", "are", "was", "were", "be", "been", "being", "get", "gets", "got"]);
|
|
20
20
|
const WH_WORDS = new Set(["which", "what", "who", "whom", "whose"]);
|
|
21
21
|
const PLACEHOLDER_SET = new Set(PLACEHOLDER_NOUNS.map((w) => w.toLowerCase()));
|
|
22
|
+
// See ask-vocab.mjs's own HAS_FAMILY_VERBS for why a bare have-family verb
|
|
23
|
+
// never resolves to `defines` in this strategy's two-named-role "ask" shape
|
|
24
|
+
// below (the forward/reverse branches keep their own tested grain-check
|
|
25
|
+
// decline, per that constant's own docblock).
|
|
22
26
|
|
|
23
27
|
/** Find the longest phrase from `table`'s keys that appears as a contiguous
|
|
24
28
|
* run of `words` (case already lowercased by the caller). Longest-match-first
|
|
@@ -107,6 +111,13 @@ export function parseKeywordSpot(text, nlp = null) {
|
|
|
107
111
|
fuzzyVerb = { from: lcWords[at], to: fuzzyWords[at] };
|
|
108
112
|
}
|
|
109
113
|
}
|
|
114
|
+
// Tracks whether verbHit came from PASSIVE_PARTICIPLE_TO_KIND's fallback
|
|
115
|
+
// rather than an active VERB_TO_KIND entry — that table's own header says
|
|
116
|
+
// it's only meant to fire once a passive auxiliary AND an agent-marking
|
|
117
|
+
// "by" are confirmed. A direct complement with no "by" ("my cat is called
|
|
118
|
+
// whiskers") is the naming sense of "call", not the invoke-relation passive,
|
|
119
|
+
// so this flag gates the ask-shape SVO fallback below from misreading it.
|
|
120
|
+
let verbFromParticiple = false;
|
|
110
121
|
if (!verbHit) {
|
|
111
122
|
// A participle with no active verb entry still marks a passive when a passive
|
|
112
123
|
// auxiliary precedes it — with or without an agent "by" phrase. "is http.mjs
|
|
@@ -125,10 +136,28 @@ export function parseKeywordSpot(text, nlp = null) {
|
|
|
125
136
|
for (let i = 0; i < lcWords.length; i += 1) {
|
|
126
137
|
const k = PASSIVE_PARTICIPLE_TO_KIND[lcWords[i]];
|
|
127
138
|
if (k && lcWords[i] === "used" && lcWords[i + 1] === "for") continue;
|
|
128
|
-
if (k && lcWords.slice(0, i).some((w) => PASSIVE_AUX.has(w))) {
|
|
139
|
+
if (k && lcWords.slice(0, i).some((w) => PASSIVE_AUX.has(w))) {
|
|
140
|
+
verbHit = { kind: k, start: i, end: i + 1 };
|
|
141
|
+
verbFromParticiple = true;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
129
144
|
}
|
|
130
145
|
}
|
|
131
146
|
if (!verbHit) return null;
|
|
147
|
+
// A single-word verbHit whose LITERAL surface text (not the lemma the tier-2
|
|
148
|
+
// pass may have rewritten it to) is itself a PASSIVE_PARTICIPLE_TO_KIND entry,
|
|
149
|
+
// preceded by a passive auxiliary, is the same "needs a confirmed 'by' agent"
|
|
150
|
+
// case the fallback loop above already flags — checked here too because the
|
|
151
|
+
// lemma tier reaches it independently: lemma("called") is "call", an ACTIVE
|
|
152
|
+
// verb-table entry in its own right, so tier 2 resolves verbHit before the
|
|
153
|
+
// fallback loop ever runs, silently losing the participle reading. "my cat is
|
|
154
|
+
// called whiskers" (the naming sense of "call") must not be read as though
|
|
155
|
+
// "cat" were the active subject invoking "whiskers".
|
|
156
|
+
if (!verbFromParticiple && verbHit.end - verbHit.start === 1
|
|
157
|
+
&& PASSIVE_PARTICIPLE_TO_KIND[lcWords[verbHit.start]]
|
|
158
|
+
&& lcWords.slice(0, verbHit.start).some((w) => PASSIVE_AUX.has(w))) {
|
|
159
|
+
verbFromParticiple = true;
|
|
160
|
+
}
|
|
132
161
|
// A tier-3 verb is a REPAIR, not a reading — downstream consumers (the teach
|
|
133
162
|
// lane's canonical receipt, and the chat surface's fuzzy-verb decline) need
|
|
134
163
|
// to know the difference AND which word was rewritten, so {from, to} rides
|
|
@@ -221,15 +250,35 @@ export function parseKeywordSpot(text, nlp = null) {
|
|
|
221
250
|
const [patient, agent] = agentIsFronted
|
|
222
251
|
? [roleText(passiveAuxIdx + 1, words.length), roleText(byIdx + 1, passiveAuxIdx)]
|
|
223
252
|
: [roleText(0, byIdx), roleText(byIdx + 1, words.length)];
|
|
224
|
-
if (patient && agent)
|
|
253
|
+
if (patient && agent) {
|
|
254
|
+
if (kind === "defines" && HAS_FAMILY_VERBS.has(canonWords.slice(verbHit.start, verbHit.end).join(" "))) return null;
|
|
255
|
+
return stamp({ shape: "ask", entityType: null, modifier: "direct", kind, subject: agent, object: patient });
|
|
256
|
+
}
|
|
225
257
|
if (agent) return stamp({ shape: "forward", entityType, modifier, kind, object: agent });
|
|
226
258
|
if (patient) return stamp({ shape: "reverse", entityType, modifier, kind, object: patient });
|
|
227
259
|
}
|
|
228
260
|
|
|
261
|
+
// "called" specifically (never the rest of the participle family — "was it
|
|
262
|
+
// touched recently"/"is X used anywhere" are genuine bare-passive code
|
|
263
|
+
// queries with a trailing adverb, not a competing sense, and must keep
|
|
264
|
+
// reaching their existing reading) carries a whole separate NAMING sense
|
|
265
|
+
// ("my cat is called whiskers") distinct from the invoke-relation passive
|
|
266
|
+
// this table exists for. A following complement with no "by" agent is that
|
|
267
|
+
// naming sense, not "whiskers calls cat" — falling through to the
|
|
268
|
+
// active-SVO/reverse branches below would read the participle as if it
|
|
269
|
+
// were an active verb and answer a code-graph question nobody asked, so
|
|
270
|
+
// this misses honestly instead.
|
|
271
|
+
if (verbFromParticiple && byIdx < 0 && afterText && lcWords[verbHit.start] === "called") return null;
|
|
272
|
+
|
|
229
273
|
if (beforeText && afterText) {
|
|
274
|
+
const verbPhrase = canonWords.slice(verbHit.start, verbHit.end).join(" ");
|
|
275
|
+
// The bare have-family "ask" shape ("does X have Y") declines here, same
|
|
276
|
+
// reasoning as grammar.mjs's T1 (see HAS_FAMILY_VERBS above) — never for
|
|
277
|
+
// the entityType-driven forward/reverse branches below, which keep their
|
|
278
|
+
// own tested grain-check decline.
|
|
279
|
+
if (kind === "defines" && HAS_FAMILY_VERBS.has(verbPhrase)) return null;
|
|
230
280
|
// A semantically-reverse verb ("superclass of") swaps subject/object, same as
|
|
231
281
|
// grammar.mjs's T1.
|
|
232
|
-
const verbPhrase = canonWords.slice(verbHit.start, verbHit.end).join(" ");
|
|
233
282
|
let subject = beforeText;
|
|
234
283
|
let object = afterText;
|
|
235
284
|
if (INHERITS_REVERSE_VERBS.includes(verbPhrase)) [subject, object] = [object, subject];
|
|
@@ -77,6 +77,17 @@ export function provenanceTagToSource(tag) {
|
|
|
77
77
|
const pack = rest.slice(0, colon) || "unknown";
|
|
78
78
|
return { kind: referenceKindFor(pack), pack, article: rest.slice(colon + 1) };
|
|
79
79
|
}
|
|
80
|
+
// research:<topic>@<depth> — the research lane's Simple English Wikipedia
|
|
81
|
+
// loads. Live-fetched at query time like the wikipedia-live pack, so it
|
|
82
|
+
// scores at the same referenceLive prior, below every curated pack. Parsed
|
|
83
|
+
// from the FULL tag (a topic may contain spaces); the depth segment records
|
|
84
|
+
// how far the fan-out reached and is not part of the Source identity.
|
|
85
|
+
if (t.startsWith("research:")) {
|
|
86
|
+
const rest = t.slice("research:".length);
|
|
87
|
+
const at = rest.lastIndexOf("@");
|
|
88
|
+
const topic = (at >= 0 ? rest.slice(0, at) : rest).trim();
|
|
89
|
+
return { kind: "referenceLive", pack: "research", article: topic || "unknown" };
|
|
90
|
+
}
|
|
80
91
|
const head = t.split(/\s+/)[0]; // drop trailing " /r/IsA" etc.
|
|
81
92
|
if (head.startsWith("corpus-weak:")) return { kind: "corpusWeak", name: head.slice("corpus-weak:".length) || "unknown" };
|
|
82
93
|
if (head.startsWith("corpus:")) return { kind: "corpus", name: head.slice("corpus:".length) || "unknown" };
|
|
@@ -242,11 +242,18 @@ export function registerCapability(cap) {
|
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
// ---- unregistered dispatch tools ---------------------------------------------
|
|
245
|
-
// Dispatch tools not yet registered; each names the
|
|
245
|
+
// Dispatch tools not yet registered; each names the work it needs first — a
|
|
246
|
+
// precondition/effect design, or resolver wiring, before it can join the registry.
|
|
246
247
|
export const EXCLUDED_FROM_REGISTRY = Object.freeze({
|
|
247
248
|
tmct_context: "unbounded edit-context bundle (multi-file); needs a size/budget precondition",
|
|
248
249
|
tmct_context_more: "unbounded context continuation; same as tmct_context",
|
|
249
250
|
tmct_snippet: "raw source-file read (reads the filesystem); needs a file-read + span precondition",
|
|
251
|
+
tmct_ask: "the plain-English question entry point itself — calls ask.mjs directly and bypasses the capability planner; not one of the planner's operators",
|
|
252
|
+
tmct_export: "reads the memory store's whole fact set with no discriminating param and no resolver goal frame; needs NL-reachability wiring before it can join",
|
|
253
|
+
tmct_ingest: "writes into the memory store (grounds facts); capability() only models read-only operators, so a write path needs its own precondition/effect design first",
|
|
254
|
+
tmct_file_history: "a module-grain cut of the same history edge tmct_history already models; no NL frame distinguishes the granularities yet",
|
|
255
|
+
tmct_method_history: "a method-grain cut of the same history edge tmct_history already models; no NL frame distinguishes the granularities yet",
|
|
256
|
+
tmct_class_history: "a class-grain cut of the same history edge tmct_history already models; no NL frame distinguishes the granularities yet",
|
|
250
257
|
});
|
|
251
258
|
|
|
252
259
|
/** The full registry as a plain frozen object (facts + index), for callers that
|
|
@@ -69,3 +69,53 @@ export function isWorldRow(row) {
|
|
|
69
69
|
export function worldProvenanceTag(worldName) {
|
|
70
70
|
return `world:${worldName}`;
|
|
71
71
|
}
|
|
72
|
+
|
|
73
|
+
const DEFAULT_CONTAINS_PREDICATE = "mgx:default-contains";
|
|
74
|
+
|
|
75
|
+
/** Materialize a world's class-default contents: for every
|
|
76
|
+
* `<room> mgx:default-contains <class>` fact, mint one placed, portable
|
|
77
|
+
* instance of that class in that room unless the room already holds one. A
|
|
78
|
+
* minted book gets `rdf:type <class>` (so it resolves its own sprite),
|
|
79
|
+
* `rdf:type portable` (so the take family accepts it), and
|
|
80
|
+
* `mgx:located-in <room>` (so it shows up in the room and can be carried
|
|
81
|
+
* out). Pure and deterministic: defaults are visited in sorted order, and a
|
|
82
|
+
* collision mints `<class>-2`, `<class>-3`… so re-running never renames an
|
|
83
|
+
* earlier instance. Rows keep whatever world/kind wrapper the input rows
|
|
84
|
+
* carry, so both a loaded shard (world rows) and a bare triple list expand
|
|
85
|
+
* the same way. */
|
|
86
|
+
export function expandWorldDefaultContents(facts) {
|
|
87
|
+
const rows = facts || [];
|
|
88
|
+
const defaults = rows
|
|
89
|
+
.filter((r) => r.predicate === DEFAULT_CONTAINS_PREDICATE)
|
|
90
|
+
.sort((a, b) => `${a.subject}\0${a.object}`.localeCompare(`${b.subject}\0${b.object}`));
|
|
91
|
+
if (!defaults.length) return rows;
|
|
92
|
+
|
|
93
|
+
const instanceIds = new Set(rows.filter((r) => r.predicate === "rdf:type").map((r) => r.subject));
|
|
94
|
+
const placedIn = new Map();
|
|
95
|
+
for (const r of rows) {
|
|
96
|
+
if (r.predicate === "mgx:located-in") placedIn.set(r.subject, r.object);
|
|
97
|
+
}
|
|
98
|
+
const roomAlreadyHas = (klass, room) =>
|
|
99
|
+
rows.some((r) => r.predicate === "rdf:type" && r.object === klass && placedIn.get(r.subject) === room);
|
|
100
|
+
|
|
101
|
+
const wrapper = rows.find((r) => typeof r.world === "string" && r.kind === "fact");
|
|
102
|
+
const wrap = (subject, predicate, object) => ({
|
|
103
|
+
...(wrapper ? { world: wrapper.world, kind: "fact" } : {}),
|
|
104
|
+
subject, predicate, object,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const minted = [];
|
|
108
|
+
for (const d of defaults) {
|
|
109
|
+
const room = d.subject;
|
|
110
|
+
const klass = d.object;
|
|
111
|
+
if (roomAlreadyHas(klass, room)) continue;
|
|
112
|
+
let id = klass;
|
|
113
|
+
let n = 1;
|
|
114
|
+
while (instanceIds.has(id)) { n += 1; id = `${klass}-${n}`; }
|
|
115
|
+
instanceIds.add(id);
|
|
116
|
+
minted.push(wrap(id, "rdf:type", klass));
|
|
117
|
+
minted.push(wrap(id, "rdf:type", "portable"));
|
|
118
|
+
minted.push(wrap(id, "mgx:located-in", room));
|
|
119
|
+
}
|
|
120
|
+
return minted.length ? [...rows, ...minted] : rows;
|
|
121
|
+
}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
// fact simply has nothing for this module to infer a goal toward.
|
|
27
27
|
import { findActionPath } from "../domain/planning.mjs";
|
|
28
28
|
import { loadMemory, readFactRows } from "../adapters/memory/core.mjs";
|
|
29
|
-
import { foldWorldState, adventureTurn } from "./adventure.mjs";
|
|
29
|
+
import { foldWorldState, adventureTurn, worldActionRows } from "./adventure.mjs";
|
|
30
30
|
|
|
31
31
|
const SNAPSHOT_RE = /^(.+)@turn(\d+)$/;
|
|
32
32
|
const baseSubjectOf = (subject) => SNAPSHOT_RE.exec(subject)?.[1] ?? subject;
|
|
@@ -138,7 +138,10 @@ async function stepTowardThenAct({
|
|
|
138
138
|
*/
|
|
139
139
|
export async function runAdventureAutoplayTick(memoryDir, opts = {}) {
|
|
140
140
|
const { exposedRoomIds, planHolder, sessionId = "", env = {}, graph = null, cache = null } = opts;
|
|
141
|
-
|
|
141
|
+
// Auto-play reasons over the game world only — the same isolation the manual
|
|
142
|
+
// command path uses, so a fact the player taught mid-game never steers the
|
|
143
|
+
// planner toward a room the world never placed anything in.
|
|
144
|
+
const rows = worldActionRows(readFactRows(await loadMemory(memoryDir)));
|
|
142
145
|
const state = foldWorldState(rows);
|
|
143
146
|
const here = state.placements.get("player")?.object ?? null;
|
|
144
147
|
const exposed = new Set(exposedRoomIds || []);
|