Add full shell, replaceable Omics adapter and bilingual interaction

Implement approved specification #32 and tickets #33-#37. Keep host authentication server-verified and pin session interaction language. Compile scoped base selectors for browser compatibility and retain full gutters during CSS pruning.
This commit is contained in:
Codex
2026-09-13 14:26:39 +02:00
parent 2d1b714ebe
commit d8a29bfbdd
207 changed files with 8570 additions and 2164 deletions
@@ -1,6 +1,7 @@
const test = require("node:test");
const assert = require("node:assert/strict");
const { installRepairGate } = require("../memory/repair.js");
const { gateText } = require("../core/locale.js");
function gate({ answers, fail = false, resume = false }) {
let tool;
@@ -8,6 +9,8 @@ function gate({ answers, fail = false, resume = false }) {
const repair = { repair_id: "receipt", choice: null, status: "proposed", can_apply: true,
saved: false, indexed: false, options: [{ id: "fix", content: { detail: "Correction" } }] };
installRepairGate({ registerTool: def => { tool = def; } }, {
languageFor: () => "en",
gateText,
workflow: { activate() {}, phase: () => ({ id: "F4" }) },
memory: { execute: (_ctx, args) => {
calls.push(args);
@@ -208,8 +208,8 @@ test("buildArtifactGate derives approve/reject options from action.kind", () =>
action: { kind: "approve_reject" },
});
assert.deepEqual(w.options, [
{ id: "approve", label: "Salva e procedi", recommended: true },
{ id: "reject", label: "Rifiuta" },
{ id: "approve", label: "Save and proceed", label_i18n: "Save and proceed", recommended: true },
{ id: "reject", label: "Reject", label_i18n: "Reject" },
]);
});
@@ -217,7 +217,7 @@ test("buildArtifactGate: confirm -> single approve; view_only -> no options", ()
const confirm = buildArtifactGate({
id: "u1", phase: "F1", title: "t", artifact: { kind: "phase", data: {} }, action: { kind: "confirm" },
});
assert.deepEqual(confirm.options, [{ id: "approve", label: "Salva e procedi", recommended: true }]);
assert.deepEqual(confirm.options, [{ id: "approve", label: "Save and proceed", label_i18n: "Save and proceed", recommended: true }]);
const viewOnly = buildArtifactGate({
id: "u1", phase: "F1", title: "t", artifact: { kind: "phase", data: {} }, action: { kind: "view_only" },
});
@@ -214,7 +214,7 @@ test("appendLedgerSection appends only the phase's substantive decisions", () =>
const out = appendLedgerSection(data, decisions, ["concept_clarified", "ambiguity_open"]);
assert.equal(out.sections.length, 2);
const ledger = out.sections[1];
assert.equal(ledger.title, "Decisioni registrate in questa fase (dal ledger)");
assert.equal(ledger.title, "Decisions recorded in this phase (from the ledger)");
assert.equal(ledger.items.length, 1);
assert.equal(ledger.items[0].label, "concept_clarified: ablazione");
assert.equal(ledger.items[0].value, "solo transcatetere");
@@ -18,7 +18,9 @@ if (typeof globalThis.require === "undefined") {
}
const shell = { current: () => "" };
cp.execFileSync = (file, args, opts) => shell.current(file, args, opts);
cp.execFileSync = (file, args, opts) => args[1] === "ensure-interaction-language"
? JSON.stringify({ interaction_language: "it", workspace_language: "it" })
: shell.current(file, args, opts);
const META = JSON.stringify({
max_phase: 8,
@@ -23,7 +23,9 @@ if (typeof globalThis.require === "undefined") {
// swap `activeStub` instead of clobbering the dispatcher. Each test's existing
// `cp.execFileSync = fn` / restore-in-finally then Just Works.
let activeStub = cp.execFileSync;
const dispatcher = (...args) => activeStub(...args);
const dispatcher = (...args) => args[1]?.[1] === "ensure-interaction-language"
? JSON.stringify({ interaction_language: "it", workspace_language: "it" })
: activeStub(...args);
Object.defineProperty(cp, "execFileSync", {
configurable: true,
get: () => dispatcher,
@@ -30,6 +30,8 @@ async function loadGate() {
function phase8Stub(calls) {
return (_file, args) => {
calls.push(args);
if (args[0] === "session" && args[1] === "ensure-interaction-language")
return JSON.stringify({ interaction_language: "it", workspace_language: "it" });
if (args[0] === "phase" && args[1] === "meta") {
return JSON.stringify({
max_phase: 8,
@@ -0,0 +1,104 @@
const test = require("node:test");
const assert = require("node:assert/strict");
const cp = require("node:child_process");
const { createRequire } = require("node:module");
const path = require("node:path");
const { createFakePi } = require("./fake_pi_runtime.js");
const GATE = path.join(__dirname, "..", "..", "tht-gate.js");
if (!globalThis.require) globalThis.require = createRequire(GATE);
let activeStub = cp.execFileSync;
const dispatcher = (...args) => activeStub(...args);
Object.defineProperty(cp, "execFileSync", {
configurable: true, get: () => dispatcher, set: fn => { activeStub = fn; },
});
for (const [language, workspaceLanguage, entry, title, yes, no] of [
["en", "it", "/riprendi-sessione s1", "Generate a datamart?", "Yes, generate the datamart", "No, skip the datamart"],
["it", "en", '/nuova-domanda "Pazienti"', "Vuoi generare un datamart?", "Sì, genera il datamart", "No, salta il datamart"],
["fr-FR", "it", "/riprendi-sessione s1", "Generate a datamart?", "Yes, generate the datamart", "No, skip the datamart"],
]) test(`manifest language ${language} governs every model turn and generated gate chrome`, async () => {
const previous = { exec: cp.execFileSync, session: process.env.THT_SESSION,
language: process.env.THT_INTERACTION_LANGUAGE, profile: process.env.THT_PROFILE };
process.env.THT_SESSION = "s1";
process.env.THT_INTERACTION_LANGUAGE = workspaceLanguage; // stale launch hints have no authority
process.env.THT_PROFILE = "server";
cp.execFileSync = (_file, args) => {
if (args[0] === "session" && args[1] === "ensure-interaction-language")
return JSON.stringify({ id: "s1", interaction_language: language, workspace_language: workspaceLanguage });
if (args[0] === "session" && args[1] === "retrieval-pack") return "Pazienti: età > 65";
if (args[0] === "phase" && args[1] === "meta")
return JSON.stringify({ max_phase: 8, phases: [{ num: 8, id: "F8", name: "datamart",
emits: ["datamart_requested", "datamart_declined"] }] });
if (args[0] === "phase" && args[1] === "show") return "Fase corrente: 8\n";
return "";
};
try {
const { default: installGate } = await import(GATE);
const { pi, ctx, tools } = createFakePi();
ctx.cwd = "/nonexistent-language-test";
installGate(pi);
await pi.emit("input", { text: entry, source: "rpc" });
for (let turn = 0; turn < 2; turn++) {
const injected = await pi.emit("before_agent_start", { systemPrompt: "BASE" });
assert.ok(injected.systemPrompt.includes(`interaction_language=${language}`));
assert.ok(injected.systemPrompt.includes(`workspace_language=${workspaceLanguage}`));
assert.match(injected.systemPrompt, /reviewer questions, explanations, and choices/);
}
let widget;
ctx.ui.input = async title => {
widget = JSON.parse(title);
return JSON.stringify({ id: widget.id, choices: ["skip"] });
};
await tools.get("reviewer_datamart").def.execute("call", { session: "s1" }, null, null, ctx);
assert.equal(widget.title, title);
assert.equal(widget.title_i18n, "Generate a datamart?");
assert.deepEqual(widget.options.map(o => [o.id, o.label]), [
["generate", yes], ["skip", no],
]);
} finally {
cp.execFileSync = previous.exec;
for (const [key, value] of [["THT_SESSION", previous.session],
["THT_INTERACTION_LANGUAGE", previous.language], ["THT_PROFILE", previous.profile]]) {
if (value === undefined) delete process.env[key]; else process.env[key] = value;
}
}
});
test("an unavailable manifest blocks model startup instead of trusting a launch hint", async () => {
const originalExec = cp.execFileSync;
const previousSession = process.env.THT_SESSION;
process.env.THT_SESSION = "unavailable";
cp.execFileSync = () => { throw new Error("storage unavailable"); };
try {
const { default: installGate } = await import(GATE);
const { pi, ctx } = createFakePi();
ctx.cwd = "/nonexistent-language-test";
installGate(pi);
await pi.emit("input", { text: "/riprendi-sessione unavailable", source: "rpc" });
await assert.rejects(pi.emit("before_agent_start", { systemPrompt: "BASE" }), /storage unavailable/);
} finally {
cp.execFileSync = originalExec;
if (previousSession === undefined) delete process.env.THT_SESSION;
else process.env.THT_SESSION = previousSession;
}
});
test("artifact and join chrome localize while SQL, decisions and generated text remain verbatim", () => {
const { buildArtifactGate, buildJoinReviewRequest } = require("../core/builders.js");
const artifact = { kind: "sql", data: "SELECT nome FROM pazienti WHERE stato = 'attivo'" };
const options = [{ id: "join-1", label: "Collega pazienti", detail: "p.id = v.id" }];
for (const [locale, save, reject, next] of [
["en", "Save and proceed", "Reject", "Continue"],
["it", "Salva e procedi", "Rifiuta", "Continua"],
["fr", "Save and proceed", "Reject", "Continue"],
]) {
const gate = buildArtifactGate({ id: "u1", phase: "F7", title: "SQL finale",
artifact, action: { kind: "approve_reject" }, locale });
assert.deepEqual(gate.options.map(o => [o.id, o.label]), [["approve", save], ["reject", reject]]);
assert.deepEqual(gate.artifact, artifact);
assert.equal(gate.title, "SQL finale");
const joins = buildJoinReviewRequest({ id: "u2", phase: "F4", title: "Join", options, locale });
assert.equal(joins.confirm_label, next);
assert.deepEqual(joins.options, options);
}
});
@@ -36,6 +36,8 @@ const JOIN_OPTIONS = [
function shellStub(calls, inputs = []) {
return (_file, args, options = {}) => {
calls.push(args.join(" "));
if (args[0] === "session" && args[1] === "ensure-interaction-language")
return JSON.stringify({ interaction_language: "en", workspace_language: "it" });
if (options.input) inputs.push(options.input);
if (args[0] === "phase" && args[1] === "meta") {
return JSON.stringify({ phases: [{ num: 4, id: "F4" }] });
@@ -1,6 +1,7 @@
const test = require("node:test");
const assert = require("node:assert/strict");
const { createMemoryGate } = require("../memory/index.js");
const { gateText } = require("../core/locale.js");
const { createFakePi } = require("./fake_pi_runtime.js");
function setup({ phase = 8, response, failure = null } = {}) {
@@ -8,6 +9,8 @@ function setup({ phase = 8, response, failure = null } = {}) {
const calls = [];
const summary = { summary_id: "summary", items: [{ id: "rule", card: { subject: "Rule" } }] };
createMemoryGate({
languageFor: () => "en",
gateText,
workflow: { activate() {}, phase: () => ({ number: phase, id: "F" + phase }),
close: () => { calls.push("close"); return "closed"; } },
memory: { execute: () => JSON.stringify(summary),
@@ -1,6 +1,7 @@
const test = require("node:test");
const assert = require("node:assert");
const { createMemoryGate } = require("../memory/index.js");
const { gateText } = require("../core/locale.js");
const { buildMultiselectRequest } = require("../core/builders.js");
const { isReserved } = require("../core/reserved-labels.mjs");
const { createFakePi } = require("./fake_pi_runtime.js");
@@ -43,7 +44,7 @@ const MEMORY_OPTIONS = [
];
function setupRecall(choices) {
function setupRecall(choices, language = "it") {
const { pi, tools, ctx } = createFakePi();
const calls = [];
let descriptor;
@@ -53,6 +54,8 @@ function setupRecall(choices) {
return JSON.stringify({ id: descriptor.id, choices });
};
const memoryGate = createMemoryGate({
languageFor: () => language,
gateText,
workflow: {
activate: () => calls.push(["activate"]),
phase: () => ({ number: 8, id: "F8" }),
@@ -117,6 +120,19 @@ test("the Memory facade normalizes search hits and applies only the selected F2
assert.match(result.content[0].text, /1 decisioni.*La fase resta aperta/s);
});
test("English Memory chrome preserves Italian content and carries UI translation keys", async () => {
const { ctx, memoryGate, descriptor } = setupRecall(["first"], "en");
await memoryGate.reviewRecall(ctx, {
session: "s1", title: "Memory", options: MEMORY_OPTIONS, allow_empty: true, advance: false,
}, "F2");
assert.equal(descriptor().title, "Select memories to apply to the question");
assert.equal(descriptor().confirm_label, "Apply selected memories");
assert.equal(descriptor().confirm_label_i18n, "Apply selected memories");
assert.equal(descriptor().options[0].label, "Regola paziente attivo");
assert.equal(descriptor().options[0].detail, MEMORY_OPTIONS[0].description);
assert.equal(descriptor().options[0].label_i18n, undefined);
});
test("authoritative UUID card identities survive normalization into the reviewer widget", async () => {
const { ctx, memoryGate, descriptor } = setupRecall(["first"]);
const id = "mem-11111111-1111-4111-8111-111111111111";
@@ -26,7 +26,7 @@ case "$1 $2" in
"phase show") echo "Fase corrente: ${phase}";;
"phase meta") echo '{"max_phase":8,"phases":[{"num":2,"id":"F2","emits":[]},{"num":8,"id":"F8","emits":[]}]}';;
"memory summary") echo '{"summary_id":"saved-review","reviewed":true}';;
"session show") echo '{"status":"${status}"}';;
"session show"|"session ensure-interaction-language") echo '{"id":"s1","status":"${status}","interaction_language":"it","workspace_language":"it"}';;
*) echo "OK";;
esac
`;
@@ -11,12 +11,14 @@ test("con THT_SESSION il kickoff usa l'id fornito e NON crea la sessione", async
const bin = path.join(tmp, "bin");
fs.mkdirSync(bin);
const tht = path.join(bin, "tht");
fs.writeFileSync(tht, "#!/bin/sh\nexit 1\n", { mode: 0o755 });
fs.writeFileSync(tht, '#!/bin/sh\nif [ "$2" = "ensure-interaction-language" ]; then\n' +
'echo \'{"interaction_language":"it","workspace_language":"it"}\'\nelse\nexit 1\nfi\n', { mode: 0o755 });
const oldPath = process.env.PATH;
process.env.PATH = bin + ":" + oldPath;
process.env.THT_SESSION = "2026-06-27-100000-test";
try {
const { pi, ctx } = createFakePi();
ctx.cwd = tmp;
installGate(pi);
await pi.emit("input", { source: "rpc", text: '/nuova-domanda "x"' });
const injected = await pi.emit("before_agent_start", { systemPrompt: "" });
@@ -56,7 +58,9 @@ test("con retrieval pack persistito lo inietta senza chiedere tool call ridondan
fs.writeFileSync(
tht,
"#!/bin/sh\n" +
"printf \"# Retrieval pack\\n\\nTABELLA_CANDIDATA\\n\"\n",
'if [ "$2" = "ensure-interaction-language" ]; then\n' +
'echo \'{"interaction_language":"it","workspace_language":"it"}\'\nelse\n' +
"printf \"# Retrieval pack\\n\\nTABELLA_CANDIDATA\\n\"\nfi\n",
{ mode: 0o755 },
);
const oldPath = process.env.PATH;
@@ -1,5 +1,15 @@
const test = require("node:test");
const assert = require("node:assert");
const cp = require("node:child_process");
const { syncBuiltinESMExports } = require("node:module");
const originalExec = cp.execFileSync;
cp.execFileSync = (_file, args) => {
if (args[0] === "session" && args[1] === "ensure-interaction-language")
return JSON.stringify({ interaction_language: "it", workspace_language: "it" });
throw new Error("No retrieval pack in this fixture");
};
syncBuiltinESMExports();
test.after(() => { cp.execFileSync = originalExec; syncBuiltinESMExports(); });
const { createFakePi } = require("./fake_pi_runtime.js");
const installGate = require("../../tht-gate.js").default ?? require("../../tht-gate.js");
@@ -21,5 +21,5 @@ test("no-limbo: undefined (cancel) ri-presenta lo stesso widget", async () => {
const resp = await emitAndWait(ctx, descriptor);
assert.deepEqual(resp, { id: "u1", choices: ["a"] });
assert.equal(ctx.uiCalls.length, 2); // ri-presentato una volta
assert.ok(ctx.notifications.some((n) => /Esc non chiude/.test(n.message)));
assert.ok(ctx.notifications.some((n) => /Esc does not close/.test(n.message)));
});
@@ -10,7 +10,9 @@ const GATE = path.join(__dirname, "..", "..", "tht-gate.js");
globalThis.require = createRequire(GATE);
const shell = { current: () => "" };
cp.execFileSync = (file, args, options) => shell.current(file, args, options);
cp.execFileSync = (file, args, options) => args[1] === "ensure-interaction-language"
? JSON.stringify({ interaction_language: "it", workspace_language: "it" })
: shell.current(file, args, options);
const installGate = require(GATE).default ?? require(GATE);
@@ -541,7 +543,7 @@ test("F8 warns about pending indexing but preserves the saved review and finaliz
const { ctx, tools, calls } = await setupGate({ phase: 8, preview: [PROMOTION_CANDIDATE], indexed: false });
answerReview(ctx, [PROMOTION_CANDIDATE]);
await tools.get("reviewer_memory_promote").def.execute("pending", { session: "s1" }, null, null, ctx);
assert(ctx.notifications.some(({ message, level }) => level === "warning" && message.includes("Memory saved")));
assert(ctx.notifications.some(({ message, level }) => level === "warning" && message.includes("Memory salvata")));
assert(memoryPromotionMutationArgs(calls).some(args => args.includes("memory_summary_reviewed")));
});
test("F8 recovers a saved review without asking again or saving cards again", async () => {