feat: harden runtime readiness and session workflow
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
// fake-Pi runtime mock (cross-cutting follow-up) would let it run in CI.
|
||||
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { Type } from "typebox";
|
||||
import {
|
||||
buildSelectRequest,
|
||||
@@ -46,6 +47,33 @@ import {
|
||||
} from "./gate/enrich.js";
|
||||
import { isReserved } from "./reserved-labels.mjs";
|
||||
|
||||
// Load the workflow contract once when Pi loads the extension. Asking the model to
|
||||
// discover/read the skill as its first action proved unreliable with remote models:
|
||||
// they can spend the whole RPC turn exploring the repository before opening the exact
|
||||
// file named in the kickoff. Embedding the canonical file keeps SKILL.md as the single
|
||||
// source of truth while making session bootstrap deterministic.
|
||||
const SESSION_SKILL = readFileSync(
|
||||
new URL("../skills/tht-sessione/SKILL.md", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
// Read through the CLI so workspace/config resolution stays canonical (sessions may
|
||||
// live outside this repository). A missing pack is a soft miss: standalone/TUI flows
|
||||
// retain the existing `tht search pack` bootstrap path.
|
||||
export function readRetrievalPack(ctx, sessionId) {
|
||||
if (!sessionId) return null;
|
||||
try {
|
||||
const content = execFileSync(
|
||||
"tht",
|
||||
["session", "retrieval-pack", sessionId],
|
||||
{ cwd: ctx.cwd, encoding: "utf8" },
|
||||
).trim();
|
||||
return content ? content.replaceAll("</retrieval-pack>", "</retrieval-pack>") : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// --- prepareArguments: parse stringified arrays (workaround for models that send
|
||||
// arrays as JSON strings -- same pattern as pi-core's edit tool prepareEditArguments)
|
||||
// Coerce a value that may arrive as a JSON-encoded string back into an object/array.
|
||||
@@ -122,16 +150,14 @@ export const GATE_CODE_FILES = /\.pi[\\/]extensions[\\/]/;
|
||||
|
||||
// --- kickoff payloads (verbatim from source L184-212, load-bearing model prose) -
|
||||
const NUOVA_DOMANDA_KICKOFF =
|
||||
"AZIONE IMMEDIATA OBBLIGATORIA: la skill canonica è già inclusa integralmente nel " +
|
||||
"system prompt. Non esplorare il repository e non leggere altri file di istruzioni.\n" +
|
||||
"Istruzioni operative — nuova sessione ThothII (workflow human-in-the-middle: tu " +
|
||||
"orchestri, il reviewer decide, la CLI `tht` persiste; NON sei in modalita' autonoma).\n" +
|
||||
'1. Esegui `tht session new "<la domanda dell\'utente nel messaggio sopra>"` e annota ' +
|
||||
"l'id stampato nell'ultima riga.\n" +
|
||||
"2. Carica la skill leggendo il file con il tool `read`: `.pi/skills/tht-sessione/SKILL.md` " +
|
||||
"(NON come comando di shell ne' come `/skill:...`). Poi segui il suo workflow dalla Fase 1 " +
|
||||
"(Chiarimento), usando l'id di sessione in ogni comando `tht`.\n" +
|
||||
"Se la skill non si carica (per qualsiasi motivo): FERMATI. Non proseguire da solo, non " +
|
||||
"improvvisare analisi o query. Comunica al reviewer che la skill tht-sessione non e' " +
|
||||
"disponibile e attendi istruzioni.\n" +
|
||||
"2. Segui il workflow dalla Fase 1 (Chiarimento), usando l'id di sessione in ogni " +
|
||||
"comando `tht`.\n" +
|
||||
"Regole non negoziabili (valgono SEMPRE, anche senza la skill):\n" +
|
||||
"- Una domanda al reviewer per volta; attendi la sua risposta prima di proseguire.\n" +
|
||||
"- MAI promuovere, escludere, correggere o applicare alcunche' senza conferma esplicita.\n" +
|
||||
@@ -140,27 +166,41 @@ 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) =>
|
||||
const NUOVA_DOMANDA_KICKOFF_PROVIDED = (sessionId, hasRetrievalPack = false) =>
|
||||
"AZIONE IMMEDIATA OBBLIGATORIA: non esplorare il repository, non usare `ls`, `find`, " +
|
||||
"`--help` e non leggere README, sorgenti, test o altri file di istruzioni. " +
|
||||
"La skill canonica è già inclusa integralmente nel system prompt qui sotto.\n" +
|
||||
"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` +
|
||||
(hasRetrievalPack
|
||||
? "1. Il retrieval pack persistito è già incluso nel system prompt: usalo direttamente. " +
|
||||
"NON eseguire `tht search pack` e NON chiamare un tool per leggere `retrieval_pack.md`.\n"
|
||||
: "1. Il retrieval pack non era ancora disponibile: come PRIMA chiamata tool esegui " +
|
||||
"subito `tht search pack \"<domanda originale nel messaggio utente>\" --session " +
|
||||
sessionId + "` e usa il risultato.\n") +
|
||||
"2. Nel primo turno identifica la SOLA ambiguità con maggiore impatto sulla query e " +
|
||||
"chiama subito `reviewer_select` con opzioni concrete. Niente lunga narrazione, elenco " +
|
||||
"di tutte le ambiguità o ricapitolazione preliminare.\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 =
|
||||
const RIPRENDI_KICKOFF = (hasRetrievalPack = false) =>
|
||||
"AZIONE IMMEDIATA OBBLIGATORIA: non esplorare il repository, non usare `ls`, `find`, " +
|
||||
"`--help` e non leggere README, sorgenti, test o altri file di istruzioni. " +
|
||||
"La skill canonica è già inclusa integralmente nel system prompt qui sotto.\n" +
|
||||
"Istruzioni operative — ripresa di una sessione ThothII esistente (id nel messaggio sopra).\n" +
|
||||
"1. Esegui `tht session show <id>` e leggi: stato, domanda, decisioni registrate, presenza " +
|
||||
"di schema_linking.json.\n" +
|
||||
"2. Carica la skill leggendo `.pi/skills/tht-sessione/SKILL.md` con il tool `read` (non come " +
|
||||
"comando di shell ne' `/skill:...`).\n" +
|
||||
(hasRetrievalPack
|
||||
? "2. Il retrieval pack persistito e' gia' incluso nel system prompt: usalo direttamente. NON eseguire `tht search pack` e NON chiamare un tool per leggere `retrieval_pack.md`.\n"
|
||||
: "2. Se serve il retrieval pack, esegui `tht search pack` una sola volta, senza esplorare altri file.\n") +
|
||||
"3. Determina l'ultima fase completata dai fatti persistiti (le decisioni sono la verita': " +
|
||||
"cio' che non e' registrato non e' avvenuto) e riprendi da li'.\n" +
|
||||
"Esegui i passi 1-2 ORA, in QUESTO stesso turno, chiamando subito i tool (`bash` per " +
|
||||
"`tht session show`, `read` per la skill): NON limitarti a dichiarare l'intenzione e NON " +
|
||||
"terminare il turno prima di aver chiamato i tool.\n" +
|
||||
"Esegui il passo 1 ORA, in QUESTO stesso turno, chiamando subito il tool `bash` per " +
|
||||
"`tht session show`: NON limitarti a dichiarare l'intenzione e NON " +
|
||||
"terminare il turno prima di aver chiamato i tool. Se la sessione e' in fase 1 e non ha decisioni, dopo `session show` chiama `reviewer_select` subito: non produrre testo libero.\n" +
|
||||
"Valgono le stesse regole non negoziabili: una domanda per volta, conferma esplicita, tool " +
|
||||
"reviewer_*, niente phase advance/reopen o decision add da shell, una fase alla volta.";
|
||||
|
||||
@@ -470,7 +510,7 @@ export default function (pi) {
|
||||
? process.env.THT_SESSION
|
||||
? NUOVA_DOMANDA_KICKOFF_PROVIDED(process.env.THT_SESSION)
|
||||
: NUOVA_DOMANDA_KICKOFF
|
||||
: RIPRENDI_KICKOFF;
|
||||
: RIPRENDI_KICKOFF();
|
||||
}
|
||||
// free-input block: attivo quando il lock è su, per qualsiasi input utente (non solo interattivo).
|
||||
if (!lockActive) return { action: "continue" };
|
||||
@@ -492,18 +532,46 @@ export default function (pi) {
|
||||
|
||||
// 3) BEFORE_AGENT_START: one-shot kickoff injection (appends the operational
|
||||
// instructions to the system prompt on the turn that starts/resumes a session).
|
||||
pi.on("before_agent_start", (event) => {
|
||||
pi.on("before_agent_start", (event, ctx) => {
|
||||
if (!pendingKickoff) return undefined;
|
||||
const inject = pendingKickoff;
|
||||
const sessionId = process.env.THT_SESSION;
|
||||
const isProvidedNewSession =
|
||||
Boolean(sessionId) && pendingKickoff === NUOVA_DOMANDA_KICKOFF_PROVIDED(sessionId);
|
||||
const isResumeSession = pendingKickoff === RIPRENDI_KICKOFF();
|
||||
const retrievalPack = isProvidedNewSession || isResumeSession
|
||||
? readRetrievalPack(ctx, sessionId)
|
||||
: null;
|
||||
const inject = isProvidedNewSession
|
||||
? NUOVA_DOMANDA_KICKOFF_PROVIDED(sessionId, Boolean(retrievalPack))
|
||||
: isResumeSession
|
||||
? RIPRENDI_KICKOFF(Boolean(retrievalPack))
|
||||
: pendingKickoff;
|
||||
pendingKickoff = null;
|
||||
return { systemPrompt: `${event.systemPrompt}\n\n${inject}` };
|
||||
return {
|
||||
message: {
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "Esegui ora il workflow richiesto. Non scrivere analisi, spiegazioni o un elenco: usa il tool bash per `tht session show` e, se la sessione e' in Fase 1 senza decisioni, invoca immediatamente reviewer_select. La tua prossima risposta visibile deve essere una tool call." }],
|
||||
},
|
||||
systemPrompt:
|
||||
`${event.systemPrompt}\n\n` +
|
||||
"<tht-sessione-skill>\n" +
|
||||
SESSION_SKILL +
|
||||
"\n</tht-sessione-skill>\n\n" +
|
||||
inject +
|
||||
(retrievalPack
|
||||
? "\n\n<retrieval-pack>\n" + retrievalPack + "\n</retrieval-pack>"
|
||||
: ""),
|
||||
};
|
||||
});
|
||||
|
||||
// 4) SESSION_START: reset all session-scoped state.
|
||||
// 4) SESSION_START: Pi 0.80 emits this AFTER the RPC input hook. Preserve a
|
||||
// just-armed kickoff until before_agent_start injects it; clearing it here
|
||||
// silently drops the F1/resume contract and lets the model meander.
|
||||
pi.on("session_start", (_event, _ctx) => {
|
||||
lockActive = false;
|
||||
lastSteered = false;
|
||||
pendingKickoff = null;
|
||||
if (!pendingKickoff) {
|
||||
lockActive = false;
|
||||
lastSteered = false;
|
||||
}
|
||||
activeSessionId = null;
|
||||
_phaseMetaCache = null;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user