@mycompbox/compbox-mcp 1.19.0 → 1.20.0
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/index.js +233 -4
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -529,7 +529,7 @@ function interviewFieldKeys() {
|
|
|
529
529
|
function buildInterviewSystemPrompt(definition = INTERVIEW_DEFINITION, seedContext) {
|
|
530
530
|
const requiredSections = definition.filter((s) => s.required);
|
|
531
531
|
const optionalSections = definition.filter((s) => !s.required);
|
|
532
|
-
const
|
|
532
|
+
const renderSection2 = (s) => {
|
|
533
533
|
const tag = s.required ? "REQUIRED" : "OPTIONAL \u2014 ask, but the user may decline";
|
|
534
534
|
const followUps = s.followUps.map((q) => ` - ${q}`).join("\n");
|
|
535
535
|
return `- ${s.title} (${tag}) [json keys: ${s.fields.join(", ")}]
|
|
@@ -547,10 +547,10 @@ ${followUps}`;
|
|
|
547
547
|
Work through these interview sections in order. Cover EVERY required section; for optional sections, ask about them too, but if the employee has nothing to add or declines, move on and leave that field out (never invent or pad it):
|
|
548
548
|
|
|
549
549
|
REQUIRED sections (an Epic Request cannot omit these):
|
|
550
|
-
${requiredSections.map(
|
|
550
|
+
${requiredSections.map(renderSection2).join("\n")}
|
|
551
551
|
|
|
552
552
|
OPTIONAL-BUT-RECOMMENDED sections (ask about each; omit the field if the employee has nothing):
|
|
553
|
-
${optionalSections.map(
|
|
553
|
+
${optionalSections.map(renderSection2).join("\n")}
|
|
554
554
|
|
|
555
555
|
The employee's answers may be short or casual \u2014 that's fine, but when you write the final fields below, EXPAND them into complete, well-written prose using everything they told you across the whole conversation, not just a compressed one-liner. Each required field is scored by an automated quality rubric with hard minimums, so undershooting length is a real failure, not just a style nitpick:
|
|
556
556
|
- problemStatement: at least ${String(INTERVIEW_FIELD_MIN_WORDS.problemStatement)} words.
|
|
@@ -830,12 +830,233 @@ var init_interview_completion = __esm({
|
|
|
830
830
|
}
|
|
831
831
|
});
|
|
832
832
|
|
|
833
|
+
// ../shared/dist/interview-conversation.js
|
|
834
|
+
function renderSection(s) {
|
|
835
|
+
const tag = s.required ? "needed" : "nice to have";
|
|
836
|
+
return `- ${s.title} (${tag}): ${s.purpose} For example: ${s.followUps.slice(0, 2).join(" ")}`;
|
|
837
|
+
}
|
|
838
|
+
function renderSaved(saved) {
|
|
839
|
+
const fieldLines = Object.entries(saved.fields).filter((entry) => typeof entry[1] === "string" && entry[1].trim().length > 0).map(([key, value]) => `${key}: ${value}`).join("\n");
|
|
840
|
+
const score = saved.compliance ? `Its quality score is ${String(saved.compliance.overall)}/100 (${saved.compliance.tier}).` : "It has no quality score yet.";
|
|
841
|
+
const issues = saved.compliance && saved.compliance.issues.length > 0 ? `
|
|
842
|
+
What the score says is missing or weak:
|
|
843
|
+
${saved.compliance.issues.slice(0, 8).map((i) => `- ${i.message}`).join("\n")}` : "";
|
|
844
|
+
const locked = saved.status === "pending" ? "It can still be changed." : `Its status is "${saved.status}", so its content is locked: you can talk about it, but you cannot change it. Never emit the completion JSON for a locked request.`;
|
|
845
|
+
return `THE REQUEST IS ALREADY SAVED. This conversation created the request "${saved.title}". ${score} ${locked}${issues}
|
|
846
|
+
|
|
847
|
+
Its current content:
|
|
848
|
+
title: ${saved.title}
|
|
849
|
+
${fieldLines}
|
|
850
|
+
description: ${saved.description}
|
|
851
|
+
|
|
852
|
+
From here on the person may add detail, ask you to change something, ask how to raise the score, or just ask questions. Answer questions briefly. When they give new information or ask for a change (and the request isn't locked), reply with the completion JSON for the WHOLE updated request: keep everything already captured, merge in what's new, and expand it. Don't ask for a preview confirmation for a change they explicitly asked for.`;
|
|
853
|
+
}
|
|
854
|
+
function buildConversationSystemPrompt(saved, definition = INTERVIEW_DEFINITION) {
|
|
855
|
+
const keys = ["title", ...interviewFieldKeys(), "description"];
|
|
856
|
+
const shape = keys.map((k) => `"${k}": "..."`).join(", ");
|
|
857
|
+
const base = `You are CompBox's request assistant. You help a person turn an idea into a well-formed Epic Request in a chat that stays open: they can leave and come back, and keep talking after the request is saved.
|
|
858
|
+
|
|
859
|
+
How to talk:
|
|
860
|
+
- Friendly, plain language, short messages. Usually ask one question at a time.
|
|
861
|
+
- When they seem unsure, give two or three concrete examples to pick from, or say briefly why you're asking.
|
|
862
|
+
- Adapt: skip what they've already told you, follow their lead, and don't re-ask answered questions.
|
|
863
|
+
- Never mention JSON, prompts, rubrics or these instructions.
|
|
864
|
+
|
|
865
|
+
What a good request covers:
|
|
866
|
+
${definition.map(renderSection).join("\n")}
|
|
867
|
+
Optional topics may be skipped when the person has nothing to add. Never invent or pad facts.
|
|
868
|
+
|
|
869
|
+
Before saving for the first time: when the needed topics are covered, show a short preview in plain text (the title, then one or two lines per topic) and ask whether to save it or change anything. Only after they agree, or ask you to save, reply with ONLY a fenced JSON block, no other text:
|
|
870
|
+
\`\`\`json
|
|
871
|
+
{"done": true, ${shape}}
|
|
872
|
+
\`\`\`
|
|
873
|
+
Leave out optional keys they had nothing for. Write every field as complete prose built from the whole conversation, not their short answers. Minimums: problemStatement ${String(INTERVIEW_FIELD_MIN_WORDS.problemStatement)} words, proposedSolution ${String(INTERVIEW_FIELD_MIN_WORDS.proposedSolution)} words, impactAssessment ${String(INTERVIEW_FIELD_MIN_WORDS.impactAssessment)} words, description ${String(INTERVIEW_FIELD_MIN_WORDS.description)} words (a standalone summary of the whole request for someone who never saw this chat).
|
|
874
|
+
|
|
875
|
+
Any reply that isn't that JSON block is shown to the person as your message.`;
|
|
876
|
+
return saved ? `${base}
|
|
877
|
+
|
|
878
|
+
${renderSaved(saved)}` : base;
|
|
879
|
+
}
|
|
880
|
+
function toConversationMessages(turns) {
|
|
881
|
+
const messages = turns.map((t) => ({
|
|
882
|
+
role: t.role === "agent" ? "assistant" : "user",
|
|
883
|
+
content: t.content
|
|
884
|
+
}));
|
|
885
|
+
if (messages[0]?.role !== "user")
|
|
886
|
+
messages.unshift({ role: "user", content: STARTER });
|
|
887
|
+
if (messages[messages.length - 1]?.role !== "user")
|
|
888
|
+
messages.push({ role: "user", content: NUDGE });
|
|
889
|
+
return messages;
|
|
890
|
+
}
|
|
891
|
+
function renderConversationPrompt(messages) {
|
|
892
|
+
const lines = messages.map((m) => `${m.role === "assistant" ? "You" : "Them"}: ${m.content}`);
|
|
893
|
+
return `The conversation so far:
|
|
894
|
+
|
|
895
|
+
${lines.join("\n\n")}
|
|
896
|
+
|
|
897
|
+
Write your next reply to them.`;
|
|
898
|
+
}
|
|
899
|
+
function savedMessage(saved, created, followUp) {
|
|
900
|
+
const verb = created ? "Saved your request" : "Updated your request";
|
|
901
|
+
const c = saved.compliance;
|
|
902
|
+
let text;
|
|
903
|
+
if (!c) {
|
|
904
|
+
text = `${verb} "${saved.title}". You can keep talking here to add detail or change anything.`;
|
|
905
|
+
} else if (meetsComplianceTarget(c)) {
|
|
906
|
+
text = `${verb} "${saved.title}". It scores ${String(c.overall)}/100, ready for review. You can keep talking here to change anything.`;
|
|
907
|
+
} else {
|
|
908
|
+
const gaps = [...c.issues.filter((i) => i.tier === "critical"), ...c.issues.filter((i) => i.tier === "advisory")].slice(0, 4).map((i) => `- ${i.message}`).join("\n");
|
|
909
|
+
text = `${verb} "${saved.title}". It scores ${String(c.overall)}/100; 90 is the target.${gaps ? ` To raise it:
|
|
910
|
+
${gaps}` : ""}
|
|
911
|
+
|
|
912
|
+
Tell me more here and I'll update it, or leave it as it is.`;
|
|
913
|
+
}
|
|
914
|
+
return followUp ? `${text}
|
|
915
|
+
|
|
916
|
+
${followUp}` : text;
|
|
917
|
+
}
|
|
918
|
+
async function runConversationStep(input, deps) {
|
|
919
|
+
const system = buildConversationSystemPrompt(input.saved);
|
|
920
|
+
const messages = toConversationMessages(input.turns);
|
|
921
|
+
const locked = input.saved !== null && input.saved.status !== "pending";
|
|
922
|
+
let requestId = input.saved?.id ?? null;
|
|
923
|
+
let savedNow = null;
|
|
924
|
+
let createdHere = false;
|
|
925
|
+
let qualityRounds = 0;
|
|
926
|
+
let repairs = 0;
|
|
927
|
+
for (; ; ) {
|
|
928
|
+
let reply;
|
|
929
|
+
try {
|
|
930
|
+
reply = await deps.reply(system, messages);
|
|
931
|
+
} catch (error) {
|
|
932
|
+
if (savedNow)
|
|
933
|
+
return { kind: "saved", text: savedMessage(savedNow.saved, savedNow.created), ...savedNow };
|
|
934
|
+
const reason = error instanceof Error ? error.message : "Something went wrong reaching the AI.";
|
|
935
|
+
return {
|
|
936
|
+
kind: "error",
|
|
937
|
+
text: `I couldn't reply just now: ${reason}
|
|
938
|
+
|
|
939
|
+
Send another message (for example "try again") when you're ready and I'll pick up where we left off.`
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
const draft = extractCompletionJson(reply);
|
|
943
|
+
if (draft) {
|
|
944
|
+
if (locked) {
|
|
945
|
+
messages.push({ role: "assistant", content: reply }, { role: "user", content: "That request is locked and can't be changed. Reply to me in plain text instead." });
|
|
946
|
+
if (++repairs > JSON_REPAIRS)
|
|
947
|
+
return { kind: "error", text: "This request is locked, so it can't be changed any more." };
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
let saved;
|
|
951
|
+
try {
|
|
952
|
+
saved = await deps.save(requestId, draft);
|
|
953
|
+
} catch (error) {
|
|
954
|
+
const reason = error instanceof Error ? error.message : "the save failed";
|
|
955
|
+
return { kind: "error", text: `I couldn't save the request: ${reason}
|
|
956
|
+
|
|
957
|
+
Reply "try again" and I'll retry.` };
|
|
958
|
+
}
|
|
959
|
+
createdHere ||= requestId === null;
|
|
960
|
+
savedNow = { saved, created: createdHere };
|
|
961
|
+
requestId = saved.id;
|
|
962
|
+
if (meetsComplianceTarget(saved.compliance) || qualityRounds >= CONVERSATION_QUALITY_ROUNDS || !saved.compliance) {
|
|
963
|
+
return { kind: "saved", text: savedMessage(saved, savedNow.created), ...savedNow };
|
|
964
|
+
}
|
|
965
|
+
qualityRounds++;
|
|
966
|
+
messages.push({ role: "assistant", content: reply }, {
|
|
967
|
+
role: "user",
|
|
968
|
+
content: `${buildAuditFeedback(saved.compliance)}
|
|
969
|
+
|
|
970
|
+
The request IS already saved, and the person will be shown its score. If you ask them something, ask it as a way to improve the saved request; never say it hasn't been saved.`
|
|
971
|
+
});
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
if (looksLikeCompletionAttempt(reply)) {
|
|
975
|
+
if (++repairs > JSON_REPAIRS) {
|
|
976
|
+
return savedNow ? { kind: "saved", text: savedMessage(savedNow.saved, savedNow.created), ...savedNow } : { kind: "error", text: `I had trouble writing up the request. Reply "try again" and I'll have another go.` };
|
|
977
|
+
}
|
|
978
|
+
messages.push({ role: "assistant", content: reply }, { role: "user", content: COMPLETION_REPAIR_INSTRUCTION });
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
const text = reply.trim();
|
|
982
|
+
if (savedNow) {
|
|
983
|
+
return { kind: "saved", text: savedMessage(savedNow.saved, savedNow.created, text || void 0), ...savedNow };
|
|
984
|
+
}
|
|
985
|
+
if (!text)
|
|
986
|
+
return { kind: "error", text: "I didn't get a reply that time. Send another message and I'll try again." };
|
|
987
|
+
return { kind: "message", text };
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
function savedContextFromRecord(record) {
|
|
991
|
+
if (typeof record !== "object" || record === null)
|
|
992
|
+
return null;
|
|
993
|
+
const r = record;
|
|
994
|
+
if (typeof r.id !== "string" || typeof r.title !== "string")
|
|
995
|
+
return null;
|
|
996
|
+
let structured = r.structuredDesc;
|
|
997
|
+
if (typeof structured === "string") {
|
|
998
|
+
try {
|
|
999
|
+
structured = JSON.parse(structured);
|
|
1000
|
+
} catch {
|
|
1001
|
+
structured = null;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
const fields = {};
|
|
1005
|
+
if (typeof structured === "object" && structured !== null) {
|
|
1006
|
+
for (const [key, value] of Object.entries(structured)) {
|
|
1007
|
+
if (typeof value === "string")
|
|
1008
|
+
fields[key] = value;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
let score = r.complianceScore;
|
|
1012
|
+
if (typeof score === "string") {
|
|
1013
|
+
try {
|
|
1014
|
+
score = JSON.parse(score);
|
|
1015
|
+
} catch {
|
|
1016
|
+
score = null;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
return {
|
|
1020
|
+
id: r.id,
|
|
1021
|
+
title: r.title,
|
|
1022
|
+
status: typeof r.status === "string" ? r.status : "pending",
|
|
1023
|
+
description: typeof r.description === "string" ? r.description : "",
|
|
1024
|
+
fields,
|
|
1025
|
+
compliance: complianceFromRecord(score)
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
function complianceFromRecord(score) {
|
|
1029
|
+
if (typeof score !== "object" || score === null)
|
|
1030
|
+
return null;
|
|
1031
|
+
const s = score;
|
|
1032
|
+
if (typeof s.overall !== "number" || typeof s.tier !== "string")
|
|
1033
|
+
return null;
|
|
1034
|
+
const issues = Array.isArray(s.issues) ? s.issues.filter((i) => typeof i === "object" && i !== null).map((i) => ({
|
|
1035
|
+
message: typeof i.message === "string" ? i.message : typeof i.code === "string" ? i.code : "issue",
|
|
1036
|
+
tier: i.tier === "critical" ? "critical" : "advisory"
|
|
1037
|
+
})) : [];
|
|
1038
|
+
return { overall: s.overall, tier: s.tier, issues };
|
|
1039
|
+
}
|
|
1040
|
+
var CONVERSATION_QUALITY_ROUNDS, JSON_REPAIRS, STARTER, NUDGE;
|
|
1041
|
+
var init_interview_conversation = __esm({
|
|
1042
|
+
"../shared/dist/interview-conversation.js"() {
|
|
1043
|
+
"use strict";
|
|
1044
|
+
init_interview_definition();
|
|
1045
|
+
init_interview_completion();
|
|
1046
|
+
CONVERSATION_QUALITY_ROUNDS = 2;
|
|
1047
|
+
JSON_REPAIRS = 3;
|
|
1048
|
+
STARTER = "Hi! I'd like to propose something.";
|
|
1049
|
+
NUDGE = "(Please continue.)";
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
|
|
833
1053
|
// ../shared/dist/index.js
|
|
834
1054
|
var dist_exports = {};
|
|
835
1055
|
__export(dist_exports, {
|
|
836
1056
|
AI_PROVIDERS: () => AI_PROVIDERS,
|
|
837
1057
|
APPEND_DESCRIPTION_MAX: () => APPEND_DESCRIPTION_MAX,
|
|
838
1058
|
COMPLETION_REPAIR_INSTRUCTION: () => COMPLETION_REPAIR_INSTRUCTION,
|
|
1059
|
+
CONVERSATION_QUALITY_ROUNDS: () => CONVERSATION_QUALITY_ROUNDS,
|
|
839
1060
|
DESCRIPTION_MAX: () => DESCRIPTION_MAX,
|
|
840
1061
|
INTERVIEW_DEFINITION: () => INTERVIEW_DEFINITION,
|
|
841
1062
|
INTERVIEW_FIELD_MIN_WORDS: () => INTERVIEW_FIELD_MIN_WORDS,
|
|
@@ -856,7 +1077,9 @@ __export(dist_exports, {
|
|
|
856
1077
|
aiProviderValues: () => aiProviderValues,
|
|
857
1078
|
aiRouteValues: () => aiRouteValues,
|
|
858
1079
|
buildAuditFeedback: () => buildAuditFeedback,
|
|
1080
|
+
buildConversationSystemPrompt: () => buildConversationSystemPrompt,
|
|
859
1081
|
buildInterviewSystemPrompt: () => buildInterviewSystemPrompt,
|
|
1082
|
+
complianceFromRecord: () => complianceFromRecord,
|
|
860
1083
|
err: () => err,
|
|
861
1084
|
extractCompletionJson: () => extractCompletionJson,
|
|
862
1085
|
getAnthropicProxyUrl: () => getAnthropicProxyUrl,
|
|
@@ -889,12 +1112,17 @@ __export(dist_exports, {
|
|
|
889
1112
|
normalizeGitRemote: () => normalizeGitRemote,
|
|
890
1113
|
ok: () => ok,
|
|
891
1114
|
probeProxyHealth: () => probeProxyHealth,
|
|
1115
|
+
renderConversationPrompt: () => renderConversationPrompt,
|
|
892
1116
|
resolveRouting: () => resolveRouting,
|
|
1117
|
+
runConversationStep: () => runConversationStep,
|
|
893
1118
|
safeValidateSkillPackManifest: () => safeValidateSkillPackManifest,
|
|
1119
|
+
savedContextFromRecord: () => savedContextFromRecord,
|
|
1120
|
+
savedMessage: () => savedMessage,
|
|
894
1121
|
skillPackManifestSchema: () => skillPackManifestSchema,
|
|
895
1122
|
skillSchema: () => skillSchema,
|
|
896
1123
|
sleep: () => sleep,
|
|
897
1124
|
thinkingLevelValues: () => thinkingLevelValues,
|
|
1125
|
+
toConversationMessages: () => toConversationMessages,
|
|
898
1126
|
validatePackName: () => validatePackName,
|
|
899
1127
|
validateSkillPackManifest: () => validateSkillPackManifest
|
|
900
1128
|
});
|
|
@@ -909,6 +1137,7 @@ var init_dist = __esm({
|
|
|
909
1137
|
init_pxpipe();
|
|
910
1138
|
init_interview_definition();
|
|
911
1139
|
init_interview_completion();
|
|
1140
|
+
init_interview_conversation();
|
|
912
1141
|
}
|
|
913
1142
|
});
|
|
914
1143
|
|
|
@@ -11089,7 +11318,7 @@ function withLegacyToolCallRewrite(transport) {
|
|
|
11089
11318
|
var SERVER_NAME = "compbox-mcp";
|
|
11090
11319
|
function resolveServerVersion() {
|
|
11091
11320
|
if (true) {
|
|
11092
|
-
return "1.
|
|
11321
|
+
return "1.20.0";
|
|
11093
11322
|
}
|
|
11094
11323
|
const require2 = createRequire(import.meta.url);
|
|
11095
11324
|
for (const candidate of ["../package.json", "./package.json"]) {
|