@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
|
@@ -117,6 +117,26 @@ export function loadProgressLine(parts) {
|
|
|
117
117
|
: "loading the engine… " + mb(loaded) + " MB";
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
/**
|
|
121
|
+
* The "researched this session" panel's own reading of a settled research
|
|
122
|
+
* turn's answer — the passage it read and where it read it, straight off
|
|
123
|
+
* research.mjs's own `renderResearchAnswer` shape (`term — summary (source:
|
|
124
|
+
* research article "title", Simple English Wikipedia, CC BY-SA 4.0 — url)`),
|
|
125
|
+
* never a second fetch: the citation text is already the retrieved passage,
|
|
126
|
+
* this just pulls its pieces apart for display. Returns null on anything
|
|
127
|
+
* else (a miss, a status/stop reply, or text that doesn't open this way) —
|
|
128
|
+
* an honest "nothing to show" rather than a guessed passage.
|
|
129
|
+
*
|
|
130
|
+
* Self-contained, `.toString()`-splice safe — the same discipline every
|
|
131
|
+
* other pure export in this module holds.
|
|
132
|
+
*/
|
|
133
|
+
export function parseResearchAnswer(answer) {
|
|
134
|
+
const text = String(answer || "");
|
|
135
|
+
const m = /^(.*?) — ([\s\S]*?) \(source: research article "([^"]+)", Simple English Wikipedia, CC BY-SA 4\.0 — (\S+)\)/.exec(text);
|
|
136
|
+
if (!m) return null;
|
|
137
|
+
return { term: m[1], passage: m[2], title: m[3], url: m[4] };
|
|
138
|
+
}
|
|
139
|
+
|
|
120
140
|
/**
|
|
121
141
|
* The exported transcript as ONE Markdown document, in the SAME shape the
|
|
122
142
|
* Node CLI/TUI's own .tmct/session-<id>.md writes (session-log-format.mjs,
|
|
@@ -286,6 +306,24 @@ ${THEME_TOKENS_CSS}
|
|
|
286
306
|
.statsPanel .forget-btn:hover { color: var(--ink); }
|
|
287
307
|
.statsPanel .persist-note { color: var(--muted); font-size: .64rem; margin: .4rem 0 0; }
|
|
288
308
|
|
|
309
|
+
/* the "researched this session" panel: its own section under the memory
|
|
310
|
+
stats, filled from window.tmctChat.researchedFactRows() plus each
|
|
311
|
+
settled research turn's own answer text — the passage tmct actually
|
|
312
|
+
read, the article it read it from, and the facts that passage grounded.
|
|
313
|
+
A sibling section, not folded into #statsPanelStats — that div's own
|
|
314
|
+
re-render (renderStatsPanelInto) clears and rebuilds its children on
|
|
315
|
+
every turn, which would wipe this section's own history too if it lived
|
|
316
|
+
inside it. */
|
|
317
|
+
#researchedPanel:not(:empty) { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--line); }
|
|
318
|
+
.statsPanel .researched-item { margin: 0 0 .9rem; padding-bottom: .8rem; border-bottom: 1px dashed var(--line); }
|
|
319
|
+
.statsPanel .researched-item:last-child { border-bottom: none; padding-bottom: 0; margin-bottom: 0; }
|
|
320
|
+
.statsPanel .researched-title { display: block; color: var(--ink); font-weight: 600; }
|
|
321
|
+
.statsPanel .researched-passage { display: block; color: var(--muted); margin: .3rem 0; }
|
|
322
|
+
.statsPanel .researched-link { display: inline-block; margin: 0 0 .3rem; }
|
|
323
|
+
.statsPanel .researched-facts { margin: .3rem 0 0; padding-left: 1.1rem; }
|
|
324
|
+
.statsPanel .researched-facts li { margin: .12rem 0; }
|
|
325
|
+
.statsPanel .researched-none { color: var(--muted); font-style: italic; margin: .3rem 0 0; }
|
|
326
|
+
|
|
289
327
|
@media (max-width: 860px) {
|
|
290
328
|
.statsPanel { display: none; }
|
|
291
329
|
}
|
|
@@ -361,8 +399,9 @@ ${THEME_TOKENS_CSS}
|
|
|
361
399
|
</form>
|
|
362
400
|
<div class="statusline" id="status">loading the engine…</div>
|
|
363
401
|
</div>
|
|
364
|
-
<aside class="statsPanel" id="statsPanel" aria-label="This session's memory">
|
|
365
|
-
<p class="empty">loading memory stats…</p>
|
|
402
|
+
<aside class="statsPanel" id="statsPanel" aria-label="This session's memory and research">
|
|
403
|
+
<div id="statsPanelStats"><p class="empty">loading memory stats…</p></div>
|
|
404
|
+
<div id="researchedPanel"></div>
|
|
366
405
|
</aside>
|
|
367
406
|
<script src="./chat-browser.bundle.js"></script>
|
|
368
407
|
<script>
|
|
@@ -371,6 +410,7 @@ ${THEME_TOKENS_CSS}
|
|
|
371
410
|
const provBucketFor = ${provBucketFor.toString()};
|
|
372
411
|
const provenanceChipFor = ${provenanceChipFor.toString()};
|
|
373
412
|
const loadProgressLine = ${loadProgressLine.toString()};
|
|
413
|
+
const parseResearchAnswer = ${parseResearchAnswer.toString()};
|
|
374
414
|
const sessionLogTimeOfDay = ${sessionLogTimeOfDay.toString()};
|
|
375
415
|
const sessionLogHeaderMarkdown = ${sessionLogHeaderMarkdown.toString()};
|
|
376
416
|
const sessionLogTurnMarkdown = ${sessionLogTurnMarkdown.toString()};
|
|
@@ -390,7 +430,8 @@ ${THEME_TOKENS_CSS}
|
|
|
390
430
|
const inputEl = el("composerInput");
|
|
391
431
|
const sendBtn = el("composerSend");
|
|
392
432
|
const statusEl = el("status");
|
|
393
|
-
const statsPanelEl = el("
|
|
433
|
+
const statsPanelEl = el("statsPanelStats");
|
|
434
|
+
const researchedPanelEl = el("researchedPanel");
|
|
394
435
|
const wikiModeFieldset = el("wikiMode");
|
|
395
436
|
const wikiModeRadios = Array.prototype.slice.call(wikiModeFieldset.querySelectorAll('input[type="radio"]'));
|
|
396
437
|
const synthSliderEl = el("synthSlider");
|
|
@@ -708,6 +749,98 @@ ${THEME_TOKENS_CSS}
|
|
|
708
749
|
});
|
|
709
750
|
}
|
|
710
751
|
|
|
752
|
+
// ---- researched this session: what "research <topic>" has actually read
|
|
753
|
+
// and grounded so far — each entry pairs the passage a settled research
|
|
754
|
+
// turn's own answer cites (parseResearchAnswer, off the SAME "(source:
|
|
755
|
+
// research article ...)" text the chat bubble already shows) with the real
|
|
756
|
+
// facts that turn stored, read back through window.tmctChat.
|
|
757
|
+
// researchedFactRows(memoryDir) rather than re-deriving them from the
|
|
758
|
+
// answer text — the citation names WHERE tmct read, the fact rows name
|
|
759
|
+
// WHAT it kept, and this panel never invents either from the other.
|
|
760
|
+
const researchedEntries = [];
|
|
761
|
+
const researchedFactKeysSeen = new Set();
|
|
762
|
+
|
|
763
|
+
function renderResearchedPanel() {
|
|
764
|
+
researchedPanelEl.textContent = "";
|
|
765
|
+
researchedPanelEl.appendChild(Object.assign(document.createElement("h2"), { textContent: "researched this session" }));
|
|
766
|
+
if (!researchedEntries.length) {
|
|
767
|
+
const empty = document.createElement("p");
|
|
768
|
+
empty.className = "empty";
|
|
769
|
+
empty.textContent = 'nothing yet — ask it to "research <a topic>" and what it reads, with the passage and the facts it grounded, lands here.';
|
|
770
|
+
researchedPanelEl.appendChild(empty);
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
for (const entry of researchedEntries.slice(-8).reverse()) {
|
|
774
|
+
const item = document.createElement("div");
|
|
775
|
+
item.className = "researched-item";
|
|
776
|
+
const title = document.createElement("span");
|
|
777
|
+
title.className = "researched-title";
|
|
778
|
+
title.textContent = entry.title || "(untitled)";
|
|
779
|
+
item.appendChild(title);
|
|
780
|
+
if (entry.passage) {
|
|
781
|
+
const passage = document.createElement("span");
|
|
782
|
+
passage.className = "researched-passage";
|
|
783
|
+
passage.textContent = entry.passage;
|
|
784
|
+
item.appendChild(passage);
|
|
785
|
+
}
|
|
786
|
+
if (entry.url) {
|
|
787
|
+
const link = document.createElement("a");
|
|
788
|
+
link.className = "researched-link";
|
|
789
|
+
link.href = entry.url;
|
|
790
|
+
link.target = "_blank";
|
|
791
|
+
link.rel = "noopener noreferrer";
|
|
792
|
+
link.textContent = "source \\u2197";
|
|
793
|
+
item.appendChild(link);
|
|
794
|
+
}
|
|
795
|
+
if (entry.facts.length) {
|
|
796
|
+
const list = document.createElement("ul");
|
|
797
|
+
list.className = "researched-facts";
|
|
798
|
+
for (const fact of entry.facts) {
|
|
799
|
+
const li = document.createElement("li");
|
|
800
|
+
li.textContent = fact.subject + " " + fact.predicate + " " + fact.object;
|
|
801
|
+
list.appendChild(li);
|
|
802
|
+
}
|
|
803
|
+
item.appendChild(list);
|
|
804
|
+
} else {
|
|
805
|
+
const none = document.createElement("p");
|
|
806
|
+
none.className = "researched-none";
|
|
807
|
+
none.textContent = "no new fact grounded from this passage.";
|
|
808
|
+
item.appendChild(none);
|
|
809
|
+
}
|
|
810
|
+
researchedPanelEl.appendChild(item);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/** After a settled, non-miss research turn: read back the facts that turn
|
|
815
|
+
* actually stored (a set-diff against every research fact seen so far, so
|
|
816
|
+
* a later step never re-lists an earlier one's facts) and pair them with
|
|
817
|
+
* this turn's own cited passage. A turn that grounded nothing new (an
|
|
818
|
+
* empty article, or a re-fetch of an already-known one) still gets its
|
|
819
|
+
* own entry — the passage was still read, even where nothing new stuck. */
|
|
820
|
+
async function noteResearchLearned(result) {
|
|
821
|
+
if (result.research === undefined || !result.record || result.record.miss) return;
|
|
822
|
+
if (!window.tmctChat.researchedFactRows || !window.tmctChatSession) return;
|
|
823
|
+
let rows;
|
|
824
|
+
try { rows = await window.tmctChat.researchedFactRows(window.tmctChatSession.memoryDir); }
|
|
825
|
+
catch { return; }
|
|
826
|
+
const newFacts = [];
|
|
827
|
+
for (const row of rows) {
|
|
828
|
+
const key = row.subject + "|" + row.predicate + "|" + row.object;
|
|
829
|
+
if (researchedFactKeysSeen.has(key)) continue;
|
|
830
|
+
researchedFactKeysSeen.add(key);
|
|
831
|
+
newFacts.push(row);
|
|
832
|
+
}
|
|
833
|
+
const parsed = parseResearchAnswer(result.answer);
|
|
834
|
+
if (!parsed && !newFacts.length) return;
|
|
835
|
+
researchedEntries.push({
|
|
836
|
+
title: parsed ? parsed.title : "",
|
|
837
|
+
passage: parsed ? parsed.passage : "",
|
|
838
|
+
url: parsed ? parsed.url : "",
|
|
839
|
+
facts: newFacts,
|
|
840
|
+
});
|
|
841
|
+
renderResearchedPanel();
|
|
842
|
+
}
|
|
843
|
+
|
|
711
844
|
// "supplement" (typed /wiki supplement only) has no radio; the statusline
|
|
712
845
|
// still names it, read straight off the session's own liveReference getter
|
|
713
846
|
// rather than the last radio the page itself set.
|
|
@@ -791,6 +924,7 @@ ${THEME_TOKENS_CSS}
|
|
|
791
924
|
// that changed nothing costs at most one coalesced write.
|
|
792
925
|
if (result.record && result.record.via !== "command") scheduleSave();
|
|
793
926
|
await renderStatsPanel(); // a teach or learned-load turn grew this session's memory; a plain ask leaves it unchanged either way
|
|
927
|
+
await noteResearchLearned(result);
|
|
794
928
|
} catch (err) {
|
|
795
929
|
settleAssistantBubble(pendingRow,
|
|
796
930
|
"something went wrong answering that (" + (err && err.message ? err.message : err) + ") \\u2014 try rephrasing",
|
|
@@ -1018,6 +1152,18 @@ ${THEME_TOKENS_CSS}
|
|
|
1018
1152
|
addSystemLine("tmct \\u2014 the real engine, running in this page \\u2014 " + statsSummaryLine(stats, bandLabelFor)
|
|
1019
1153
|
+ "." + restoredNote + " Ask it something, or teach it a fact of your own.");
|
|
1020
1154
|
await renderStatsPanel(stats);
|
|
1155
|
+
// A restored session may already carry earlier research facts (they
|
|
1156
|
+
// persist with everything else this session taught) — seed the
|
|
1157
|
+
// seen-set from them so a later research turn only reports what's
|
|
1158
|
+
// actually new, without fabricating passages for a visit this page
|
|
1159
|
+
// was never open to read.
|
|
1160
|
+
if (window.tmctChat.researchedFactRows) {
|
|
1161
|
+
try {
|
|
1162
|
+
const existingResearch = await window.tmctChat.researchedFactRows(window.tmctChatSession.memoryDir);
|
|
1163
|
+
for (const row of existingResearch) researchedFactKeysSeen.add(row.subject + "|" + row.predicate + "|" + row.object);
|
|
1164
|
+
} catch { /* best-effort seeding only — a fresh session has none to seed */ }
|
|
1165
|
+
}
|
|
1166
|
+
renderResearchedPanel();
|
|
1021
1167
|
inputEl.placeholder = seedPayload ? 'try "what is a dog" or "list facts"' : window.tmctChat.vocabExampleHint(false);
|
|
1022
1168
|
renderStatus();
|
|
1023
1169
|
setBusy(false);
|