@polycode-projects/the-mechanical-code-talker 2.9.0 → 2.9.4
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 +50 -28
- package/bin/tmct.mjs +176 -31
- package/corpus/LICENSES.json +7 -0
- package/corpus/sprites/src/sprite-facts.jsonl +1033 -0
- package/corpus/worlds/manifest.json +9 -9
- package/corpus/worlds/shards/ashcombe-hall.jsonl.gz +0 -0
- package/corpus/worlds/shards/spider-fly.jsonl.gz +0 -0
- package/corpus/worlds/src/ashcombe-hall.jsonl +3 -0
- package/corpus/worlds/src/spider-fly.jsonl +17 -0
- package/package.json +37 -34
- package/src/adapters/corpus/wikipedia-live.mjs +145 -0
- package/src/adapters/memory/core.mjs +77 -12
- package/src/adapters/toml-config.mjs +6 -5
- package/src/domain/cli-verbs.mjs +4 -2
- package/src/domain/hanoi-lesson.mjs +10 -0
- package/src/domain/reference-pack.mjs +102 -0
- package/src/domain/spider-fly-world.mjs +54 -1
- package/src/domain/sprite-facts.mjs +0 -0
- package/src/services/adventure-viz.mjs +209 -67
- package/src/services/chat-page-viz.mjs +366 -37
- package/src/services/chat-session.mjs +38 -22
- package/src/services/chat.mjs +199 -20
- package/src/services/fold.mjs +28 -44
- package/src/services/import-file.mjs +7 -6
- package/src/services/init.mjs +10 -5
- package/src/services/ledger-viz.mjs +25 -34
- package/src/services/plan-viz.mjs +26 -26
- package/src/services/sessions.mjs +15 -3
- package/src/services/spider-fly-viz.mjs +51 -45
- package/src/services/sprite-catalog-viz.mjs +187 -3
- package/src/services/viz-theme.mjs +8 -0
- package/src/surfaces/web/adventure-browser-entry.mjs +23 -10
- package/src/surfaces/web/chat-browser-entry.mjs +17 -2
- package/src/surfaces/web/idb-persist.mjs +115 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +155 -25927
- package/src/surfaces/web/sprites-browser-entry.mjs +68 -0
- package/src/tools/memory-fallthrough.mjs +11 -4
|
@@ -116,12 +116,14 @@ export async function createSession({
|
|
|
116
116
|
gitRoot = gitToplevel,
|
|
117
117
|
ephemeral = false,
|
|
118
118
|
narrate = false,
|
|
119
|
-
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
// connection
|
|
123
|
-
//
|
|
124
|
-
//
|
|
119
|
+
liveReference = false,
|
|
120
|
+
// The storage-backend seam: the default (empty or "default") resolves to
|
|
121
|
+
// Backend C — the sqlite store at .tmct/memory/graph.sqlite, a live
|
|
122
|
+
// node:sqlite connection lazily imported on open. The flat-file Backend A is
|
|
123
|
+
// retired from routing. "memory" selects Backend B (zero disk I/O,
|
|
124
|
+
// session-scoped). This is `tmct chat --memory-backend <...>`'s
|
|
125
|
+
// already-resolved value; full precedence (this param > TMCT_MEMORY_BACKEND
|
|
126
|
+
// env > tmct.toml > the sqlite default) resolved below.
|
|
125
127
|
memoryBackend = null,
|
|
126
128
|
} = {}) {
|
|
127
129
|
// EPHEMERAL mode (--ephemeral, or TMCT_EPHEMERAL=1): read the target graph but
|
|
@@ -135,6 +137,12 @@ export async function createSession({
|
|
|
135
137
|
// narrate mode already on. Session-scoped and mutable — `/narrate on|off`
|
|
136
138
|
// flips it turn-to-turn (see `turn()` below). Default OFF.
|
|
137
139
|
let narrateOn = narrate || /^(1|true|yes)$/i.test(String(env.TMCT_NARRATE || ""));
|
|
140
|
+
// LIVE WIKIPEDIA supplement (--live-wikipedia, TMCT_LIVE_WIKIPEDIA=1, or
|
|
141
|
+
// tmct.toml's corpus tier3 — the tier that already means "reach for the
|
|
142
|
+
// network"): start the session with the live lookup enabled. Session-scoped
|
|
143
|
+
// and mutable — `/wiki on|off` flips it turn-to-turn, exactly like narrate.
|
|
144
|
+
// Default OFF; the toml tier is folded in below, once toml has resolved.
|
|
145
|
+
let liveReferenceOn = liveReference || /^(1|true|yes)$/i.test(String(env.TMCT_LIVE_WIKIPEDIA || ""));
|
|
138
146
|
// Graph resolution order (delegates to src/services/cli-args.mjs's
|
|
139
147
|
// resolveRuntimeConfig): explicit --graph path(s) win outright; then --repo
|
|
140
148
|
// (never silently redirected by env); then TMCT_GRAPH_FILE env; then
|
|
@@ -190,6 +198,10 @@ export async function createSession({
|
|
|
190
198
|
// to the shipped defaults for every key.
|
|
191
199
|
const gameConfig = resolveGameConfig(toml);
|
|
192
200
|
|
|
201
|
+
// tmct.toml's corpus tier3 opts the session into the live supplement too —
|
|
202
|
+
// the flag/env tiers above stay authoritative when set.
|
|
203
|
+
if (toml?.corpus?.tier === "tier3") liveReferenceOn = true;
|
|
204
|
+
|
|
193
205
|
// Ephemeral: keep config.graphFile pointing at the READ graph, but divert the
|
|
194
206
|
// write base (repo → logs/memory/sessions) to a throwaway temp dir. The committed
|
|
195
207
|
// target is never touched; the demo's memory simply doesn't persist across runs.
|
|
@@ -259,9 +271,9 @@ export async function createSession({
|
|
|
259
271
|
};
|
|
260
272
|
|
|
261
273
|
// `memoryDir` is the opaque token every memory/core.mjs call in this file
|
|
262
|
-
// threads through unchanged
|
|
263
|
-
//
|
|
264
|
-
// > tmct.toml > default.
|
|
274
|
+
// threads through unchanged — always a Backend B/C handle from
|
|
275
|
+
// openMemoryBackend now that the flat-file Backend A is retired from
|
|
276
|
+
// routing. Precedence — CLI flag > env > tmct.toml > the sqlite default.
|
|
265
277
|
const backendChoice = String(memoryBackend || env.TMCT_MEMORY_BACKEND || toml?.memory?.backend || "").trim().toLowerCase();
|
|
266
278
|
// openMemoryBackend is the ONE shared resolver for this seam — init.mjs's
|
|
267
279
|
// corpus seed calls the exact same function, so a repo's seeded facts and
|
|
@@ -276,14 +288,14 @@ export async function createSession({
|
|
|
276
288
|
// bootstrap (a fixture/provider graph never seeds), only once (the marker),
|
|
277
289
|
// and never when TMCT_NO_SEED=1 opts out.
|
|
278
290
|
//
|
|
279
|
-
// Known
|
|
280
|
-
//
|
|
281
|
-
// file
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
// are unaffected: only the conversational transcript mirror
|
|
286
|
-
// disk, never the facts.
|
|
291
|
+
// Known residual split: seedBootstrapMemory/hasSeededVocabulary resolve
|
|
292
|
+
// their marker file off the STRING `repo` path (correct — the marker is a
|
|
293
|
+
// file, not store content), so W3 seeding still only fires on the default
|
|
294
|
+
// token. And sessions.mjs's per-turn utterance mirror still writes an
|
|
295
|
+
// ordinary .tmct/memory/graph.json off the repo string — a file no routed
|
|
296
|
+
// reader opens now that Backend A is retired from routing. Taught FACTS
|
|
297
|
+
// themselves are unaffected: only the conversational transcript mirror
|
|
298
|
+
// leaks onto disk, never the facts.
|
|
287
299
|
let seeded = null;
|
|
288
300
|
if (empty && backendChoice === "" && String(env.TMCT_NO_SEED || "") !== "1") {
|
|
289
301
|
seeded = await seedBootstrapMemory(repo, env);
|
|
@@ -336,6 +348,7 @@ export async function createSession({
|
|
|
336
348
|
get planState() { return planState; },
|
|
337
349
|
get turns() { return turns; },
|
|
338
350
|
get narrate() { return narrateOn; },
|
|
351
|
+
get liveReference() { return liveReferenceOn; },
|
|
339
352
|
promptFor: () => promptFor(focus),
|
|
340
353
|
|
|
341
354
|
/** One dispatched turn through the FULL sink sequencing (writeLog → writeSidecar
|
|
@@ -346,7 +359,7 @@ export async function createSession({
|
|
|
346
359
|
async turn(line) {
|
|
347
360
|
let result;
|
|
348
361
|
try {
|
|
349
|
-
result = await runTurn(line, { config, source, graph, focus, last, memoryDir, sessionId, env, lexicon, narrate: narrateOn, vocabHint, tel, biasByBundle, planState, gameConfig });
|
|
362
|
+
result = await runTurn(line, { config, source, graph, focus, last, memoryDir, sessionId, env, lexicon, narrate: narrateOn, liveReference: liveReferenceOn, vocabHint, tel, biasByBundle, planState, gameConfig });
|
|
350
363
|
} catch (e) {
|
|
351
364
|
const ts = new Date().toISOString();
|
|
352
365
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -357,13 +370,15 @@ export async function createSession({
|
|
|
357
370
|
turns += 1;
|
|
358
371
|
return { answer: `Something went wrong answering that (${message}). Try rephrasing, or /help.`, end: false, prompt: promptFor(focus) };
|
|
359
372
|
}
|
|
360
|
-
const { answer, logLines, record, focus: nextFocus, last: nextLast, end, narrate: nextNarrate } = result;
|
|
373
|
+
const { answer, logLines, record, focus: nextFocus, last: nextLast, end, narrate: nextNarrate, liveReference: nextLiveReference } = result;
|
|
361
374
|
focus = nextFocus;
|
|
362
375
|
last = nextLast;
|
|
363
376
|
if ("planState" in result) planState = result.planState;
|
|
364
|
-
// /narrate on|off (runCommand)
|
|
365
|
-
// update does — apply
|
|
377
|
+
// /narrate on|off and /wiki on|off (runCommand) ride the turn RESULT the
|
|
378
|
+
// same way a focus update does — apply them to this handle's
|
|
379
|
+
// session-scoped state.
|
|
366
380
|
if (typeof nextNarrate === "boolean") narrateOn = nextNarrate;
|
|
381
|
+
if (typeof nextLiveReference === "boolean") liveReferenceOn = nextLiveReference;
|
|
367
382
|
await writeLog(logLines.join("\n") + "\n");
|
|
368
383
|
await writeSidecar(record);
|
|
369
384
|
turnRecords.push(record);
|
|
@@ -416,12 +431,13 @@ export async function runChat({
|
|
|
416
431
|
gitRoot = gitToplevel,
|
|
417
432
|
ephemeral = false,
|
|
418
433
|
narrate = false,
|
|
434
|
+
liveReference = false,
|
|
419
435
|
memoryBackend = null,
|
|
420
436
|
} = {}) {
|
|
421
437
|
// createSession's first-run seed (~2-3s) produces ZERO output until it fully
|
|
422
438
|
// resolves, which otherwise reads as `npm run chat` hanging with total silence.
|
|
423
439
|
output.write("tmct — starting…\n");
|
|
424
|
-
const session = await createSession({ repoPath, graphPaths, configPath, source, env, cwd, gitRoot, ephemeral, narrate, memoryBackend });
|
|
440
|
+
const session = await createSession({ repoPath, graphPaths, configPath, source, env, cwd, gitRoot, ephemeral, narrate, liveReference, memoryBackend });
|
|
425
441
|
|
|
426
442
|
const dim = (s) => (env.NO_COLOR || !output.isTTY ? s : `\x1b[2m${s}\x1b[0m`);
|
|
427
443
|
for (const line of session.bannerLines) output.write(dim(line) + "\n");
|
package/src/services/chat.mjs
CHANGED
|
@@ -46,8 +46,12 @@ import { readConstructionFiles } from "../adapters/corpus/construction-banks.mjs
|
|
|
46
46
|
import { fuzzyMatchInSet, fuzzyBound } from "../domain/interpret/fuzzy.mjs";
|
|
47
47
|
import { loadLexicon, lookupNoun } from "../domain/grammar/lexicon.mjs";
|
|
48
48
|
import { pickPhrase } from "../domain/answer-variants.mjs";
|
|
49
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
REFERENCE_PACK_NAME, cleanMissReferenceTerm, renderReferenceAnswer, referenceProvenanceTag,
|
|
51
|
+
LIVE_PACK_NAME, cleanMissLiveTerm, renderLiveReferenceAnswer, liveProvenanceTag,
|
|
52
|
+
} from "../domain/reference-pack.mjs";
|
|
50
53
|
import { getReferencePackProvider } from "../adapters/corpus/reference-pack.mjs";
|
|
54
|
+
import { getLiveReferenceProvider } from "../adapters/corpus/wikipedia-live.mjs";
|
|
51
55
|
import { CHILD_PACK_NAME, childProvenanceTag } from "../domain/child-pack.mjs";
|
|
52
56
|
import { getChildPackProvider } from "../adapters/corpus/child-pack.mjs";
|
|
53
57
|
import { dialogueActForLane } from "../domain/dialogue-acts.mjs";
|
|
@@ -2391,6 +2395,15 @@ const BOARD_WHERE_EVERY_RE = /^where\s+(?:is|are)\s+(?:every|each|all(?:\s+the)?
|
|
|
2391
2395
|
* question-shape ("was X Y?") this could ever misfire on. */
|
|
2392
2396
|
const TEACH_PROPERTY_RE = /^(?:every\s+|each\s+|all\s+|the\s+)?(.+?)\s+(?:is|are|was|were)\s+(?!an?\b|the\b)([A-Za-z][\w-]*)$/i;
|
|
2393
2397
|
|
|
2398
|
+
/** The closed place-adverb set ("anywhere"/"everywhere"/"nowhere"/
|
|
2399
|
+
* "somewhere"). One of these sitting in an OBJECT slot ("http.mjs used is
|
|
2400
|
+
* anywhere") marks a garbled usage QUESTION, never a storable property or
|
|
2401
|
+
* relation object: no reader ever matches a fact whose object is a place
|
|
2402
|
+
* adverb, so storing one is a silent write with no possible read-back.
|
|
2403
|
+
* Every teach path that binds a free-form object refuses on it, and the
|
|
2404
|
+
* teach-offer generators never suggest a phrasing that contains one. */
|
|
2405
|
+
const PLACE_ADVERB_OBJECT_RE = /^(?:anywhere|everywhere|nowhere|somewhere)$/i;
|
|
2406
|
+
|
|
2394
2407
|
/** The teach lane's provenance tag — mirrors grammar/assert.mjs's provenanceTag
|
|
2395
2408
|
* shape under a distinct "teach:" family, so a taught fact is auditable apart
|
|
2396
2409
|
* from the ACE-parsed asserts: teach:chat:<sessionId>@<ts>. core.mjs maps the
|
|
@@ -2735,12 +2748,18 @@ async function objectReadsAsNonNoun(word) {
|
|
|
2735
2748
|
return false;
|
|
2736
2749
|
}
|
|
2737
2750
|
}
|
|
2738
|
-
async function unknownObjectFallback(payload, { memoryDir, sessionId, lexicon }, cache = null) {
|
|
2751
|
+
async function unknownObjectFallback(payload, { memoryDir, sessionId, lexicon, classIntent = false }, cache = null) {
|
|
2739
2752
|
if (!memoryDir) return null;
|
|
2740
2753
|
const m = String(payload).trim().match(UNKNOWN_SUBJECT_RE);
|
|
2741
2754
|
if (!m) return null;
|
|
2742
2755
|
const [, det, subjectRaw, verb, objectRaw] = m;
|
|
2743
|
-
|
|
2756
|
+
// `classIntent` (an explicit "kind of"/"type of" infix in the ORIGINAL
|
|
2757
|
+
// sentence, detected by the caller before stripKindOf erased it) is the
|
|
2758
|
+
// same class-level signal a universal quantifier gives: "dog is a kind of
|
|
2759
|
+
// mammal" — tmct's OWN read-back phrasing for a subClassOf fact — names a
|
|
2760
|
+
// class relation, never one entity's property, so it earns the mint the
|
|
2761
|
+
// bare unmarked "module is banana" shape must still never get.
|
|
2762
|
+
if (!/^(?:every|each|all|any)$/i.test((det || "").trim()) && !classIntent) return null; // class-level mint needs a universal quantifier or an explicit kind-of infix
|
|
2744
2763
|
const { loadLexicon, lookupNoun } = await import("../domain/grammar/lexicon.mjs");
|
|
2745
2764
|
const lex = lexicon || loadLexicon();
|
|
2746
2765
|
const subjectGrounded = await isGroundedTerm(subjectRaw, lex, memoryDir, cache);
|
|
@@ -2832,6 +2851,7 @@ async function unknownAdjectiveFallback(payload, { memoryDir, sessionId, lexicon
|
|
|
2832
2851
|
const m = String(payload).trim().match(UNKNOWN_SUBJECT_RE);
|
|
2833
2852
|
if (!m) return null;
|
|
2834
2853
|
const [, , subjectRaw, , objectRaw] = m;
|
|
2854
|
+
if (PLACE_ADVERB_OBJECT_RE.test(objectRaw)) return null; // a place adverb is never a property
|
|
2835
2855
|
const { loadLexicon, lookupNoun, classify } = await import("../domain/grammar/lexicon.mjs");
|
|
2836
2856
|
const lex = lexicon || loadLexicon();
|
|
2837
2857
|
// Y already a known NOUN or a fact-grounded CLASS term — a genuine class-
|
|
@@ -3162,6 +3182,7 @@ async function generalVerbTeach(payload) {
|
|
|
3162
3182
|
// object no question can match.
|
|
3163
3183
|
const object = folded.object.replace(/^(?:an?|the)\s+/i, "").trim();
|
|
3164
3184
|
if (!subject || !object) return null; // no well-formed triple — honest decline (point 6)
|
|
3185
|
+
if (PLACE_ADVERB_OBJECT_RE.test(object)) return null; // a place adverb is never a real object
|
|
3165
3186
|
return { subject, predicate: negated ? negatedPredicate(folded.predicate) : folded.predicate, object };
|
|
3166
3187
|
}
|
|
3167
3188
|
|
|
@@ -3420,13 +3441,24 @@ function matchBareCanTeach(text) {
|
|
|
3420
3441
|
* user to teach a class-membership fact that was never what they said. Only
|
|
3421
3442
|
* a payload that ALREADY carried an article gets its article corrected —
|
|
3422
3443
|
* the "every monkey is a animal" -> "an animal" case this function exists
|
|
3423
|
-
* for in the first place.
|
|
3444
|
+
* for in the first place.
|
|
3445
|
+
*
|
|
3446
|
+
* A PLURAL phrasing ("all spiders are venomous") folds its subject to the
|
|
3447
|
+
* singular the suggested "every …" rewrite grammatically requires — "every
|
|
3448
|
+
* spiders is venomous" is ungrammatical AND stores under a different
|
|
3449
|
+
* spelling than the singular every other frame uses, so following it
|
|
3450
|
+
* verbatim wrote a plural-keyed orphan fact. Gated on the "are" copula, the
|
|
3451
|
+
* same is/are safety distinction unknownSubjectFallback draws (an s-final
|
|
3452
|
+
* singular like "redis is a cache" must never strip). A trailing
|
|
3453
|
+
* sentence-final mark is tolerated for the same reason UNKNOWN_SUBJECT_RE
|
|
3454
|
+
* tolerates one: an ordinary full-sentence turn ("dog is a mammal.")
|
|
3455
|
+
* otherwise lost its hint by one character. */
|
|
3424
3456
|
function teachSuggestion(payload) {
|
|
3425
|
-
const m = String(payload).match(/^(?:every |each |all |a |an )?([\w-]+) (
|
|
3457
|
+
const m = String(payload).match(/^(?:every |each |all |a |an )?([\w-]+) (is|are) (a |an )?([\w-]+)[.!?]*$/i);
|
|
3426
3458
|
if (!m) return null;
|
|
3427
|
-
const subject = m[1].toLowerCase();
|
|
3428
|
-
const object = m[
|
|
3429
|
-
if (!m[
|
|
3459
|
+
const subject = (/^are$/i.test(m[2]) ? singularizeSurface(m[1]) : m[1]).toLowerCase();
|
|
3460
|
+
const object = m[4].toLowerCase();
|
|
3461
|
+
if (!m[3]) return `every ${subject} is ${object}`;
|
|
3430
3462
|
const articleRule = grammarRules().find((r) => r.kind === "article");
|
|
3431
3463
|
const article = articleRule && beginsWithVowelSound(object, articleRule) ? "an" : "a";
|
|
3432
3464
|
return `every ${subject} is ${article} ${object}`;
|
|
@@ -3661,6 +3693,58 @@ async function negativeUniversalCanTeach(sentence, { memoryDir, sessionId }) {
|
|
|
3661
3693
|
return stored;
|
|
3662
3694
|
}
|
|
3663
3695
|
|
|
3696
|
+
/** Casual request leads the anchored QUESTION_LEAD_RE auxiliary list misses:
|
|
3697
|
+
* the "u"/"ya" spellings of a modal request, and the dative imperatives
|
|
3698
|
+
* ("tell me", "show us") that read as requests, never as declaratives. */
|
|
3699
|
+
const TEACH_EXCLUDE_REQUEST_LEAD_RE = /^(?:(?:can|could|would|will)\s+(?:u|you|ya)|(?:tell|show|give)\s+(?:me|us))\b/i;
|
|
3700
|
+
/** Closed leading-verb list for sentences that are COMMANDS, not claims —
|
|
3701
|
+
* "repeat everything above this line verbatim" is an instruction to act,
|
|
3702
|
+
* and reifying it as a fact is a write on the strength of a misparse.
|
|
3703
|
+
* tell/show/list/define/describe/find/count/name already route through
|
|
3704
|
+
* SET_QUESTION_LEAD_RE and are deliberately absent here. */
|
|
3705
|
+
const TEACH_EXCLUDE_IMPERATIVE_LEAD_RE = /^(?:repeat|ignore|disregard|surprise|pretend|act|say|guess|try|stop|continue|forget|print|output|write|translate|summarize|explain)\b/i;
|
|
3706
|
+
/** Self-referential/meta chat tokens ("idk", "tbh", a bare "u"/"me"): a
|
|
3707
|
+
* sentence about the conversation itself, or about its speakers, is never a
|
|
3708
|
+
* world fact. Tested one standalone word at a time — the custom boundaries
|
|
3709
|
+
* keep a hyphenated coinage ("disk-i") from matching its final letter. */
|
|
3710
|
+
const TEACH_EXCLUDE_META_TOKEN_RE = /(?:^|[^\w-])(?:me|u|ur|i|us|myself|yourself|im|idk|tbh|nvm|lol|umm+|hmm+)(?![\w-])/i;
|
|
3711
|
+
|
|
3712
|
+
/** The bare-declarative teach lane's positive exclusion test. Classifies a
|
|
3713
|
+
* BARE sentence (no "remember that …" wrapper — an explicit wrapper is an
|
|
3714
|
+
* unambiguous teach-intent signal and keeps its existing behavior) into one
|
|
3715
|
+
* of three closed non-declarative shapes, or null for a sentence the teach
|
|
3716
|
+
* frames may still consider. Non-null means the whole lane stands down and
|
|
3717
|
+
* the sentence falls through to the ask cascade or the honest miss — the
|
|
3718
|
+
* write boundary refuses BEFORE any frame can reify a misparse.
|
|
3719
|
+
*
|
|
3720
|
+
* Three classes, checked in order:
|
|
3721
|
+
* - "interrogative": a casual request lead QUESTION_LEAD_RE's anchored
|
|
3722
|
+
* first-word list misses, or a genuine mid-sentence interrogative
|
|
3723
|
+
* (hasMidSentenceInterrogative — run here unconditionally, where the
|
|
3724
|
+
* per-frame gates below only ever ran it on their own paths);
|
|
3725
|
+
* - "imperative": a closed leading command verb. The retract phrasings
|
|
3726
|
+
* ("forget that X is a Y", "forget that disk-1 rests on peg-b") are
|
|
3727
|
+
* carved out — they are this lane's own, deliberate write-boundary
|
|
3728
|
+
* actions, not misparses;
|
|
3729
|
+
* - "self-referential": a standalone meta/chat token anywhere in the
|
|
3730
|
+
* sentence. A pronoun-SUBJECT sentence ("i am a developer") is carved
|
|
3731
|
+
* out so it still reaches the pronoun guard's specific decline below —
|
|
3732
|
+
* same no-store outcome, better guidance than a silent fall-through.
|
|
3733
|
+
*
|
|
3734
|
+
* Runs applyPreambleFrames itself (idempotent on an already-peeled
|
|
3735
|
+
* sentence), so a stripped greeting can never leave a leading token that
|
|
3736
|
+
* misclassifies, and callers outside teachLane can hand it a raw surface. */
|
|
3737
|
+
async function teachExclusionReason(sentence) {
|
|
3738
|
+
const s = applyPreambleFrames(String(sentence || "").trim());
|
|
3739
|
+
if (TEACH_EXCLUDE_REQUEST_LEAD_RE.test(s) || (await hasMidSentenceInterrogative(s))) return "interrogative";
|
|
3740
|
+
const unpunctuated = s.replace(/[.!?]+\s*$/, "");
|
|
3741
|
+
if (TEACH_EXCLUDE_IMPERATIVE_LEAD_RE.test(s)
|
|
3742
|
+
&& !RETRACT_FORGET_RE.test(unpunctuated) && !RETRACT_FORGET_LOCATIVE_RE.test(unpunctuated)) return "imperative";
|
|
3743
|
+
if (TEACH_EXCLUDE_META_TOKEN_RE.test(s) && !TEACH_PRONOUN_RE.test(s)) return "self-referential";
|
|
3744
|
+
return null;
|
|
3745
|
+
}
|
|
3746
|
+
export { teachExclusionReason };
|
|
3747
|
+
|
|
3664
3748
|
async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cache = null, planHolder = null }) {
|
|
3665
3749
|
// A closed discourse-marker preamble ahead of a teach sentence ("howdy
|
|
3666
3750
|
// pardner, remember that TaskController is fragile") would otherwise
|
|
@@ -3683,6 +3767,12 @@ async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cac
|
|
|
3683
3767
|
if (QUESTION_LEAD_RE.test(correctMisspellings(rawInput))) return null;
|
|
3684
3768
|
const m = rawInput.match(TEACH_RE);
|
|
3685
3769
|
const wrappedInput = m ? m[1].trim() : null;
|
|
3770
|
+
// The positive exclusion test (teachExclusionReason, above) — BARE surface
|
|
3771
|
+
// only: an interrogative, imperative, or self-referential sentence never
|
|
3772
|
+
// reaches any teach frame, so a fresh casual phrasing can't slip past the
|
|
3773
|
+
// per-frame gates and reify as a fact. An explicit wrapper keeps its
|
|
3774
|
+
// existing, more permissive path.
|
|
3775
|
+
if (wrappedInput == null && (await teachExclusionReason(rawInput))) return null;
|
|
3686
3776
|
// Refuse an existential BEFORE any frame below can read it as a universal:
|
|
3687
3777
|
// every one of them stores "some men are fathers" as a premise meaning every
|
|
3688
3778
|
// man, whether it keeps the quantifier as an attribute the reasoner doesn't
|
|
@@ -3712,7 +3802,12 @@ async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cac
|
|
|
3712
3802
|
// kind of parent" normalizes to "a father is a parent", which
|
|
3713
3803
|
// unknownSubjectFallback already stores as father ⊑ parent ("parent" is
|
|
3714
3804
|
// already a lexicon noun).
|
|
3715
|
-
const
|
|
3805
|
+
const kindOfInfixRe = /\b(is|are|was|were)\s+(?:an?\s+)?(?:kind|type)\s+of\s+/i;
|
|
3806
|
+
const stripKindOf = (s) => (s == null ? s : s.replace(kindOfInfixRe, "$1 a "));
|
|
3807
|
+
// Remembered BEFORE the strip erases it: an explicit "kind of"/"type of"
|
|
3808
|
+
// infix is an unambiguous class-level claim, and unknownObjectFallback's
|
|
3809
|
+
// quantifier gate accepts it as a peer of "every" (its own docblock).
|
|
3810
|
+
const kindOfClassIntent = kindOfInfixRe.test(wrappedInput ?? rawInput);
|
|
3716
3811
|
// "my <class-noun> <Name> is/are …" — a THIRD natural phrasing of the exact
|
|
3717
3812
|
// same "X is a Y" assertion this lane already teaches two other ways ("john
|
|
3718
3813
|
// is a man", a bare name; "every cat is an animal", a universal quantifier) — a
|
|
@@ -4600,7 +4695,7 @@ async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cac
|
|
|
4600
4695
|
// STATIC lexicon (or a prior taught fact) already grounds can mint a
|
|
4601
4696
|
// brand-new object term. See unknownObjectFallback's own docblock for the
|
|
4602
4697
|
// exact narrowing rules (the "both sides ungrounded" safety guard, etc.).
|
|
4603
|
-
const objectFallback = await unknownObjectFallback(payload, { memoryDir, sessionId, lexicon }, cache);
|
|
4698
|
+
const objectFallback = await unknownObjectFallback(payload, { memoryDir, sessionId, lexicon, classIntent: kindOfClassIntent }, cache);
|
|
4604
4699
|
if (objectFallback) return objectFallback;
|
|
4605
4700
|
// ADJECTIVE-MINT fallback: tried right after unknownObjectFallback
|
|
4606
4701
|
// declines, so a grounded subject (static
|
|
@@ -4617,7 +4712,7 @@ async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cac
|
|
|
4617
4712
|
// wrapped "X is a Y" over known lexicon still lands as rdfs:subClassOf.
|
|
4618
4713
|
if (wrapped) {
|
|
4619
4714
|
const prop = wrapped.match(TEACH_PROPERTY_RE);
|
|
4620
|
-
if (prop) {
|
|
4715
|
+
if (prop && !PLACE_ADVERB_OBJECT_RE.test(prop[2])) {
|
|
4621
4716
|
const stored = await teachFact(memoryDir, sessionId, {
|
|
4622
4717
|
subject: prop[1], predicate: HAS_PROPERTY_PREDICATE, object: prop[2],
|
|
4623
4718
|
});
|
|
@@ -5270,6 +5365,7 @@ export async function helpText() {
|
|
|
5270
5365
|
["/capabilities", "what /plan can plan over: the built-in graph tools plus your taught actions"],
|
|
5271
5366
|
["/syllogise <term>", "work out and remember what follows from the facts about a term (needed for chains longer than 2 hops)"],
|
|
5272
5367
|
["/narrate on|off", "verbose developer/debug mode: decision points, matched pattern, results+sources, goal per turn"],
|
|
5368
|
+
["/wiki on|off", "live Wikipedia supplement (default off): a question I can't answer also tries en.wikipedia.org (network), cited"],
|
|
5273
5369
|
["/help", "this list"],
|
|
5274
5370
|
["/exit", "leave the session (also Ctrl+C / Ctrl+D)"],
|
|
5275
5371
|
];
|
|
@@ -7666,7 +7762,8 @@ async function factReadBackReaders(memoryDir, query, envelope, miss, graph = nul
|
|
|
7666
7762
|
// only ever fires for "it"/"this"/"that" — never a personal pronoun like
|
|
7667
7763
|
// "you", so `subject` itself would already carry the pronoun verbatim).
|
|
7668
7764
|
if (subject && !/^there\b/i.test(subject) && !envelope?.parsed
|
|
7669
|
-
&& !IS_ADJECTIVE_YESNO_PRONOUN_SUBJECT_RE.test(rawSubject)
|
|
7765
|
+
&& !IS_ADJECTIVE_YESNO_PRONOUN_SUBJECT_RE.test(rawSubject)
|
|
7766
|
+
&& !PLACE_ADVERB_OBJECT_RE.test(emptyIsAdj[2].trim())) {
|
|
7670
7767
|
return unknownAdjectiveOffer(subject, emptyIsAdj[2].trim().toLowerCase());
|
|
7671
7768
|
}
|
|
7672
7769
|
}
|
|
@@ -8815,7 +8912,7 @@ async function factReadBackReaders(memoryDir, query, envelope, miss, graph = nul
|
|
|
8815
8912
|
// subject known only under an UNRELATED property (e.g. "deprecated")
|
|
8816
8913
|
// must not offer to teach "tested" when ask()'s own grammar already
|
|
8817
8914
|
// resolved it structurally.
|
|
8818
|
-
if (!envelope?.parsed) return unknownAdjectiveOffer(subject, adjective);
|
|
8915
|
+
if (!envelope?.parsed && !PLACE_ADVERB_OBJECT_RE.test(adjective)) return unknownAdjectiveOffer(subject, adjective);
|
|
8819
8916
|
}
|
|
8820
8917
|
}
|
|
8821
8918
|
|
|
@@ -9459,6 +9556,39 @@ async function referencePackMissAnswer(term, { graph, memoryDir, lexicon, env, c
|
|
|
9459
9556
|
return key ? referencePackAnswerForKey(key, env) : null;
|
|
9460
9557
|
}
|
|
9461
9558
|
|
|
9559
|
+
/** The LIVE variant of cleanMissPackKey — the same resolveEntity and
|
|
9560
|
+
* remembered-fact checks, but through cleanMissLiveTerm, which drops the
|
|
9561
|
+
* lexicon-membership wall: a word the lexicon has never met is exactly what
|
|
9562
|
+
* the live lookup exists for. Null means the turn proceeds byte-identically
|
|
9563
|
+
* to a live-off run. */
|
|
9564
|
+
async function cleanMissLiveKey(term, { graph, memoryDir, lexicon, cache }) {
|
|
9565
|
+
if (!term || !memoryDir) return null;
|
|
9566
|
+
let key = null;
|
|
9567
|
+
try { key = cleanMissLiveTerm(term, lexicon ?? undefined); } catch { key = null; }
|
|
9568
|
+
if (!key) return null;
|
|
9569
|
+
if (await resolveEntity(graph, term)) return null;
|
|
9570
|
+
let normFactTerm;
|
|
9571
|
+
try { ({ normFactTerm } = await import("../adapters/memory/core.mjs")); } catch { return null; }
|
|
9572
|
+
const variants = factTermVariants(normFactTerm, term);
|
|
9573
|
+
variants.add(key);
|
|
9574
|
+
const rows = await factRows(memoryDir, cache);
|
|
9575
|
+
if (rows.some((f) => variants.has(f.subject) || variants.has(f.object))) return null;
|
|
9576
|
+
return key;
|
|
9577
|
+
}
|
|
9578
|
+
|
|
9579
|
+
/** The live Wikipedia lookup for an already-gated key: the article, or null
|
|
9580
|
+
* (toggle-off provider, network failure, throttle, drift-guard rejection —
|
|
9581
|
+
* all byte-identical to a live-off run). `onLiveLookup` is a notify-only
|
|
9582
|
+
* hook (the web page's "searching wikipedia…" statusline); its own failure
|
|
9583
|
+
* is swallowed too. */
|
|
9584
|
+
async function liveReferenceAnswerForKey(key, onLiveLookup) {
|
|
9585
|
+
try { if (typeof onLiveLookup === "function") onLiveLookup(key); } catch { /* notify-only */ }
|
|
9586
|
+
let article = null;
|
|
9587
|
+
try { article = await getLiveReferenceProvider().lookup(key); } catch { article = null; }
|
|
9588
|
+
if (!article) return null;
|
|
9589
|
+
return { key, article, text: renderLiveReferenceAnswer(key, article) };
|
|
9590
|
+
}
|
|
9591
|
+
|
|
9462
9592
|
/** The child-pack half of learn-on-miss, for an already-gated key: look the
|
|
9463
9593
|
* key up in the shipped child triples pack and append every fact under child
|
|
9464
9594
|
* provenance, so the SAME question can be re-asked from the store. Null on a
|
|
@@ -9480,13 +9610,13 @@ async function childPackFactsForKey(key, { memoryDir, env, cache }) {
|
|
|
9480
9610
|
/** Store the article's first-sentence isa as a subClassOf fact carrying
|
|
9481
9611
|
* reference provenance — AFTER the cited answer composed, and failure-
|
|
9482
9612
|
* tolerated: the answer stands whether or not the fact lands. */
|
|
9483
|
-
async function appendReferenceIsaFact(memoryDir, key, article, cache) {
|
|
9613
|
+
async function appendReferenceIsaFact(memoryDir, key, article, cache, tagFor = referenceProvenanceTag) {
|
|
9484
9614
|
if (!article?.isa) return;
|
|
9485
9615
|
try {
|
|
9486
9616
|
const { appendFact } = await import("../adapters/memory/core.mjs");
|
|
9487
9617
|
await appendFact(memoryDir, {
|
|
9488
9618
|
subject: key, predicate: "rdfs:subClassOf", object: article.isa,
|
|
9489
|
-
provenance:
|
|
9619
|
+
provenance: tagFor(article),
|
|
9490
9620
|
});
|
|
9491
9621
|
if (cache) cache.rows = null;
|
|
9492
9622
|
} catch { /* tolerated — the cited answer is already composed */ }
|
|
@@ -10649,7 +10779,7 @@ const DECISION_RECALL_RE = /^(?:remind\s+me\s+)?what\s+(?:did\s+)?(?:we|i|you)\s
|
|
|
10649
10779
|
* than silently accepted alongside the current location. */
|
|
10650
10780
|
const MOVE_HISTORY_RE = /^where\s+did\s+(.+?)\s+(?:move|get\s+moved|go)(?:\s+to)?[?.!\s]*$/i;
|
|
10651
10781
|
|
|
10652
|
-
async function runAsk(query, { config, source, graph, focus, last, templates, memoryDir, sessionId = "", lexicon = null, env, trace, vocabHint = null, tel = null, biasByBundle = {}, cache = null, vocabAntecedent = null, planHolder = null, gameConfig = DEFAULT_GAME_CONFIG }) {
|
|
10782
|
+
async function runAsk(query, { config, source, graph, focus, last, templates, memoryDir, sessionId = "", lexicon = null, env, trace, vocabHint = null, tel = null, biasByBundle = {}, cache = null, vocabAntecedent = null, planHolder = null, gameConfig = DEFAULT_GAME_CONFIG, liveReference = false, onLiveLookup = null }) {
|
|
10653
10783
|
const ts = new Date().toISOString();
|
|
10654
10784
|
// DISCOURSE ANAPHORA: a follow-up like "which of those are tested" / "count
|
|
10655
10785
|
// them" filters or counts the PREVIOUS answer's entity set, threaded as
|
|
@@ -11251,6 +11381,14 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
11251
11381
|
const ref = await referencePackAnswerForKey(key, env);
|
|
11252
11382
|
if (ref) bareMetaHit = { text: ref.text, replace: true, reference: ref };
|
|
11253
11383
|
}
|
|
11384
|
+
// The live Wikipedia supplement (opt-in), LAST — the shipped packs
|
|
11385
|
+
// always speak first, and a live null/failure leaves bareMetaHit
|
|
11386
|
+
// exactly as a live-off run would.
|
|
11387
|
+
if (!bareMetaHit && liveReference && refTerm) {
|
|
11388
|
+
const liveKey = await cleanMissLiveKey(refTerm, { graph, memoryDir, lexicon, cache });
|
|
11389
|
+
const live = liveKey ? await liveReferenceAnswerForKey(liveKey, onLiveLookup) : null;
|
|
11390
|
+
if (live) bareMetaHit = { text: live.text, replace: true, live };
|
|
11391
|
+
}
|
|
11254
11392
|
}
|
|
11255
11393
|
}
|
|
11256
11394
|
// A bare "what is X" naming a REAL code-graph entity (not a taught fact,
|
|
@@ -11298,6 +11436,15 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
11298
11436
|
note(trace, "lane: (2b) REFERENCE PACK — a bare \"what is X\" clean miss answered from the shipped reference pack, cited");
|
|
11299
11437
|
note(trace, `source: reference pack ${REFERENCE_PACK_NAME} — article "${bareMetaHit.reference.article.title}" (revid ${bareMetaHit.reference.article.revid})`);
|
|
11300
11438
|
await appendReferenceIsaFact(memoryDir, bareMetaHit.reference.key, bareMetaHit.reference.article, cache);
|
|
11439
|
+
} else if (bareMetaHit?.live) {
|
|
11440
|
+
// The bare-form LIVE hit settles the same way, under live provenance.
|
|
11441
|
+
answer = bareMetaHit.text;
|
|
11442
|
+
via = "reference";
|
|
11443
|
+
recordMiss = false;
|
|
11444
|
+
handled = true;
|
|
11445
|
+
note(trace, "lane: (2b) LIVE WIKIPEDIA — a bare \"what is X\" clean miss answered from a live en.wikipedia.org lookup (opt-in), cited");
|
|
11446
|
+
note(trace, `source: live reference ${LIVE_PACK_NAME} — article "${bareMetaHit.live.article.title}" (revid ${bareMetaHit.live.article.revid})`);
|
|
11447
|
+
await appendReferenceIsaFact(memoryDir, bareMetaHit.live.key, bareMetaHit.live.article, cache, liveProvenanceTag);
|
|
11301
11448
|
} else if (bareMetaHit) {
|
|
11302
11449
|
answer = bareMetaHit.replace ? bareMetaHit.text : `${answer}\n${bareMetaHit.text}`;
|
|
11303
11450
|
// Same discipline as lane (3): a fact-lane return flagged `miss` is an
|
|
@@ -11707,6 +11854,22 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
11707
11854
|
await appendReferenceIsaFact(memoryDir, ref.key, ref.article, cache);
|
|
11708
11855
|
}
|
|
11709
11856
|
}
|
|
11857
|
+
// The live Wikipedia supplement (opt-in), strictly AFTER both shipped
|
|
11858
|
+
// packs: its own gate (no lexicon-membership wall) may pass where the
|
|
11859
|
+
// pack gate could not, and a null/throwing lookup leaves the honest
|
|
11860
|
+
// miss byte-identical to a live-off run.
|
|
11861
|
+
if (miss && recordMiss && via === "composed" && liveReference && refTerm) {
|
|
11862
|
+
const liveKey = await cleanMissLiveKey(refTerm, { graph, memoryDir, lexicon, cache });
|
|
11863
|
+
const live = liveKey ? await liveReferenceAnswerForKey(liveKey, onLiveLookup) : null;
|
|
11864
|
+
if (live) {
|
|
11865
|
+
answer = live.text;
|
|
11866
|
+
via = "reference";
|
|
11867
|
+
recordMiss = false;
|
|
11868
|
+
note(trace, "lane: (4h) LIVE WIKIPEDIA — a clean miss answered from a live en.wikipedia.org lookup (opt-in), cited");
|
|
11869
|
+
note(trace, `source: live reference ${LIVE_PACK_NAME} — article "${live.article.title}" (revid ${live.article.revid})`);
|
|
11870
|
+
await appendReferenceIsaFact(memoryDir, live.key, live.article, cache, liveProvenanceTag);
|
|
11871
|
+
}
|
|
11872
|
+
}
|
|
11710
11873
|
}
|
|
11711
11874
|
// (5) #1 SHORT TAILORED MISS — replace ONLY the engine's full grammar cheat-sheet
|
|
11712
11875
|
// wall (WALL_MISS_RE). Receipt-bearing misses keep their specific wording.
|
|
@@ -11890,24 +12053,26 @@ const GOAL_BY_COMMAND = {
|
|
|
11890
12053
|
arch: "understand the overall architecture (package/module boundaries)",
|
|
11891
12054
|
capabilities: "see what /plan can plan over — built-in query tools and taught actions",
|
|
11892
12055
|
syllogise: "materialize the entailed facts that follow from what's remembered about one term",
|
|
12056
|
+
wiki: "toggle the live Wikipedia supplement for questions nothing local can answer",
|
|
11893
12057
|
};
|
|
11894
12058
|
|
|
11895
12059
|
/** A slash-command → the mapped tool (or the /help, /focus, /narrate, unknown
|
|
11896
12060
|
* cases). Returns the same { answer, logLines, record, focus } shape as
|
|
11897
12061
|
* runAsk. Also carries a `goal` field mirroring runAsk's own, so
|
|
11898
12062
|
* withGoalLine's "Goal (inferred): …" line fires for command dispatches too. */
|
|
11899
|
-
async function runCommand(line, { config, source, graph, focus, memoryDir, trace, narrate = false, tel = null, biasByBundle = {}, cache = null }) {
|
|
12063
|
+
async function runCommand(line, { config, source, graph, focus, memoryDir, trace, narrate = false, liveReference = false, tel = null, biasByBundle = {}, cache = null }) {
|
|
11900
12064
|
const ts = new Date().toISOString();
|
|
11901
12065
|
const sp = line.indexOf(" ");
|
|
11902
12066
|
const name = (sp === -1 ? line.slice(1) : line.slice(1, sp)).toLowerCase();
|
|
11903
12067
|
const argText = (sp === -1 ? "" : line.slice(sp + 1)).trim();
|
|
11904
|
-
const mk = (answer, { resolvedIds = [], miss = false, newFocus = focus, narrateNext } = {}) => ({
|
|
12068
|
+
const mk = (answer, { resolvedIds = [], miss = false, newFocus = focus, narrateNext, liveReferenceNext } = {}) => ({
|
|
11905
12069
|
answer,
|
|
11906
12070
|
logLines: [ts, `> ${line}`, answer, ""],
|
|
11907
12071
|
record: { type: "turn", ts, query: line, command: name, via: "command", resolvedIds, answeredIds: [], miss },
|
|
11908
12072
|
focus: newFocus,
|
|
11909
12073
|
goal: GOAL_BY_COMMAND[name] || "use a specific tool/command directly",
|
|
11910
12074
|
...(narrateNext !== undefined ? { narrate: narrateNext } : {}),
|
|
12075
|
+
...(liveReferenceNext !== undefined ? { liveReference: liveReferenceNext } : {}),
|
|
11911
12076
|
});
|
|
11912
12077
|
|
|
11913
12078
|
if (name === "help") { note(trace, "goal: get oriented / learn available commands"); return mk(await helpText()); }
|
|
@@ -11931,6 +12096,20 @@ async function runCommand(line, { config, source, graph, focus, memoryDir, trace
|
|
|
11931
12096
|
return mk(`narrate mode ${next ? "on" : "off"}.`, { narrateNext: next });
|
|
11932
12097
|
}
|
|
11933
12098
|
|
|
12099
|
+
// /wiki on|off — the live Wikipedia supplement toggle (session-scoped,
|
|
12100
|
+
// exactly the /narrate pattern: the new state rides the turn RESULT as
|
|
12101
|
+
// `liveReference`, and each session shell applies it to its own mutable
|
|
12102
|
+
// state). A bare "/wiki" reports the CURRENT state and changes nothing.
|
|
12103
|
+
if (name === "wiki") {
|
|
12104
|
+
const arg = argText.toLowerCase();
|
|
12105
|
+
if (arg !== "on" && arg !== "off") {
|
|
12106
|
+
return mk(`live Wikipedia supplement is ${liveReference ? "on" : "off"} — /wiki on or /wiki off. `
|
|
12107
|
+
+ "When on, a question I can't answer also tries en.wikipedia.org (network).");
|
|
12108
|
+
}
|
|
12109
|
+
const next = arg === "on";
|
|
12110
|
+
return mk(`live Wikipedia supplement ${next ? "on" : "off"}.`, { liveReferenceNext: next });
|
|
12111
|
+
}
|
|
12112
|
+
|
|
11934
12113
|
// /memory [verbose] — what tmct remembers, as text (the same renderer
|
|
11935
12114
|
// serves the `tmct memory` CLI).
|
|
11936
12115
|
if (name === "memory") {
|
|
@@ -12871,7 +13050,7 @@ function vocabAntecedentFrom(last) {
|
|
|
12871
13050
|
return m[1];
|
|
12872
13051
|
}
|
|
12873
13052
|
|
|
12874
|
-
export async function runTurn(input, { config, source = defaultSource, graph = null, focus = null, last = null, memoryDir = null, sessionId = "", env = process.env, lexicon = null, narrate = false, vocabHint = null, tel = null, biasByBundle = {}, factRowsCache: injectedFactRowsCache = null, planState = null, gameConfig = null, _noSplit = false } = {}) {
|
|
13053
|
+
export async function runTurn(input, { config, source = defaultSource, graph = null, focus = null, last = null, memoryDir = null, sessionId = "", env = process.env, lexicon = null, narrate = false, liveReference = false, onLiveLookup = null, vocabHint = null, tel = null, biasByBundle = {}, factRowsCache: injectedFactRowsCache = null, planState = null, gameConfig = null, _noSplit = false } = {}) {
|
|
12875
13054
|
// Every game's tuning knobs (spider-fly's mass economy, guess-the-number's
|
|
12876
13055
|
// bounds, the shared plan lane's search-depth cap) — a caller's own
|
|
12877
13056
|
// gameConfig (chat-session.mjs resolves one per session from tmct.toml)
|
|
@@ -12934,7 +13113,7 @@ export async function runTurn(input, { config, source = defaultSource, graph = n
|
|
|
12934
13113
|
// the PLAN NEXT block below write planHolder.state; every other path leaves
|
|
12935
13114
|
// it untouched, and the caller re-threads whatever comes back.
|
|
12936
13115
|
const planHolder = { state: planState };
|
|
12937
|
-
const ctx = { config, source, graph, focus, last, memoryDir, sessionId, templates, env, lexicon, trace, narrate, vocabHint: resolvedVocabHint, tel, biasByBundle, cache: factRowsCache, vocabAntecedent, planHolder, gameConfig: resolvedGameConfig };
|
|
13116
|
+
const ctx = { config, source, graph, focus, last, memoryDir, sessionId, templates, env, lexicon, trace, narrate, liveReference, onLiveLookup, vocabHint: resolvedVocabHint, tel, biasByBundle, cache: factRowsCache, vocabAntecedent, planHolder, gameConfig: resolvedGameConfig };
|
|
12938
13117
|
// A DISPATCHED turn (count / slash-command / ask) becomes the new "last
|
|
12939
13118
|
// answer" that why/say-more re-renders; a conversational turn does not.
|
|
12940
13119
|
// Every dispatched turn's result passes through finish() here — the LAST
|