From 0240242f5cb5ffc19996219c483dcfe2ba2dcddd Mon Sep 17 00:00:00 2001 From: mptyl Date: Sat, 27 Jun 2026 20:07:25 +0200 Subject: [PATCH] feat(harness): gate uses externally-provided THT_SESSION id (BE-5) When THT_SESSION env var is set, /nuova-domanda injects a kickoff that tells the model to use the pre-created session id instead of running `tht session new`. /riprendi-sessione and no-env-var paths unchanged. Co-Authored-By: Claude Sonnet 4.6 --- .../__tests__/gate_provided_session.test.js | 30 +++++++++++++++++++ harness/.pi/extensions/tht-gate.js | 14 ++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 harness/.pi/extensions/gate/__tests__/gate_provided_session.test.js diff --git a/harness/.pi/extensions/gate/__tests__/gate_provided_session.test.js b/harness/.pi/extensions/gate/__tests__/gate_provided_session.test.js new file mode 100644 index 00000000..297bfeb6 --- /dev/null +++ b/harness/.pi/extensions/gate/__tests__/gate_provided_session.test.js @@ -0,0 +1,30 @@ +const test = require("node:test"); +const assert = require("node:assert"); +const { createFakePi } = require("./fake_pi_runtime.js"); +const installGate = require("../../tht-gate.js").default ?? require("../../tht-gate.js"); + +test("con THT_SESSION il kickoff usa l'id fornito e NON crea la sessione", async () => { + process.env.THT_SESSION = "2026-06-27-100000-test"; + try { + const { pi, ctx } = createFakePi(); + installGate(pi); + await pi.emit("input", { source: "rpc", text: '/nuova-domanda "x"' }); + const injected = await pi.emit("before_agent_start", { systemPrompt: "" }); + // before_agent_start returns { systemPrompt: "\n\n" } + const text = injected?.systemPrompt ?? ""; + assert.match(text, /2026-06-27-100000-test/); + assert.doesNotMatch(text, /tht session new/); + } finally { + delete process.env.THT_SESSION; + } +}); + +test("senza THT_SESSION il kickoff contiene tht session new (comportamento attuale)", async () => { + delete process.env.THT_SESSION; + const { pi, ctx } = createFakePi(); + installGate(pi); + await pi.emit("input", { source: "rpc", text: '/nuova-domanda "x"' }); + const injected = await pi.emit("before_agent_start", { systemPrompt: "" }); + const text = injected?.systemPrompt ?? ""; + assert.match(text, /tht session new/); +}); diff --git a/harness/.pi/extensions/tht-gate.js b/harness/.pi/extensions/tht-gate.js index 07695859..d5001d0c 100644 --- a/harness/.pi/extensions/tht-gate.js +++ b/harness/.pi/extensions/tht-gate.js @@ -60,6 +60,16 @@ const NUOVA_DOMANDA_KICKOFF = "NON eseguire mai `tht phase advance|reopen` ne' `tht decision add` da shell.\n" + "- Procedi una fase alla volta: a fine fase cedi il turno al reviewer, non incatenare le fasi."; +const NUOVA_DOMANDA_KICKOFF_PROVIDED = (sessionId) => + "Istruzioni operative — sessione ThothII (workflow human-in-the-middle). " + + `La sessione è GIÀ creata con id \`${sessionId}\`: usalo in OGNI comando \`tht\`. ` + + "NON creare una nuova sessione.\n" + + "1. Carica la skill leggendo `.pi/skills/tht-sessione/SKILL.md` con il tool `read`, " + + `poi segui il workflow dalla Fase 1 usando l'id \`${sessionId}\`.\n` + + "Regole non negoziabili: una domanda al reviewer per volta; mai promuovere/escludere/" + + "correggere senza conferma; le interazioni passano dai tool reviewer_*; testo libero col prefisso '!'. " + + "MAI `tht phase advance|reopen` né `tht decision add` da shell."; + const RIPRENDI_KICKOFF = "Istruzioni operative — ripresa di una sessione ThothII esistente (id nel messaggio sopra).\n" + "1. Esegui `tht session show ` e leggi: stato, domanda, decisioni registrate, presenza " + @@ -208,7 +218,9 @@ export default function (pi) { lockActive = true; lastSteered = false; pendingKickoff = /^\/nuova-domanda\b/.test(raw) - ? NUOVA_DOMANDA_KICKOFF + ? (process.env.THT_SESSION + ? NUOVA_DOMANDA_KICKOFF_PROVIDED(process.env.THT_SESSION) + : NUOVA_DOMANDA_KICKOFF) : RIPRENDI_KICKOFF; } // free-input block: attivo quando il lock è su, per qualsiasi input utente (non solo interattivo).