fix(harness): gate kickoff/lock entry works in RPC mode (not only interactive)
- Removed event.source === "interactive" guard from entry detection so /nuova-domanda and /riprendi-sessione activate lockActive+pendingKickoff regardless of source (TUI or RPC prompt). - Removed event.source !== "interactive" from free-input filter; lock now blocks/steers all user input when active, not only interactive keystrokes. - Added typebox@1.1.38 devDep + fake_pi_runtime.registerCommand (gap from Task 2). - New test: gate_entry.test.js (2 tests: lock activates on RPC; !-steer passes). - Full suite: 19/19 pass, zero regressions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,18 +17,20 @@ function createFakePi() {
|
|||||||
confirm: async (title, message, opts) => { ctx.uiCalls.push({ method: "confirm", title, message, opts }); return ctx.uiQueue.shift(); },
|
confirm: async (title, message, opts) => { ctx.uiCalls.push({ method: "confirm", title, message, opts }); return ctx.uiQueue.shift(); },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const commands = new Map();
|
||||||
const pi = {
|
const pi = {
|
||||||
on: (event, handler) => {
|
on: (event, handler) => {
|
||||||
if (!handlers.has(event)) handlers.set(event, []);
|
if (!handlers.has(event)) handlers.set(event, []);
|
||||||
handlers.get(event).push(handler);
|
handlers.get(event).push(handler);
|
||||||
},
|
},
|
||||||
registerTool: (def, fn) => tools.set(def?.name ?? def, { def, fn }),
|
registerTool: (def, fn) => tools.set(def?.name ?? def, { def, fn }),
|
||||||
|
registerCommand: (name, def) => commands.set(name, def),
|
||||||
emit: async (event, payload) => {
|
emit: async (event, payload) => {
|
||||||
let result;
|
let result;
|
||||||
for (const h of handlers.get(event) ?? []) result = await h(payload, ctx);
|
for (const h of handlers.get(event) ?? []) result = await h(payload, ctx);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return { pi, ctx, tools, emit: pi.emit, enqueueUi: (v) => ctx.uiQueue.push(v) };
|
return { pi, ctx, tools, commands, emit: pi.emit, enqueueUi: (v) => ctx.uiQueue.push(v) };
|
||||||
}
|
}
|
||||||
module.exports = { createFakePi };
|
module.exports = { createFakePi };
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
const test = require("node:test");
|
||||||
|
const assert = require("node:assert");
|
||||||
|
const { createFakePi } = require("./fake_pi_runtime.js");
|
||||||
|
|
||||||
|
// The gate is ESM; resolve it once via dynamic import (Global Constraints: use await import()
|
||||||
|
// if require() of ESM misbehaves on this Node version).
|
||||||
|
const installGatePromise = import("../../tht-gate.js").then((m) => m.default);
|
||||||
|
|
||||||
|
test("avvio workflow via input non-interattivo attiva il lock (free text bloccato)", async () => {
|
||||||
|
const installGate = await installGatePromise;
|
||||||
|
const { pi } = createFakePi();
|
||||||
|
installGate(pi);
|
||||||
|
// entry del workflow con source 'rpc' (come un comando prompt RPC)
|
||||||
|
await pi.emit("input", { source: "rpc", text: '/nuova-domanda "x"' });
|
||||||
|
// dopo l'entry, un testo libero senza '!' deve essere bloccato (lock attivo)
|
||||||
|
const res = await pi.emit("input", { source: "rpc", text: "promuovi la tabella pazienti" });
|
||||||
|
assert.equal(res.action, "handled");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("testo con '!' passa al modello (steer) anche con lock attivo", async () => {
|
||||||
|
const installGate = await installGatePromise;
|
||||||
|
const { pi } = createFakePi();
|
||||||
|
installGate(pi);
|
||||||
|
await pi.emit("input", { source: "rpc", text: '/nuova-domanda "x"' });
|
||||||
|
const res = await pi.emit("input", { source: "rpc", text: "!considera solo il 2024" });
|
||||||
|
assert.deepEqual(res, { action: "transform", text: "considera solo il 2024" });
|
||||||
|
});
|
||||||
@@ -217,10 +217,8 @@ export default function (pi) {
|
|||||||
// 2) INPUT hook: workflow entry detection + free-input block + `!` steer channel.
|
// 2) INPUT hook: workflow entry detection + free-input block + `!` steer channel.
|
||||||
pi.on("input", async (event, ctx) => {
|
pi.on("input", async (event, ctx) => {
|
||||||
const raw = (event.text ?? "").trimStart();
|
const raw = (event.text ?? "").trimStart();
|
||||||
if (
|
// entry detection: workflow-start funziona sia da TUI sia da comando RPC `prompt`.
|
||||||
event.source === "interactive" &&
|
if (/^\/(nuova-domanda|riprendi-sessione)\b/.test(raw)) {
|
||||||
/^\/(nuova-domanda|riprendi-sessione)\b/.test(raw)
|
|
||||||
) {
|
|
||||||
// NOTE: the source runs an ollama preflight here; ThothII defers embeddings
|
// NOTE: the source runs an ollama preflight here; ThothII defers embeddings
|
||||||
// readiness to the session's first vector op. Entry detection only:
|
// readiness to the session's first vector op. Entry detection only:
|
||||||
lockActive = true;
|
lockActive = true;
|
||||||
@@ -229,7 +227,8 @@ export default function (pi) {
|
|||||||
? NUOVA_DOMANDA_KICKOFF
|
? NUOVA_DOMANDA_KICKOFF
|
||||||
: RIPRENDI_KICKOFF;
|
: RIPRENDI_KICKOFF;
|
||||||
}
|
}
|
||||||
if (!lockActive || event.source !== "interactive") return { action: "continue" };
|
// free-input block: attivo quando il lock è su, per qualsiasi input utente (non solo interattivo).
|
||||||
|
if (!lockActive) return { action: "continue" };
|
||||||
const trimmed = (event.text ?? "").trimStart();
|
const trimmed = (event.text ?? "").trimStart();
|
||||||
if (trimmed.length === 0) return { action: "continue" };
|
if (trimmed.length === 0) return { action: "continue" };
|
||||||
if (trimmed.startsWith("/")) return { action: "continue" };
|
if (trimmed.startsWith("/")) return { action: "continue" };
|
||||||
|
|||||||
Generated
+22
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "thothii-harness-gate",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "thothii-harness-gate",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"typebox": "1.1.38"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/typebox": {
|
||||||
|
"version": "1.1.38",
|
||||||
|
"resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz",
|
||||||
|
"integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,5 +5,8 @@
|
|||||||
"description": "Pi gate extension for the ThothII NL->SQL harness (widget-descriptor builders + glue)",
|
"description": "Pi gate extension for the ThothII NL->SQL harness (widget-descriptor builders + glue)",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node --test .pi/extensions/gate/__tests__/*.test.js"
|
"test": "node --test .pi/extensions/gate/__tests__/*.test.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typebox": "1.1.38"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user