@polycode-projects/the-mechanical-code-talker 2.10.3 → 2.10.5
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 +66 -10
- package/bin/tmct.mjs +5 -2
- package/package.json +3 -1
- package/src/adapters/memory/core.mjs +8 -1
- package/src/domain/cli-verbs.mjs +2 -0
- package/src/domain/memory/trust.mjs +21 -2
- package/src/domain/sense-split.mjs +203 -0
- package/src/services/chat-page-viz.mjs +87 -19
- package/src/services/chat-session.mjs +12 -7
- package/src/services/chat.mjs +320 -30
- package/src/services/code-explorer-viz.mjs +16 -2
- package/src/services/extract-facts.mjs +293 -81
- package/src/services/fold.mjs +1 -1
- package/src/services/ingest-viz.mjs +388 -0
- package/src/services/ledger-viz.mjs +110 -0
- package/src/services/session-log-format.mjs +64 -0
- package/src/services/sessions.mjs +56 -22
- package/src/services/spider-fly-turn.mjs +54 -1
- package/src/services/spider-fly-viz.mjs +21 -19
- package/src/surfaces/web/chat-browser-entry.mjs +10 -6
- package/src/surfaces/web/ingest-browser-entry.mjs +126 -0
- package/src/surfaces/web/ledger-browser-entry.mjs +15 -2
- package/src/surfaces/web/memory-ask-browser.bundle.js +135 -126
- package/src/tools/definitions.mjs +14 -0
- package/src/tools/handlers/index.mjs +2 -0
- package/src/tools/handlers/tmct-ingest.mjs +43 -0
- package/src/tools/server.mjs +5 -2
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
// exist (both built earlier in that same script, for the embedded widget).
|
|
31
31
|
import { THEME_TOKENS_CSS, SERIF_STACK, MONO_STACK, escapeHtml } from "./viz-theme.mjs";
|
|
32
32
|
import { provBucketFor } from "./ledger-viz.mjs";
|
|
33
|
+
import { sessionLogTimeOfDay, sessionLogHeaderMarkdown, sessionLogTurnMarkdown } from "./session-log-format.mjs";
|
|
33
34
|
|
|
34
35
|
const DEFAULT_TITLE = "the-mechanical-code-talker — talk to it";
|
|
35
36
|
|
|
@@ -115,25 +116,37 @@ export function loadProgressLine(parts) {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
/**
|
|
118
|
-
* The exported transcript as
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
119
|
+
* The exported transcript as ONE Markdown document, in the SAME shape the
|
|
120
|
+
* Node CLI/TUI's own .tmct/session-<id>.md writes (session-log-format.mjs,
|
|
121
|
+
* spliced in beside this function below): a title naming the version and a
|
|
122
|
+
* short session id, one heading per turn at millisecond time-of-day
|
|
123
|
+
* precision, the question as a verbatim blockquote, the answer in a fenced
|
|
124
|
+
* block. No closing session-end line — unlike a CLI session's close(), an
|
|
125
|
+
* export can happen mid-conversation, before anything has actually ended.
|
|
124
126
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
+
* Reads the page's transcript MODEL (an array alternating { role: "you" |
|
|
128
|
+
* "tmct", text, chipTier, ts }, one entry per submit and per settled
|
|
129
|
+
* reply), never the DOM — the message column may virtualize long chats
|
|
130
|
+
* someday, and an export must still carry every turn.
|
|
131
|
+
*
|
|
132
|
+
* `headerMd`/`turnMd` are the injected session-log-format.mjs builders
|
|
133
|
+
* (spliced in as their own consts alongside this function) — injected
|
|
134
|
+
* rather than imported so this function stays `.toString()`-splice safe,
|
|
135
|
+
* the same discipline provenanceChipFor's injected `bucketFor` holds.
|
|
127
136
|
*/
|
|
128
|
-
export function transcriptMarkdown(turns, meta) {
|
|
137
|
+
export function transcriptMarkdown(turns, meta, headerMd, turnMd) {
|
|
129
138
|
const version = (meta && meta.version) || "dev";
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
const sessionId = (meta && meta.sessionId) || "";
|
|
140
|
+
const list = turns || [];
|
|
141
|
+
let doc = headerMd({ version: version, sessionId: sessionId, startedAt: list.length ? list[0].ts : Date.now() });
|
|
142
|
+
let turnNumber = 0;
|
|
143
|
+
for (let i = 0; i < list.length; i += 1) {
|
|
144
|
+
if (list[i].role !== "you") continue;
|
|
145
|
+
turnNumber += 1;
|
|
146
|
+
const reply = list[i + 1] && list[i + 1].role === "tmct" ? list[i + 1] : null;
|
|
147
|
+
doc += turnMd({ startedAt: list[i].ts, turnNumber: turnNumber, query: list[i].text, answer: reply ? reply.text : "" });
|
|
135
148
|
}
|
|
136
|
-
return
|
|
149
|
+
return doc;
|
|
137
150
|
}
|
|
138
151
|
|
|
139
152
|
/** The self-contained "talk to it" full-screen page. Pure — the same output
|
|
@@ -304,12 +317,14 @@ ${THEME_TOKENS_CSS}
|
|
|
304
317
|
<button type="submit" id="composerSend" aria-label="Send" disabled>→</button>
|
|
305
318
|
</div>
|
|
306
319
|
<div class="composer-tools">
|
|
307
|
-
<label class="liveLabel" title="Off by default. When on, a question nothing local can answer also asks en.wikipedia.org — two small requests per lookup, and the answer is cited (CC BY-SA).">
|
|
320
|
+
<label class="liveLabel" title="Off by default. When on, a question nothing local can answer also asks en.wikipedia.org — two small requests per lookup, and the answer is cited (CC BY-SA). Type /wiki supplement to also add a cited Wikipedia read-out under every grounded answer.">
|
|
308
321
|
<input type="checkbox" id="liveToggle" role="switch" aria-label="ask Wikipedia when I don't know">
|
|
309
322
|
<span class="toggle-track" aria-hidden="true"><span class="toggle-knob"></span></span>
|
|
310
323
|
<span>ask Wikipedia when I don’t know</span>
|
|
311
324
|
</label>
|
|
312
325
|
<span class="tool-cluster">
|
|
326
|
+
<button type="button" id="ingestFile" class="tool-btn" title="load a .txt/.md file and teach every fact it recognizes into this session">ingest file</button>
|
|
327
|
+
<input type="file" id="ingestInput" accept=".txt,.md,text/plain,text/markdown" hidden>
|
|
313
328
|
<button type="button" id="exportMd" class="tool-btn" title="download this conversation as Markdown">export .md</button>
|
|
314
329
|
<button type="button" id="exportFacts" class="tool-btn" title="download this session's facts as JSONL (the tmct extract shape, provenance included)">export facts</button>
|
|
315
330
|
<button type="button" id="printChat" class="tool-btn" title="print the whole conversation">print</button>
|
|
@@ -329,6 +344,9 @@ ${THEME_TOKENS_CSS}
|
|
|
329
344
|
const provBucketFor = ${provBucketFor.toString()};
|
|
330
345
|
const provenanceChipFor = ${provenanceChipFor.toString()};
|
|
331
346
|
const loadProgressLine = ${loadProgressLine.toString()};
|
|
347
|
+
const sessionLogTimeOfDay = ${sessionLogTimeOfDay.toString()};
|
|
348
|
+
const sessionLogHeaderMarkdown = ${sessionLogHeaderMarkdown.toString()};
|
|
349
|
+
const sessionLogTurnMarkdown = ${sessionLogTurnMarkdown.toString()};
|
|
332
350
|
const transcriptMarkdown = ${transcriptMarkdown.toString()};
|
|
333
351
|
const el = (id) => document.getElementById(id);
|
|
334
352
|
|
|
@@ -763,8 +781,14 @@ ${THEME_TOKENS_CSS}
|
|
|
763
781
|
window.tmctChatSession.turn(q)
|
|
764
782
|
.then((result) => {
|
|
765
783
|
settleAssistantBubble(pendingRow, result.answer, result.record);
|
|
766
|
-
|
|
767
|
-
|
|
784
|
+
// Persist on ANY store write, not just a teach turn: a learn-on-miss
|
|
785
|
+
// load (a child pack, a reference or live-Wikipedia article) and its
|
|
786
|
+
// auto-synthesis also append facts, and those were lost on reload when
|
|
787
|
+
// only via==="assert" saved. Commands write nothing, so they stay out.
|
|
788
|
+
// The save is debounced, so a read-through that changed nothing costs
|
|
789
|
+
// at most one coalesced write.
|
|
790
|
+
if (result.record && result.record.via !== "command") scheduleSave();
|
|
791
|
+
return renderStatsPanel(); // a teach or learned-load turn grew this session's memory; a plain ask leaves it unchanged either way
|
|
768
792
|
})
|
|
769
793
|
.catch((err) => settleAssistantBubble(pendingRow,
|
|
770
794
|
"something went wrong answering that (" + (err && err.message ? err.message : err) + ") \\u2014 try rephrasing",
|
|
@@ -791,7 +815,8 @@ ${THEME_TOKENS_CSS}
|
|
|
791
815
|
// @media print stylesheet above to un-pin the message column so every
|
|
792
816
|
// turn reaches paper.
|
|
793
817
|
el("exportMd").addEventListener("click", () => {
|
|
794
|
-
const
|
|
818
|
+
const sessionId = (window.tmctChatSession && window.tmctChatSession.sessionId) || "";
|
|
819
|
+
const md = transcriptMarkdown(transcript, { version: siteVersion, sessionId: sessionId }, sessionLogHeaderMarkdown, sessionLogTurnMarkdown);
|
|
795
820
|
const blob = new Blob([md], { type: "text/markdown" });
|
|
796
821
|
const url = URL.createObjectURL(blob);
|
|
797
822
|
const link = document.createElement("a");
|
|
@@ -829,6 +854,49 @@ ${THEME_TOKENS_CSS}
|
|
|
829
854
|
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
830
855
|
});
|
|
831
856
|
|
|
857
|
+
// "ingest file" feeds a whole .txt/.md through the SAME session, one
|
|
858
|
+
// sentence at a time (window.tmctChat.splitSentences, then session.turn),
|
|
859
|
+
// teaching every sentence the recognizer grounds and skipping the rest
|
|
860
|
+
// honestly — the same pipeline the ingest page runs, reaching the chat's own
|
|
861
|
+
// memory so the taught facts answer questions straight away.
|
|
862
|
+
el("ingestFile").addEventListener("click", () => el("ingestInput").click());
|
|
863
|
+
el("ingestInput").addEventListener("change", async (e) => {
|
|
864
|
+
const file = e.target.files && e.target.files[0];
|
|
865
|
+
e.target.value = "";
|
|
866
|
+
const session = window.tmctChatSession;
|
|
867
|
+
if (!file || busy || !session || !window.tmctChat.splitSentences) return;
|
|
868
|
+
let text;
|
|
869
|
+
try {
|
|
870
|
+
text = await file.text();
|
|
871
|
+
} catch (err) {
|
|
872
|
+
addSystemLine("couldn't read that file (" + (err && err.message ? err.message : err) + ").");
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
const sentences = window.tmctChat.splitSentences(text);
|
|
876
|
+
if (!sentences.length) { addSystemLine("nothing to ingest in " + file.name + "."); return; }
|
|
877
|
+
setBusy(true);
|
|
878
|
+
statusEl.textContent = "ingesting " + file.name + "\\u2026";
|
|
879
|
+
let grounded = 0;
|
|
880
|
+
try {
|
|
881
|
+
for (const sentence of sentences) {
|
|
882
|
+
const result = await session.turn(sentence);
|
|
883
|
+
if (result.record && result.record.via === "assert" && !result.record.miss) grounded += 1;
|
|
884
|
+
}
|
|
885
|
+
} catch (err) {
|
|
886
|
+
addSystemLine("something went wrong ingesting " + file.name + " (" + (err && err.message ? err.message : err) + ").");
|
|
887
|
+
}
|
|
888
|
+
if (grounded) scheduleSave();
|
|
889
|
+
const skipped = sentences.length - grounded;
|
|
890
|
+
addSystemLine("ingested " + file.name + " \\u2014 " + sentences.length + " sentence"
|
|
891
|
+
+ (sentences.length === 1 ? "" : "s") + " read, " + grounded + " fact"
|
|
892
|
+
+ (grounded === 1 ? "" : "s") + " added"
|
|
893
|
+
+ (skipped ? ", " + skipped + " skipped (not a recognized fact shape)" : "") + ".");
|
|
894
|
+
await renderStatsPanel();
|
|
895
|
+
renderStatus();
|
|
896
|
+
setBusy(false);
|
|
897
|
+
inputEl.focus();
|
|
898
|
+
});
|
|
899
|
+
|
|
832
900
|
// "reset to seed" is the full re-initialisation: drop the persisted payload
|
|
833
901
|
// outright and reload, so boot re-seeds from the page's shipped seed as if on
|
|
834
902
|
// a first visit. Harder than "forget everything", which only swaps the live
|
|
@@ -28,6 +28,7 @@ import * as defaultSource from "../adapters/source.mjs";
|
|
|
28
28
|
import { resolveExtensions, mergedLexiconExtra } from "./extensions.mjs";
|
|
29
29
|
import { runTurn, hasSeededVocabulary, vocabExampleHint } from "./chat.mjs";
|
|
30
30
|
import { resolveGameConfig } from "../domain/game-config.mjs";
|
|
31
|
+
import { sessionLogHeaderMarkdown, sessionLogTurnMarkdown, sessionLogEndMarkdown } from "./session-log-format.mjs";
|
|
31
32
|
|
|
32
33
|
/** Where session logs live, relative to the target repo. `.tmct/` is the repo's
|
|
33
34
|
* one artifact directory (gitignored, machine-local) — flip this single constant
|
|
@@ -243,7 +244,7 @@ export async function createSession({
|
|
|
243
244
|
const sessionsDir = join(repo, SESSIONS_DIR_REL);
|
|
244
245
|
await mkdir(logDir, { recursive: true });
|
|
245
246
|
await mkdir(sessionsDir, { recursive: true });
|
|
246
|
-
const logFile = join(logDir, `session-${sessionId}.
|
|
247
|
+
const logFile = join(logDir, `session-${sessionId}.md`);
|
|
247
248
|
const sidecarFile = join(sessionsDir, `session-${sessionId}.jsonl`);
|
|
248
249
|
const stream = createWriteStream(logFile, { flags: "a" });
|
|
249
250
|
const sidecar = createWriteStream(sidecarFile, { flags: "a" });
|
|
@@ -255,7 +256,7 @@ export async function createSession({
|
|
|
255
256
|
const writeSidecar = (obj) => flush(sidecar, JSON.stringify(obj) + "\n");
|
|
256
257
|
|
|
257
258
|
const startIso = new Date().toISOString();
|
|
258
|
-
await writeLog(
|
|
259
|
+
await writeLog(sessionLogHeaderMarkdown({ version, sessionId, startedAt: startIso, repo }));
|
|
259
260
|
await writeSidecar({ type: "session", id: sessionId, started: startIso, repo, tmctVersion: version });
|
|
260
261
|
|
|
261
262
|
// Read-time graph upsert (sessions.mjs): after every turn, the session becomes /
|
|
@@ -363,14 +364,14 @@ export async function createSession({
|
|
|
363
364
|
} catch (e) {
|
|
364
365
|
const ts = new Date().toISOString();
|
|
365
366
|
const message = e instanceof Error ? e.message : String(e);
|
|
366
|
-
await writeLog(
|
|
367
|
+
await writeLog(sessionLogTurnMarkdown({ startedAt: ts, turnNumber: turns + 1, query: line, answer: `error: ${message}` }));
|
|
367
368
|
const errorRecord = { type: "error", ts, query: line, error: message };
|
|
368
369
|
await writeSidecar(errorRecord);
|
|
369
370
|
turnRecords.push(errorRecord);
|
|
370
371
|
turns += 1;
|
|
371
372
|
return { answer: `Something went wrong answering that (${message}). Try rephrasing, or /help.`, end: false, prompt: promptFor(focus) };
|
|
372
373
|
}
|
|
373
|
-
const { answer,
|
|
374
|
+
const { answer, record, focus: nextFocus, last: nextLast, end, narrate: nextNarrate, liveReference: nextLiveReference } = result;
|
|
374
375
|
focus = nextFocus;
|
|
375
376
|
last = nextLast;
|
|
376
377
|
if ("planState" in result) planState = result.planState;
|
|
@@ -378,8 +379,10 @@ export async function createSession({
|
|
|
378
379
|
// same way a focus update does — apply them to this handle's
|
|
379
380
|
// session-scoped state.
|
|
380
381
|
if (typeof nextNarrate === "boolean") narrateOn = nextNarrate;
|
|
381
|
-
|
|
382
|
-
|
|
382
|
+
// tri-state: false (off), true (rescue on a miss), or "supplement" (also
|
|
383
|
+
// append a cited read-out under every grounded answer).
|
|
384
|
+
if (typeof nextLiveReference === "boolean" || nextLiveReference === "supplement") liveReferenceOn = nextLiveReference;
|
|
385
|
+
await writeLog(sessionLogTurnMarkdown({ startedAt: record.ts, turnNumber: turns + 1, query: line, answer }));
|
|
383
386
|
await writeSidecar(record);
|
|
384
387
|
turnRecords.push(record);
|
|
385
388
|
// One telemetry line per dispatched turn (OFF by default → no-op). query.raw is
|
|
@@ -401,7 +404,9 @@ export async function createSession({
|
|
|
401
404
|
if (closed) return;
|
|
402
405
|
closed = true;
|
|
403
406
|
const endIso = new Date().toISOString();
|
|
404
|
-
|
|
407
|
+
const closingTurnNumber = turns + 1;
|
|
408
|
+
await writeLog(sessionLogTurnMarkdown({ startedAt: endIso, turnNumber: closingTurnNumber, query: "/exit", answer: "" }));
|
|
409
|
+
await writeLog(sessionLogEndMarkdown({ endedAt: endIso, turnCount: closingTurnNumber }));
|
|
405
410
|
await writeSidecar({ type: "end", ts: endIso });
|
|
406
411
|
await upsertGraph(endIso);
|
|
407
412
|
await new Promise((resolve) => stream.end(resolve));
|