@polycode-projects/the-mechanical-code-talker 2.11.5 → 2.11.9
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/bin/tmct.mjs +20 -8
- package/package.json +2 -1
- package/src/adapters/toml-config.mjs +2 -0
- package/src/domain/ask-vocab.mjs +32 -0
- package/src/domain/ask.mjs +89 -4
- package/src/domain/domain.mjs +14 -0
- package/src/domain/interpret/strategies/keywords.mjs +18 -2
- package/src/domain/reference-pack.mjs +15 -3
- package/src/services/adventure-viz.mjs +77 -23
- package/src/services/chat-page-viz.mjs +149 -3
- package/src/services/chat.mjs +250 -53
- package/src/services/extensions.mjs +9 -2
- package/src/services/extract-facts.mjs +74 -7
- package/src/services/init.mjs +21 -2
- package/src/services/ledger-viz.mjs +1 -3
- package/src/services/research-viz.mjs +672 -0
- package/src/surfaces/http/server-http.mjs +172 -3
- package/src/surfaces/web/chat-browser-entry.mjs +21 -1
- package/src/surfaces/web/memory-ask-browser.bundle.js +140 -138
- package/src/surfaces/web/research-browser-entry.mjs +319 -0
package/src/services/chat.mjs
CHANGED
|
@@ -4233,7 +4233,7 @@ async function teachExclusionReason(sentence) {
|
|
|
4233
4233
|
}
|
|
4234
4234
|
export { teachExclusionReason };
|
|
4235
4235
|
|
|
4236
|
-
async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cache = null, planHolder = null, graph = null }) {
|
|
4236
|
+
async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cache = null, planHolder = null, graph = null, gameConfig = DEFAULT_GAME_CONFIG }) {
|
|
4237
4237
|
// A closed discourse-marker preamble ahead of a teach sentence ("howdy
|
|
4238
4238
|
// pardner, remember that TaskController is fragile") would otherwise
|
|
4239
4239
|
// corrupt TEACH_RE's own match, so strip it first. applyPreambleFrames is
|
|
@@ -4349,7 +4349,7 @@ async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cac
|
|
|
4349
4349
|
if (memoryDir && !QUESTION_LEAD_RE.test(conjSrc) && /\s+and\s+/i.test(conjSrc)
|
|
4350
4350
|
&& !(await hasMidSentenceInterrogative(conjSrc))) {
|
|
4351
4351
|
const rewrap = (half) => (wrapped != null ? `remember that ${half}` : half);
|
|
4352
|
-
const recurse = (half) => teachLane(rewrap(half), { memoryDir, sessionId, lexicon, cache, planHolder });
|
|
4352
|
+
const recurse = (half) => teachLane(rewrap(half), { memoryDir, sessionId, lexicon, cache, planHolder, gameConfig });
|
|
4353
4353
|
const stripNoted = (t) => String(t).replace(/^noted — remembered(?:\s+\d+\s+facts?)?:\s*/i, "").trim();
|
|
4354
4354
|
const shared = conjSrc.match(/^(.+?)\s+and\s+((?:is|are|has|have|can)\b.+)$/i);
|
|
4355
4355
|
const sharedSubject = shared ? shared[1].match(/^(.+?)\s+(?:is|are|has|have|can)\b/i)?.[1]?.trim() : null;
|
|
@@ -4569,13 +4569,15 @@ async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cac
|
|
|
4569
4569
|
}
|
|
4570
4570
|
}
|
|
4571
4571
|
|
|
4572
|
-
// MID-PLAN BOARD TEACH — a locative fact about a piece the LIVE plan's
|
|
4573
|
-
//
|
|
4574
|
-
//
|
|
4575
|
-
//
|
|
4576
|
-
//
|
|
4577
|
-
//
|
|
4578
|
-
//
|
|
4572
|
+
// MID-PLAN BOARD TEACH — a locative fact about a piece the LIVE plan's moves
|
|
4573
|
+
// touch is accepted and the plan is re-searched from the moved board, never
|
|
4574
|
+
// confirmed-then-contradicted by the next move. The change is written as a
|
|
4575
|
+
// NEW whole-board @step snapshot layer (never a base fact — a base write here
|
|
4576
|
+
// would sit under the standing snapshots and trip the contradictory-board
|
|
4577
|
+
// check on the next solve), then the goal is re-searched from the board as it
|
|
4578
|
+
// now stands. Scoped to the locative teach shape over the plan's own pieces;
|
|
4579
|
+
// every other teach (new vocabulary, new pieces, rules) is untouched, and
|
|
4580
|
+
// with no live plan nothing changes at all.
|
|
4579
4581
|
{
|
|
4580
4582
|
const livePlan = planHolder?.state && !planHolder.state.done
|
|
4581
4583
|
&& Array.isArray(planHolder.state.actions) && planHolder.state.actions.length
|
|
@@ -4583,13 +4585,49 @@ async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cac
|
|
|
4583
4585
|
const boardSrc = (wrapped ?? raw).replace(/[.!?]+\s*$/, "");
|
|
4584
4586
|
const board = livePlan ? boardSrc.match(BOARD_TEACH_LOCATIVE_RE) : null;
|
|
4585
4587
|
if (board && memoryDir && !QUESTION_LEAD_RE.test(boardSrc)) {
|
|
4586
|
-
const { normFactTerm } = await import("../adapters/memory/core.mjs");
|
|
4588
|
+
const { normFactTerm, appendFact } = await import("../adapters/memory/core.mjs");
|
|
4587
4589
|
const planPieces = new Set(livePlan.actions.flatMap((a) => [normFactTerm(a.subject), normFactTerm(a.target)]));
|
|
4588
4590
|
if (planPieces.has(normFactTerm(board[1])) || planPieces.has(normFactTerm(board[4]))) {
|
|
4591
|
+
const { maxSnapshotStep } = await import("../domain/domain.mjs");
|
|
4592
|
+
const { factRows, domain, state } = await loadPlanContext(memoryDir);
|
|
4593
|
+
// The single-placement change over the current fold: same subject and
|
|
4594
|
+
// predicate, new object. Written as the whole mutated board under a
|
|
4595
|
+
// fresh @step layer, so stateFromFacts reads it as the live board and no
|
|
4596
|
+
// base fact is left to contradict the next solve.
|
|
4597
|
+
const subject = normFactTerm(board[1]);
|
|
4598
|
+
const predicate = `mgx:${board[2].toLowerCase()}-${board[3].toLowerCase()}`;
|
|
4599
|
+
const object = normFactTerm(board[4]);
|
|
4600
|
+
const mutated = state.filter((r) => !(r.subject === subject && r.predicate === predicate));
|
|
4601
|
+
mutated.push({ subject, predicate, object });
|
|
4602
|
+
const layer = maxSnapshotStep(factRows, domain) + 1;
|
|
4603
|
+
for (const r of mutated) {
|
|
4604
|
+
await appendFact(memoryDir, {
|
|
4605
|
+
subject: `${r.subject}@step${layer}`, predicate: r.predicate, object: r.object,
|
|
4606
|
+
provenance: `plan:${sessionId || "chat"}:teach-replan:step${layer}`,
|
|
4607
|
+
});
|
|
4608
|
+
}
|
|
4589
4609
|
const at = livePlan.cursor > 0 ? `step ${livePlan.cursor} of ${livePlan.actions.length}` : `0 of ${livePlan.actions.length} moves made`;
|
|
4610
|
+
const goalText = livePlan.goalText ?? livePlan.goalTexts?.join("; ") ?? "the held goal";
|
|
4611
|
+
const remembered = `noted — remembered: "${board[0]}".`;
|
|
4612
|
+
const replan = await solveHeldGoals({ memoryDir, planHolder, gameConfig });
|
|
4613
|
+
if (replan.plan) {
|
|
4614
|
+
const moves = replan.plan.actions.map((a, i) => `${i + 1}. ${a.label}`).join("; ");
|
|
4615
|
+
return {
|
|
4616
|
+
text: `${remembered} That changes the board the live plan was standing on (${at}, toward: ${goalText}), so I replanned from the board as it now stands: ${moves}. Say "next" to make move 1.`,
|
|
4617
|
+
via: "plan", miss: false,
|
|
4618
|
+
};
|
|
4619
|
+
}
|
|
4620
|
+
// The write STANDS, but nothing reaches the goal from the moved board:
|
|
4621
|
+
// the old plan is dropped (goals kept, plan reset) and the failed replan
|
|
4622
|
+
// is named, never a silent success.
|
|
4623
|
+
const maxDepth = gameConfig?.planning?.maxDepth ?? DEFAULT_GAME_CONFIG.planning.maxDepth;
|
|
4624
|
+
planHolder.state = {
|
|
4625
|
+
goals: livePlan.goals, goalTexts: livePlan.goalTexts,
|
|
4626
|
+
actions: null, states: null, stepGoals: null, cursor: 0, done: false,
|
|
4627
|
+
};
|
|
4590
4628
|
return {
|
|
4591
|
-
text:
|
|
4592
|
-
via: "
|
|
4629
|
+
text: `${remembered} That changes the board the live plan was standing on (${at}, toward: ${goalText}) — from this new board no plan reaches the goal within ${maxDepth} moves, so the old plan is dropped. Re-teach the board or say "forget the goal".`,
|
|
4630
|
+
via: "plan", miss: false,
|
|
4593
4631
|
};
|
|
4594
4632
|
}
|
|
4595
4633
|
}
|
|
@@ -5405,6 +5443,17 @@ const MODULE_ORIENT_RE = new RegExp(`^what\\s+does\\s+(.+?)\\s+do${TRAILING_ADVE
|
|
|
5405
5443
|
* ending safe — a syntactic match against a term that isn't a real entity
|
|
5406
5444
|
* simply falls through unchanged, same as every other lane in this file. */
|
|
5407
5445
|
const MODULE_ORIENT_SVO_RE = new RegExp(`^what\\s+(.+?)\\s+does${TRAILING_ADVERB_RE}\\??$`, "i");
|
|
5446
|
+
/** "whats X do" / "what's X do" / "what is X do" — the CONTRACTED phrasing of
|
|
5447
|
+
* "what does X do", where the auxiliary collapses into the "what's"/"whats"
|
|
5448
|
+
* opener and "do" trails the term. MODULE_ORIENT_RE's own "does BEFORE the
|
|
5449
|
+
* term" anchor never sees it, and MODULE_ORIENT_SVO_RE needs a literal "what "
|
|
5450
|
+
* (with a space) so the bare "whats" spelling escapes that too. Safe to end
|
|
5451
|
+
* this loosely because the lane's exact-unique resolveEntity gate below is
|
|
5452
|
+
* still the sole authority — same argument as MODULE_ORIENT_SVO_RE: a term
|
|
5453
|
+
* that is not a real unique entity (a pronoun subject "whats it do", a
|
|
5454
|
+
* non-word) simply declines. The "what(?:'s|s|\s+is)" opener mirrors
|
|
5455
|
+
* MODULE_PURPOSE_RE's tolerance for the apostrophe-less "whats" contraction. */
|
|
5456
|
+
const MODULE_ORIENT_IS_DO_RE = new RegExp(`^what(?:'s|s|\\s+is)\\s+(.+?)\\s+do${TRAILING_ADVERB_RE}\\??$`, "i");
|
|
5408
5457
|
// Purpose/identity phrasing: "whats X for"/"what's X
|
|
5409
5458
|
// about"/"what is X for", the sibling of "what does X do" that asks for the
|
|
5410
5459
|
// SAME module-grain overview. Deliberately does NOT claim the literal noun
|
|
@@ -5474,7 +5523,7 @@ async function moduleOrientLane(query, { graph }) {
|
|
|
5474
5523
|
// (stripFillerWords already eats "please"/"could you" as filler; the politeness
|
|
5475
5524
|
// regex only adds the "explain [to me]" wrapper on top).
|
|
5476
5525
|
q = stripFillerWords(applyPreambleFrames(correctMisspellings(q))).replace(MODULE_ORIENT_POLITENESS_RE, "");
|
|
5477
|
-
const m = q.match(MODULE_ORIENT_RE) || q.match(MODULE_PURPOSE_OF_RE) || q.match(MODULE_PURPOSE_RE) || q.match(MODULE_ORIENT_SVO_RE);
|
|
5526
|
+
const m = q.match(MODULE_ORIENT_RE) || q.match(MODULE_PURPOSE_OF_RE) || q.match(MODULE_PURPOSE_RE) || q.match(MODULE_ORIENT_SVO_RE) || q.match(MODULE_ORIENT_IS_DO_RE);
|
|
5478
5527
|
// "what does src/core/store.mjs do" already reached the overview; the bare
|
|
5479
5528
|
// path and "what is <path>" did not, so the same module answered one
|
|
5480
5529
|
// phrasing and walled two. Both are claimed here rather than in ask.mjs,
|
|
@@ -6904,6 +6953,7 @@ const WHAT_IS_PREP_FACT_RE = new RegExp(`^what(?:'s|\\s+is|\\s+are)\\s+(${PREP_S
|
|
|
6904
6953
|
// the named kind via a direct isa-family fact.
|
|
6905
6954
|
const DO_VERB_ASK_RE = /^(?:do|does)\s+(all\s+|every\s+)?(?:an?\s+|the\s+)?([\w'-]+(?:\s+[\w'-]+)*?)\s+([a-z-]+)[?.!\s]*$/i;
|
|
6906
6955
|
const WHAT_CAN_VERB_RE = /^what\s+can\s+(?!be\s)(.+?)[?.!\s]*$/i;
|
|
6956
|
+
const WHAT_CANNOT_VERB_RE = /^what\s+(?:cannot|can't|cant|can\s+not)\s+(.+?)[?.!\s]*$/i;
|
|
6907
6957
|
const WHICH_KIND_CAN_RE = /^(?:which|what)\s+([\w'-]+(?:\s+[\w'-]+)*?)\s+can\s+(.+?)[?.!\s]*$/i;
|
|
6908
6958
|
|
|
6909
6959
|
/** The negative surface of a yes/no question asks the SAME question as its
|
|
@@ -6924,13 +6974,22 @@ const WHICH_KIND_CAN_RE = /^(?:which|what)\s+([\w'-]+(?:\s+[\w'-]+)*?)\s+can\s+(
|
|
|
6924
6974
|
* trying the raw question first would take a garbage bind over the good one. */
|
|
6925
6975
|
function positiveQuestionSurface(q) {
|
|
6926
6976
|
const s = String(q || "")
|
|
6927
|
-
.replace(/^(?:can't|cannot|can not)\s+/i, "can ")
|
|
6977
|
+
.replace(/^(?:can't|cannot|can not|cant)\s+/i, "can ")
|
|
6928
6978
|
.replace(/^(?:doesn't|does not)\s+/i, "does ")
|
|
6929
6979
|
.replace(/^(?:don't|do not)\s+/i, "do ")
|
|
6930
6980
|
.replace(/^(?:didn't|did not)\s+/i, "did ")
|
|
6931
6981
|
.replace(/\s+(?:not|never)\s+/i, " ");
|
|
6932
6982
|
return s.replace(/\s+/g, " ").trim();
|
|
6933
6983
|
}
|
|
6984
|
+
/** A negated surface ("can a dog not bark") is answered by the positive
|
|
6985
|
+
* reader (see positiveQuestionSurface's docblock), but a bare yes/no lead
|
|
6986
|
+
* then reads as agreeing with the asked polarity — drop the lead and let
|
|
6987
|
+
* the cited fact carry the real polarity on its own. */
|
|
6988
|
+
function withoutPolarityLead(reply) {
|
|
6989
|
+
const text = String(reply.text || "").replace(/^(?:yes|no) — /i, "");
|
|
6990
|
+
return text === reply.text ? reply : { ...reply, text };
|
|
6991
|
+
}
|
|
6992
|
+
const collapsedSurface = (q) => String(q || "").replace(/\s+/g, " ").trim();
|
|
6934
6993
|
|
|
6935
6994
|
/** Cite an isa chain the way (b3b) already cites one — each step as its own
|
|
6936
6995
|
* phrase plus verbatim source. Shared so the inherited-capability answers and
|
|
@@ -7607,11 +7666,13 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
|
|
|
7607
7666
|
// capabilityReply). Mirrors the ISA_ASK_RE block just above on the "never a
|
|
7608
7667
|
// guessed no" discipline: a "no" here is a REMEMBERED negative, never the
|
|
7609
7668
|
// absence of a positive.
|
|
7610
|
-
const
|
|
7669
|
+
const surfacedCan = positiveQuestionSurface(q);
|
|
7670
|
+
const can = surfacedCan.match(CAN_ASK_RE);
|
|
7611
7671
|
if (can) {
|
|
7612
7672
|
const facts = await factRows(memoryDir, cache);
|
|
7613
7673
|
const canUniversal = can[1];
|
|
7614
|
-
|
|
7674
|
+
let reply = capabilityReply(can[2], can[3], facts);
|
|
7675
|
+
if (reply && surfacedCan !== collapsedSurface(q)) reply = withoutPolarityLead(reply);
|
|
7615
7676
|
// Quantified ("can all/every X ..."): the stored facts are generic, and a
|
|
7616
7677
|
// bare "yes" would claim universality the memory can't support — the same
|
|
7617
7678
|
// hedge the do-support surface applies, echoing the quantifier as typed.
|
|
@@ -7711,7 +7772,8 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
|
|
|
7711
7772
|
// (falls through instead): the shape is looser than (b2)'s, so a do-lead
|
|
7712
7773
|
// question some later reader owns must keep its turn. The can't-confirm
|
|
7713
7774
|
// branch is additionally miss-gated for the same reason.
|
|
7714
|
-
const
|
|
7775
|
+
const surfacedDo = positiveQuestionSurface(q);
|
|
7776
|
+
const doAsk = surfacedDo.match(DO_VERB_ASK_RE);
|
|
7715
7777
|
if (doAsk) {
|
|
7716
7778
|
const facts = await factRows(memoryDir, cache);
|
|
7717
7779
|
const universal = !!doAsk[1];
|
|
@@ -7719,7 +7781,8 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
|
|
|
7719
7781
|
const obj = factTermVariants(normFactTerm, doAsk[3]);
|
|
7720
7782
|
// the SAME resolver (b2) answers through, so "do penguins fly" and "can a
|
|
7721
7783
|
// penguin fly" can never disagree in one session
|
|
7722
|
-
|
|
7784
|
+
let reply = capabilityReply(doAsk[2], doAsk[3], facts);
|
|
7785
|
+
if (reply && surfacedDo !== collapsedSurface(q)) reply = withoutPolarityLead(reply);
|
|
7723
7786
|
if (reply && universal) {
|
|
7724
7787
|
// Echo the quantifier as typed ("every dog", "all dogs") — "all dog"
|
|
7725
7788
|
// for a singular every-question is a garbled echo.
|
|
@@ -7838,6 +7901,28 @@ async function factAnswerReaders(memoryDir, query, envelope, miss, biasByBundle
|
|
|
7838
7901
|
}
|
|
7839
7902
|
}
|
|
7840
7903
|
|
|
7904
|
+
// (b3c-neg) "what cannot fly" — the negative twin of (b3c): every stored
|
|
7905
|
+
// mgxneg:capableOf fact whose OBJECT matches. A matched shape with NO
|
|
7906
|
+
// stored negatives returns a definitive memory miss rather than falling
|
|
7907
|
+
// through — the conversational catch-all downstream misread this surface
|
|
7908
|
+
// as small talk and answered with the identity card.
|
|
7909
|
+
const cannotVerb = q.match(WHAT_CANNOT_VERB_RE);
|
|
7910
|
+
if (cannotVerb && cannotVerb[1].trim().split(/\s+/).at(-1)?.toLowerCase() !== "do") {
|
|
7911
|
+
const negVariants = factTermVariants(normFactTerm, cannotVerb[1]);
|
|
7912
|
+
const negHits = (await factRows(memoryDir, cache)).filter(
|
|
7913
|
+
(f) => f.predicate === "mgxneg:capableOf" && negVariants.has(f.object),
|
|
7914
|
+
);
|
|
7915
|
+
if (negHits.length) {
|
|
7916
|
+
const ranked = rankByBiasThenTrust(uniqueFacts(negHits), biasByBundle);
|
|
7917
|
+
const lines = ranked.map(renderFactLine);
|
|
7918
|
+
const shown = lines.slice(0, FACT_ANSWER_CAP);
|
|
7919
|
+
const rest = lines.slice(FACT_ANSWER_CAP);
|
|
7920
|
+
const extra = rest.length ? `\n…and ${rest.length} more — say 'more' to see them.` : "";
|
|
7921
|
+
return { text: shown.join("\n") + extra, replace: true, ...(rest.length ? { pending: { items: rest, noun: "facts" } } : {}) };
|
|
7922
|
+
}
|
|
7923
|
+
return { text: `nothing I remember says anything cannot ${cannotVerb[1].trim()}.`, replace: true, miss: true };
|
|
7924
|
+
}
|
|
7925
|
+
|
|
7841
7926
|
// (b3c) "what can fly" — the unrestricted reverse-by-verb sibling of (b3b):
|
|
7842
7927
|
// every capableOf fact whose OBJECT matches. The "… do" tail is (b3)'s
|
|
7843
7928
|
// shape, guarded out so a zero-hit "what can a cat do" never gets misread
|
|
@@ -8490,7 +8575,19 @@ async function factReadBackReaders(memoryDir, query, envelope, miss, graph = nul
|
|
|
8490
8575
|
}
|
|
8491
8576
|
}
|
|
8492
8577
|
}
|
|
8493
|
-
|
|
8578
|
+
// An isa-shaped FIRST turn on a pristine store falls THROUGH to the isa
|
|
8579
|
+
// reader below rather than taking the empty-store bail-out: that reader's
|
|
8580
|
+
// body tolerates rows=[] end-to-end (every derived array is empty) and
|
|
8581
|
+
// lands on the specific "I don't know X at all yet — teach me" closer, so
|
|
8582
|
+
// the very first "is X a Y" no longer hits the generic grammar wall just
|
|
8583
|
+
// because nothing has been taught yet. The graph inherits-bridge above
|
|
8584
|
+
// already answers the code-entity direct/converse cases before this point.
|
|
8585
|
+
// A leading "there" subject is existential ("is there a class called X"),
|
|
8586
|
+
// which ISA_ASK_RE also matches but a LATER existence lane owns and answers
|
|
8587
|
+
// better — it keeps the bail-out, mirroring this block's own emptyIsAdj
|
|
8588
|
+
// "there" exclusion above. Every OTHER empty-store shape keeps the bail-out.
|
|
8589
|
+
const fallThroughIsa = qHedge.match(ISA_ASK_RE) || matchWhyIsa(q);
|
|
8590
|
+
if (!((fallThroughIsa && !/^there\b/i.test(fallThroughIsa[1].trim())) || CONFIRM_TAG_RE.test(q))) return null;
|
|
8494
8591
|
}
|
|
8495
8592
|
const isa = rows.filter((f) => ISA_PREDICATES.has(f.predicate));
|
|
8496
8593
|
const byTrust = (a, b) => b.trust - a.trust;
|
|
@@ -9961,6 +10058,14 @@ const STACCATO_LEAKED_CONNECTIVES = new Set(["and", "also", "so", "then", "now"]
|
|
|
9961
10058
|
* named `edithistory`) is never mistaken for the pronoun. */
|
|
9962
10059
|
const PRONOUN_IN_QUERY_RE = new RegExp(`\\b(?:${[...CONTEXT_WORDS].join("|")})\\b`, "i");
|
|
9963
10060
|
|
|
10061
|
+
/** The referring pronouns an EMBEDDED "what about X, <wh-clause>" swap replaces
|
|
10062
|
+
* — CONTEXT_WORDS (it/this/that/here) plus the personal pronouns
|
|
10063
|
+
* PRONOUN_IN_QUERY_RE deliberately omits (he/she/they/them): the embedded
|
|
10064
|
+
* clause's own subject/object, not a prior-turn antecedent, so a subject
|
|
10065
|
+
* pronoun like "he" that never appears in a code-graph query still has to be
|
|
10066
|
+
* swappable here. */
|
|
10067
|
+
const EMBEDDED_PRONOUN_RE = /\b(?:it|this|that|here|he|she|they|them)\b/i;
|
|
10068
|
+
|
|
9964
10069
|
/** DISCOURSE CONTINUATION: "what about X" carries the PRIOR
|
|
9965
10070
|
* turn's question shape across the turn boundary — re-asking it with X in place of
|
|
9966
10071
|
* the previous subject/object. Returns the reconstructed query (parsed like any
|
|
@@ -9971,6 +10076,32 @@ function discourseRewrite(query, last) {
|
|
|
9971
10076
|
let newSubj;
|
|
9972
10077
|
if (m) {
|
|
9973
10078
|
newSubj = m[1].trim();
|
|
10079
|
+
// An embedded question spliced into the "what about" subject ("what about
|
|
10080
|
+
// the store, what it do") must NEVER be substituted into the prior turn's
|
|
10081
|
+
// shape — that inherits the prior turn's DIRECTION onto a question asking
|
|
10082
|
+
// the opposite ("who uses store.mjs" then "…what it do" would answer "who
|
|
10083
|
+
// uses the store"). Split on the interior wh-clause and re-read the
|
|
10084
|
+
// remainder against a CLOSED micro-set; a clause outside it is an honest
|
|
10085
|
+
// miss, never the prior-turn substitution below. A comma NOT followed by a
|
|
10086
|
+
// wh-word ("what about the store, please") never matches and keeps its
|
|
10087
|
+
// ordinary swap.
|
|
10088
|
+
const embedded = newSubj.match(/^(.+?),\s*(what|who|which|where|how)\b\s*(.*)$/i);
|
|
10089
|
+
if (embedded) {
|
|
10090
|
+
const embSubj = embedded[1].trim();
|
|
10091
|
+
const wh = embedded[2].toLowerCase();
|
|
10092
|
+
const rest = embedded[3].trim();
|
|
10093
|
+
// "what [it/this/that/he/she] do(es)" → the module overview of the new
|
|
10094
|
+
// subject, which MODULE_ORIENT_RE serves verbatim.
|
|
10095
|
+
if (wh === "what" && /^(?:he|she|it|this|that)?\s*do(?:es)?$/i.test(rest)) {
|
|
10096
|
+
return `what does ${embSubj} do`;
|
|
10097
|
+
}
|
|
10098
|
+
// A wh-clause carrying its OWN pronoun ("what does it call") → swap that
|
|
10099
|
+
// pronoun for the new subject and ask the clause standalone.
|
|
10100
|
+
if (EMBEDDED_PRONOUN_RE.test(rest)) {
|
|
10101
|
+
return `${wh} ${rest.replace(EMBEDDED_PRONOUN_RE, () => embSubj)}`;
|
|
10102
|
+
}
|
|
10103
|
+
return null;
|
|
10104
|
+
}
|
|
9974
10105
|
} else {
|
|
9975
10106
|
const sm = String(query).match(STACCATO_SWAP_RE);
|
|
9976
10107
|
const cand = sm?.[1]?.trim();
|
|
@@ -11207,14 +11338,16 @@ async function planLaneAnswer(query, { memoryDir, planHolder, sessionId = "", ga
|
|
|
11207
11338
|
const wantsSolve = PLAN_SOLVE_RE.test(q);
|
|
11208
11339
|
const wantsLegal = LEGAL_MOVES_RE.test(q);
|
|
11209
11340
|
if (!wantsSolve && !wantsLegal) return null;
|
|
11341
|
+
if (wantsSolve) return solveHeldGoals({ memoryDir, planHolder, gameConfig });
|
|
11210
11342
|
|
|
11343
|
+
// "what moves are legal now" — one ply off the current board, no search.
|
|
11211
11344
|
let ctx;
|
|
11212
11345
|
try {
|
|
11213
11346
|
ctx = await loadPlanContext(memoryDir);
|
|
11214
11347
|
} catch (err) {
|
|
11215
11348
|
return { text: `I can't read the taught domain: ${err?.message ?? err}`, via: "plan", deduced: "plan a move sequence", note: "plan lane — domain load failed" };
|
|
11216
11349
|
}
|
|
11217
|
-
const { domain, state
|
|
11350
|
+
const { domain, state } = ctx;
|
|
11218
11351
|
if (!domain.actions.length) {
|
|
11219
11352
|
return {
|
|
11220
11353
|
text: `no action rules taught yet — teach the game first (e.g. "you can move a disk onto a peg").`,
|
|
@@ -11227,30 +11360,55 @@ async function planLaneAnswer(query, { memoryDir, planHolder, sessionId = "", ga
|
|
|
11227
11360
|
via: "plan", deduced: "plan a move sequence (no state yet)", note: "plan lane — honest decline: empty state",
|
|
11228
11361
|
};
|
|
11229
11362
|
}
|
|
11230
|
-
const { movesFromRules,
|
|
11231
|
-
|
|
11232
|
-
|
|
11233
|
-
|
|
11234
|
-
|
|
11235
|
-
|
|
11236
|
-
|
|
11237
|
-
if (err instanceof PlanBudgetError) {
|
|
11238
|
-
return { text: `too many possible moves to enumerate here (${err.message}) — narrow the classes involved.`, via: "plan", deduced: "list the legal moves (budget exceeded)", note: "plan lane — budget decline" };
|
|
11239
|
-
}
|
|
11240
|
-
throw err;
|
|
11241
|
-
}
|
|
11242
|
-
if (!moves.length) {
|
|
11243
|
-
return { text: "no legal moves from the current state.", via: "plan", deduced: "list the legal moves (none)", note: "plan lane — legal moves: none" };
|
|
11363
|
+
const { movesFromRules, PlanBudgetError } = await import("../domain/domain.mjs");
|
|
11364
|
+
let moves;
|
|
11365
|
+
try {
|
|
11366
|
+
moves = movesFromRules(state, domain, { scope: "taught" });
|
|
11367
|
+
} catch (err) {
|
|
11368
|
+
if (err instanceof PlanBudgetError) {
|
|
11369
|
+
return { text: `too many possible moves to enumerate here (${err.message}) — narrow the classes involved.`, via: "plan", deduced: "list the legal moves (budget exceeded)", note: "plan lane — budget decline" };
|
|
11244
11370
|
}
|
|
11245
|
-
|
|
11371
|
+
throw err;
|
|
11372
|
+
}
|
|
11373
|
+
if (!moves.length) {
|
|
11374
|
+
return { text: "no legal moves from the current state.", via: "plan", deduced: "list the legal moves (none)", note: "plan lane — legal moves: none" };
|
|
11375
|
+
}
|
|
11376
|
+
const lines = moves.map((m, i) => ` ${i + 1}. ${actionLabel(m.action.name, m.action.subject, m.action.target)}`);
|
|
11377
|
+
return {
|
|
11378
|
+
text: `${moves.length} legal move${moves.length === 1 ? "" : "s"} from here:\n${lines.join("\n")}`,
|
|
11379
|
+
via: "plan", deduced: "list the legal moves from the current state",
|
|
11380
|
+
note: "plan lane — movesFromRules over the current snapshot, one ply, no search",
|
|
11381
|
+
};
|
|
11382
|
+
}
|
|
11383
|
+
|
|
11384
|
+
/** Search the taught rules for a shortest sequence to the held goal(s) from the
|
|
11385
|
+
* CURRENT board fold (the newest @stepK snapshot, else the taught board).
|
|
11386
|
+
* Mints the plan onto planHolder.state and returns the plan-found reply on
|
|
11387
|
+
* success; on any missing precondition or an unreachable goal it returns the
|
|
11388
|
+
* matching honest decline and leaves planHolder.state untouched. Shared by the
|
|
11389
|
+
* plan lane's "solve it" and by the two drift sites that re-search after the
|
|
11390
|
+
* board moves under a live plan. */
|
|
11391
|
+
async function solveHeldGoals({ memoryDir, planHolder, gameConfig = DEFAULT_GAME_CONFIG }) {
|
|
11392
|
+
let ctx;
|
|
11393
|
+
try {
|
|
11394
|
+
ctx = await loadPlanContext(memoryDir);
|
|
11395
|
+
} catch (err) {
|
|
11396
|
+
return { text: `I can't read the taught domain: ${err?.message ?? err}`, via: "plan", deduced: "plan a move sequence", note: "plan lane — domain load failed" };
|
|
11397
|
+
}
|
|
11398
|
+
const { domain, state, factRows } = ctx;
|
|
11399
|
+
if (!domain.actions.length) {
|
|
11246
11400
|
return {
|
|
11247
|
-
text:
|
|
11248
|
-
via: "plan", deduced: "
|
|
11249
|
-
note: "plan lane — movesFromRules over the current snapshot, one ply, no search",
|
|
11401
|
+
text: `no action rules taught yet — teach the game first (e.g. "you can move a disk onto a peg").`,
|
|
11402
|
+
via: "plan", deduced: "plan a move sequence (no action rules yet)", note: "plan lane — honest decline: no action rules",
|
|
11250
11403
|
};
|
|
11251
11404
|
}
|
|
11252
|
-
|
|
11253
|
-
|
|
11405
|
+
if (!state.length) {
|
|
11406
|
+
return {
|
|
11407
|
+
text: `no current state taught yet — state the board first (e.g. "disk-1 rests on peg-a").`,
|
|
11408
|
+
via: "plan", deduced: "plan a move sequence (no state yet)", note: "plan lane — honest decline: empty state",
|
|
11409
|
+
};
|
|
11410
|
+
}
|
|
11411
|
+
const { movesFromRules, stateKeyFor, compileGoal, PlanBudgetError, maxSnapshotStep } = await import("../domain/domain.mjs");
|
|
11254
11412
|
if (!planHolder.state?.goals?.length) {
|
|
11255
11413
|
return {
|
|
11256
11414
|
text: `no goal set yet — teach one first (e.g. "the goal is that every disk rests on peg-c").`,
|
|
@@ -11367,8 +11525,13 @@ async function planLaneAnswer(query, { memoryDir, planHolder, sessionId = "", ga
|
|
|
11367
11525
|
// instead of an honest miss — see PLAN_WHY_SHORTEST_RE's own call site.
|
|
11368
11526
|
const becauseText = `you taught me the "${ruleNames}" rule${domain.actions.length === 1 ? "" : "s"}`
|
|
11369
11527
|
+ `${ordering.length ? ` and ${ordering.length} ordering fact${ordering.length === 1 ? "" : "s"}` : ""}.`;
|
|
11528
|
+
// The snapshot layer a fresh plan's step writes stack ABOVE: 0 on an
|
|
11529
|
+
// untouched board, K after a prior plan left @stepK rows standing. Without it
|
|
11530
|
+
// a replan's step 1 would write @step1 below the standing @stepK layer and be
|
|
11531
|
+
// read as stale by stateFromFacts (which prefers the newest snapshot).
|
|
11532
|
+
const stepBase = maxSnapshotStep(factRows, domain);
|
|
11370
11533
|
planHolder.state = {
|
|
11371
|
-
...planHolder.state, actions, states: found.states, stepGoals, cursor: 0, done: false, goalText, becauseText,
|
|
11534
|
+
...planHolder.state, actions, states: found.states, stepGoals, cursor: 0, done: false, goalText, becauseText, stepBase,
|
|
11372
11535
|
};
|
|
11373
11536
|
const moveLines = actions.map((a, i) => ` ${i + 1}. ${a.label}`);
|
|
11374
11537
|
// A piece with no taught position is an ASSUMPTION the plan silently makes
|
|
@@ -11404,23 +11567,27 @@ async function planLaneAnswer(query, { memoryDir, planHolder, sessionId = "", ga
|
|
|
11404
11567
|
/** Execute the active plan's next move: append the successor snapshot's rows
|
|
11405
11568
|
* as @stepK facts, advance the cursor, and on the final step re-read the
|
|
11406
11569
|
* store and confirm the goal from the WRITTEN facts (never assumed). */
|
|
11407
|
-
async function executePlanStep(planHolder, { memoryDir, sessionId = "" }) {
|
|
11570
|
+
async function executePlanStep(planHolder, { memoryDir, sessionId = "", gameConfig = DEFAULT_GAME_CONFIG }) {
|
|
11408
11571
|
const ps = planHolder.state;
|
|
11409
11572
|
const k = ps.cursor + 1;
|
|
11573
|
+
// The snapshot index the board rows are written under: it stacks above any
|
|
11574
|
+
// layer standing when the plan was minted (stepBase), while k stays the plan's
|
|
11575
|
+
// own 1-of-N move counter. On a fresh board stepBase is 0 and snap === k.
|
|
11576
|
+
const snap = (ps.stepBase ?? 0) + k;
|
|
11410
11577
|
const action = ps.actions[ps.cursor];
|
|
11411
11578
|
const rows = ps.states[k];
|
|
11412
11579
|
const { appendFact, loadMemory, readFactRows } = await import("../adapters/memory/core.mjs");
|
|
11413
11580
|
for (const row of rows) {
|
|
11414
11581
|
await appendFact(memoryDir, {
|
|
11415
|
-
subject: `${row.subject}@step${
|
|
11416
|
-
provenance: `plan:${sessionId || "chat"}:step${
|
|
11582
|
+
subject: `${row.subject}@step${snap}`, predicate: row.predicate, object: row.object,
|
|
11583
|
+
provenance: `plan:${sessionId || "chat"}:step${snap}`,
|
|
11417
11584
|
});
|
|
11418
11585
|
}
|
|
11419
11586
|
planHolder.state = { ...ps, cursor: k };
|
|
11420
11587
|
const boardLine = rows.map((r) => `${r.subject} ${predicatePhrase(r.predicate)} ${r.object}`).join("; ");
|
|
11421
11588
|
if (k < ps.actions.length) {
|
|
11422
11589
|
return {
|
|
11423
|
-
text: `moved — ${action.label} (step ${k} of ${ps.actions.length}). board@step${
|
|
11590
|
+
text: `moved — ${action.label} (step ${k} of ${ps.actions.length}). board@step${snap}: ${boardLine}`,
|
|
11424
11591
|
deduced: ps.stepGoals[k] ? ps.stepGoals[k] : `continue the plan (step ${k + 1} of ${ps.actions.length})`,
|
|
11425
11592
|
};
|
|
11426
11593
|
}
|
|
@@ -11432,12 +11599,31 @@ async function executePlanStep(planHolder, { memoryDir, sessionId = "" }) {
|
|
|
11432
11599
|
const domain = compileDomain(factRows, readRuleRows(payload));
|
|
11433
11600
|
const finalState = stateFromFacts(factRows, domain);
|
|
11434
11601
|
const holds = compileGoal(ps.goals, domain, { scope: "taught" })(finalState);
|
|
11602
|
+
const movedLine = `moved — ${action.label} (step ${k} of ${ps.actions.length}). board@step${snap}: ${boardLine}`;
|
|
11603
|
+
if (holds) {
|
|
11604
|
+
planHolder.state = { ...planHolder.state, done: true };
|
|
11605
|
+
return {
|
|
11606
|
+
text: `${movedLine}\n\ndone — ${ps.goalText} (checked against board@step${snap}'s written facts, not assumed).`,
|
|
11607
|
+
deduced: `goal reached — ${ps.goalText} (${k} of ${k} steps)`,
|
|
11608
|
+
};
|
|
11609
|
+
}
|
|
11610
|
+
// The final board doesn't reach the goal — the plan or the board drifted.
|
|
11611
|
+
// Before settling for the miss, re-search from the board as it now stands: a
|
|
11612
|
+
// found plan is disclosed and held (never a silent success), a miss keeps the
|
|
11613
|
+
// honest failure and names the failed replan.
|
|
11614
|
+
const replan = await solveHeldGoals({ memoryDir, planHolder, gameConfig });
|
|
11615
|
+
if (replan.plan) {
|
|
11616
|
+
const moves = replan.plan.actions.map((a, i) => `${i + 1}. ${a.label}`).join("; ");
|
|
11617
|
+
return {
|
|
11618
|
+
text: `${movedLine}\n\nBUT the goal does NOT hold against the written facts — the state drifted, so I replanned from board@step${snap}: ${moves}. Say "next" to continue.`,
|
|
11619
|
+
deduced: "plan finished but the goal check failed — replanned from the drifted board",
|
|
11620
|
+
};
|
|
11621
|
+
}
|
|
11622
|
+
const maxDepth = gameConfig?.planning?.maxDepth ?? DEFAULT_GAME_CONFIG.planning.maxDepth;
|
|
11435
11623
|
planHolder.state = { ...planHolder.state, done: true };
|
|
11436
11624
|
return {
|
|
11437
|
-
text:
|
|
11438
|
-
|
|
11439
|
-
: `moved — ${action.label} (step ${k} of ${ps.actions.length}). board@step${k}: ${boardLine}\n\nBUT the goal does NOT hold against the written facts — the plan or the state drifted; re-teach the state and solve again.`,
|
|
11440
|
-
deduced: holds ? `goal reached — ${ps.goalText} (${k} of ${k} steps)` : "plan finished but the goal check failed",
|
|
11625
|
+
text: `${movedLine}\n\nBUT the goal does NOT hold against the written facts — the plan or the state drifted; re-teach the state and solve again — I looked for a new plan from board@step${snap} and found none within ${maxDepth} moves.`,
|
|
11626
|
+
deduced: "plan finished but the goal check failed",
|
|
11441
11627
|
};
|
|
11442
11628
|
}
|
|
11443
11629
|
|
|
@@ -12055,6 +12241,16 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
12055
12241
|
const staccatoSwapMatch = String(query).match(STACCATO_SWAP_RE);
|
|
12056
12242
|
const isStaccatoSwap = !!(last?.query && staccatoSwapMatch && NAME_TOKEN_RE.test(staccatoSwapMatch[1]?.trim() || ""));
|
|
12057
12243
|
const isWhatAboutContinuation = !!(last?.query && WHAT_ABOUT_RE.test(String(query))) || isStaccatoSwap;
|
|
12244
|
+
// A bare vague-touch OPENER ("wat about validate", "tell me about store.mjs")
|
|
12245
|
+
// whose term resolves to a UNIQUE graph entity is a genuine describe request,
|
|
12246
|
+
// not small talk — defer past the conversational card so describeWrapperAnswer
|
|
12247
|
+
// (4d, below) serves its module/entity overview. Distinct from
|
|
12248
|
+
// isWhatAboutContinuation above, which needs a prior turn: this fires on the
|
|
12249
|
+
// FIRST turn too, and covers the "tell me about"/"explain" surfaces
|
|
12250
|
+
// vagueTouchTermOf reads. resolveEntity already declines on ambiguity, so an
|
|
12251
|
+
// ambiguous or unknown term ("wat about xyzzy") keeps today's orientation card.
|
|
12252
|
+
const vagueTouchTerm = graph ? vagueTouchTermOf(String(query)) : null;
|
|
12253
|
+
const isVagueTouchResolvable = !!(vagueTouchTerm && await resolveEntity(graph, vagueTouchTerm));
|
|
12058
12254
|
// Same exemption for "describe it"/"tell me about that" — needs the SAME
|
|
12059
12255
|
// deferral to reach describeWrapperAnswer's focus-aware pronoun resolution.
|
|
12060
12256
|
// Gated on an actual standing focus, same honest-decline discipline as
|
|
@@ -12166,7 +12362,7 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
12166
12362
|
} catch { /* leave false — the ordinary path decides */ }
|
|
12167
12363
|
}
|
|
12168
12364
|
}
|
|
12169
|
-
const conversationalCandidateBaseGate = !handled && miss && !envelope?.parsed && !isWhatAboutContinuation && !isDescribePronounContinuation && !isExplainTouch && !isStaccatoNegation && !isVagueRelationTouch && !isStaccatoComparative && !isStaccatoPronounNoFocus && !isPluralMembershipTeach && !isBareRelationalVerbTeach;
|
|
12365
|
+
const conversationalCandidateBaseGate = !handled && miss && !envelope?.parsed && !isWhatAboutContinuation && !isVagueTouchResolvable && !isDescribePronounContinuation && !isExplainTouch && !isStaccatoNegation && !isVagueRelationTouch && !isStaccatoComparative && !isStaccatoPronounNoFocus && !isPluralMembershipTeach && !isBareRelationalVerbTeach;
|
|
12170
12366
|
// A turn whose pronoun was bound to a vocabulary antecedent is PROVABLY a
|
|
12171
12367
|
// fact question ("can it bark" → "can dog bark") — never conversational,
|
|
12172
12368
|
// however short. Without this, the substituted 3-worder still trips
|
|
@@ -12218,7 +12414,8 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
12218
12414
|
// same divert-only-on-a-real-hit treatment as the reverse predicates
|
|
12219
12415
|
// above.
|
|
12220
12416
|
const capabilityAskShape = CAN_ASK_RE.test(gateQuery) || WHAT_CAN_DO_RE.test(gateQuery)
|
|
12221
|
-
|| DO_VERB_ASK_RE.test(gateQuery) || WHICH_KIND_CAN_RE.test(gateQuery) || WHAT_CAN_VERB_RE.test(gateQuery)
|
|
12417
|
+
|| DO_VERB_ASK_RE.test(gateQuery) || WHICH_KIND_CAN_RE.test(gateQuery) || WHAT_CAN_VERB_RE.test(gateQuery)
|
|
12418
|
+
|| WHAT_CANNOT_VERB_RE.test(gateQuery);
|
|
12222
12419
|
// A bare "who is/was <name>" (no relational tail) is as short as the
|
|
12223
12420
|
// vocabulary openers above and trips isConversational's word-count catch-all
|
|
12224
12421
|
// the same way — factReadBack's bare-who reader surfaces the person's stored
|
|
@@ -12557,7 +12754,7 @@ async function runAsk(query, { config, source, graph, focus, last, templates, me
|
|
|
12557
12754
|
// (4) #2 TEACH lane — a teach-shaped would-miss nothing above answered: route to
|
|
12558
12755
|
// memory, or say what CAN be remembered (LOUD), never the wall / a silent drop.
|
|
12559
12756
|
if (miss && recordMiss && via === "composed") {
|
|
12560
|
-
const taught = await teachLane(query, { memoryDir, sessionId, lexicon, cache, planHolder, graph });
|
|
12757
|
+
const taught = await teachLane(query, { memoryDir, sessionId, lexicon, cache, planHolder, graph, gameConfig });
|
|
12561
12758
|
if (taught) {
|
|
12562
12759
|
answer = taught.text; via = taught.via; recordMiss = taught.miss;
|
|
12563
12760
|
if (!taught.miss) dialogueLaneOverride = "teach";
|
|
@@ -14400,7 +14597,7 @@ export async function runTurn(input, { config, source = defaultSource, graph = n
|
|
|
14400
14597
|
if (memoryDir && PLAN_NEXT_RE.test(workingLine)
|
|
14401
14598
|
&& planHolder.state && !planHolder.state.done
|
|
14402
14599
|
&& Array.isArray(planHolder.state.actions) && planHolder.state.cursor < planHolder.state.actions.length) {
|
|
14403
|
-
const step = await executePlanStep(planHolder, { memoryDir, sessionId });
|
|
14600
|
+
const step = await executePlanStep(planHolder, { memoryDir, sessionId, gameConfig: resolvedGameConfig });
|
|
14404
14601
|
note(trace, `goal: ${step.deduced}`);
|
|
14405
14602
|
note(trace, "lane: PLAN NEXT — executed the active plan's next move as an @stepK snapshot write");
|
|
14406
14603
|
const stepTurn = plainTurn(workingLine, step.text, { via: "plan", focus });
|
|
@@ -256,9 +256,14 @@ export async function resolveExtensions(repoRoot, { configFile } = {}) {
|
|
|
256
256
|
*
|
|
257
257
|
* FAILURE-TOLERANT per bundle: one bad third-party pack's seedMemory throw is caught and
|
|
258
258
|
* recorded as `perBundle[name].error` while every other bundle still seeds normally.
|
|
259
|
-
* Returns `{ appended, skipped, total, perBundle: { name: {appended,skipped,total,error?} } }`.
|
|
260
|
-
|
|
259
|
+
* Returns `{ appended, skipped, total, perBundle: { name: {appended,skipped,total,error?} } }`.
|
|
260
|
+
*
|
|
261
|
+
* `opts.captureUnknownContext`/`opts.unknownContextLimit` (both optional) forward to
|
|
262
|
+
* every seedMemory call unchanged — the tmct.toml `[seed]` knob (toml-config.mjs)
|
|
263
|
+
* applies uniformly across whichever bundles are active, not per-bundle. */
|
|
264
|
+
export async function seedActiveCorpusEntries(repo, entries, opts = {}) {
|
|
261
265
|
const { seedMemory } = await import("../adapters/corpus/conceptnet.mjs");
|
|
266
|
+
const { captureUnknownContext, unknownContextLimit } = opts;
|
|
262
267
|
const perBundle = {};
|
|
263
268
|
let appended = 0;
|
|
264
269
|
let skipped = 0;
|
|
@@ -274,6 +279,8 @@ export async function seedActiveCorpusEntries(repo, entries) {
|
|
|
274
279
|
provenancePrefix: entry.provenancePrefix,
|
|
275
280
|
limit: entry.limit,
|
|
276
281
|
prefer: entry.prefer,
|
|
282
|
+
captureUnknownContext,
|
|
283
|
+
unknownContextLimit,
|
|
277
284
|
});
|
|
278
285
|
perBundle[name] = { appended: res.appended, skipped: res.skipped, total: res.total };
|
|
279
286
|
appended += res.appended;
|