@4pm/cli 1.4.6 → 1.4.12
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/dist/index.js +36 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -34992,6 +34992,20 @@ function buildPrompt(docs, question, askerRole) {
|
|
|
34992
34992
|
roleNote,
|
|
34993
34993
|
"Answer concisely in the user's language; reference the relevant doc heading/path when useful.",
|
|
34994
34994
|
"",
|
|
34995
|
+
// Structured output for inline moderation (ADR-0237): the same run also classifies the question.
|
|
34996
|
+
"Return ONLY a single JSON object (no markdown fences, no prose around it) with these keys:",
|
|
34997
|
+
' "onTopic": boolean \u2014 false if the question is NOT about how to use the 4PM product,',
|
|
34998
|
+
' "sensitive": boolean \u2014 true if the question is inappropriate / abusive OR asks how to hack,',
|
|
34999
|
+
" bypass security, gain unauthorized access, take over or steal another",
|
|
35000
|
+
" user's/org's account or data, obtain credentials, or any malicious intent,",
|
|
35001
|
+
' "reason": string \u2014 a short reason when onTopic is false or sensitive is true, else "",',
|
|
35002
|
+
` "answer": string \u2014 your reply to the user, in markdown (in the user's language).`,
|
|
35003
|
+
"Refusal policy (ADR-0237): if the question is off-topic (onTopic false) OR sensitive, DO NOT",
|
|
35004
|
+
`answer it. Set "answer" to this EXACT same polite refusal for BOTH cases (in the user's`,
|
|
35005
|
+
'language, do not reveal which category or hint at the reason): "Sorry, I can only help with',
|
|
35006
|
+
'questions about how to use 4PM. For anything else, please contact human support." Only answer',
|
|
35007
|
+
"normally when onTopic is true AND sensitive is false.",
|
|
35008
|
+
"",
|
|
34995
35009
|
"===== DOCUMENTATION =====",
|
|
34996
35010
|
docs,
|
|
34997
35011
|
"",
|
|
@@ -34999,6 +35013,23 @@ function buildPrompt(docs, question, askerRole) {
|
|
|
34999
35013
|
question
|
|
35000
35014
|
].join("\n");
|
|
35001
35015
|
}
|
|
35016
|
+
function parseModeratedAnswer(text) {
|
|
35017
|
+
const start = text.indexOf("{");
|
|
35018
|
+
const end = text.lastIndexOf("}");
|
|
35019
|
+
if (start !== -1 && end > start) {
|
|
35020
|
+
try {
|
|
35021
|
+
const obj = JSON.parse(text.slice(start, end + 1));
|
|
35022
|
+
if (typeof obj.answer === "string" && obj.answer.trim()) {
|
|
35023
|
+
const onTopic = obj.onTopic !== false;
|
|
35024
|
+
const sensitive = obj.sensitive === true;
|
|
35025
|
+
const reason = typeof obj.reason === "string" ? obj.reason : "";
|
|
35026
|
+
return { body: obj.answer.trim(), moderation: { onTopic, sensitive, reason } };
|
|
35027
|
+
}
|
|
35028
|
+
} catch {
|
|
35029
|
+
}
|
|
35030
|
+
}
|
|
35031
|
+
return { body: text.trim() };
|
|
35032
|
+
}
|
|
35002
35033
|
function runClaudeOnce(cmd, profile, prompt, extraEnv) {
|
|
35003
35034
|
return new Promise((resolve8, reject) => {
|
|
35004
35035
|
const isClaude = cmd.includes("claude");
|
|
@@ -35058,9 +35089,12 @@ async function runSupportAnswer(req, ai, profileDir2) {
|
|
|
35058
35089
|
ai
|
|
35059
35090
|
);
|
|
35060
35091
|
if (!text) return { body: "", error: "empty answer" };
|
|
35061
|
-
const
|
|
35092
|
+
const { body, moderation } = parseModeratedAnswer(text);
|
|
35093
|
+
if (!body) return { body: "", error: "empty answer" };
|
|
35094
|
+
const u = usage.tokens > 0 ? usage : { ...NO_USAGE, tokens: estimateTokens(body) };
|
|
35062
35095
|
return {
|
|
35063
|
-
body
|
|
35096
|
+
body,
|
|
35097
|
+
...moderation ? { moderation } : {},
|
|
35064
35098
|
tokens: u.tokens,
|
|
35065
35099
|
...usage.tokens > 0 ? {
|
|
35066
35100
|
tokensBreakdown: {
|