@mycompbox/compbox-mcp 1.18.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 +444 -5
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -118,7 +118,8 @@ var init_mailbox = __esm({
|
|
|
118
118
|
JOB_TYPES = [
|
|
119
119
|
"epic_request",
|
|
120
120
|
"interview_reply",
|
|
121
|
-
"start_execution"
|
|
121
|
+
"start_execution",
|
|
122
|
+
"connect_repo"
|
|
122
123
|
];
|
|
123
124
|
JOB_STATUSES = [
|
|
124
125
|
"pending",
|
|
@@ -328,6 +329,74 @@ var init_skill_packs = __esm({
|
|
|
328
329
|
}
|
|
329
330
|
});
|
|
330
331
|
|
|
332
|
+
// ../shared/dist/ai-providers.js
|
|
333
|
+
var aiRouteValues, aiProviderValues, AI_PROVIDERS;
|
|
334
|
+
var init_ai_providers = __esm({
|
|
335
|
+
"../shared/dist/ai-providers.js"() {
|
|
336
|
+
"use strict";
|
|
337
|
+
aiRouteValues = ["machine", "api_key"];
|
|
338
|
+
aiProviderValues = ["anthropic", "openai", "xai", "openrouter"];
|
|
339
|
+
AI_PROVIDERS = {
|
|
340
|
+
anthropic: {
|
|
341
|
+
id: "anthropic",
|
|
342
|
+
name: "Anthropic (Claude)",
|
|
343
|
+
keyPageUrl: "https://console.anthropic.com/settings/keys",
|
|
344
|
+
limitsPageUrl: "https://console.anthropic.com/settings/limits",
|
|
345
|
+
keyPrefix: "sk-ant-",
|
|
346
|
+
steps: [
|
|
347
|
+
"Sign in to the Anthropic Console and add a payment method or credits under Billing.",
|
|
348
|
+
'Open API keys and choose Create key. Name it after this project, for example "CompBox \u2013 My App".',
|
|
349
|
+
"Copy the key. Anthropic shows it only once.",
|
|
350
|
+
"Optional: set a monthly spend limit under Limits."
|
|
351
|
+
],
|
|
352
|
+
reportsBalance: false
|
|
353
|
+
},
|
|
354
|
+
openai: {
|
|
355
|
+
id: "openai",
|
|
356
|
+
name: "OpenAI",
|
|
357
|
+
keyPageUrl: "https://platform.openai.com/api-keys",
|
|
358
|
+
limitsPageUrl: "https://platform.openai.com/settings/organization/limits",
|
|
359
|
+
keyPrefix: "sk-",
|
|
360
|
+
steps: [
|
|
361
|
+
"Sign in to the OpenAI Platform and add credits under Billing.",
|
|
362
|
+
"Open API keys and choose Create new secret key. Name it after this project.",
|
|
363
|
+
"Copy the key. OpenAI shows it only once.",
|
|
364
|
+
"Optional: set a monthly budget under Limits."
|
|
365
|
+
],
|
|
366
|
+
reportsBalance: false
|
|
367
|
+
},
|
|
368
|
+
xai: {
|
|
369
|
+
id: "xai",
|
|
370
|
+
name: "xAI (Grok)",
|
|
371
|
+
keyPageUrl: "https://console.x.ai",
|
|
372
|
+
limitsPageUrl: "https://console.x.ai",
|
|
373
|
+
keyPrefix: "xai-",
|
|
374
|
+
steps: [
|
|
375
|
+
"Sign in to the xAI Console and add credits under Billing.",
|
|
376
|
+
"Open API Keys and create a key. Name it after this project.",
|
|
377
|
+
"Copy the key. xAI shows it only once.",
|
|
378
|
+
"Optional: set a spending limit in the console."
|
|
379
|
+
],
|
|
380
|
+
reportsBalance: false
|
|
381
|
+
},
|
|
382
|
+
openrouter: {
|
|
383
|
+
id: "openrouter",
|
|
384
|
+
name: "OpenRouter",
|
|
385
|
+
keyPageUrl: "https://openrouter.ai/settings/keys",
|
|
386
|
+
limitsPageUrl: "https://openrouter.ai/settings/credits",
|
|
387
|
+
keyPrefix: "sk-or-",
|
|
388
|
+
note: "One key for models from many providers, including free models with daily limits.",
|
|
389
|
+
steps: [
|
|
390
|
+
"Sign in to OpenRouter. Free models work without credits; add credits under Credits for paid models and higher limits.",
|
|
391
|
+
"Open Keys and choose Create key. Name it after yourself or your company, and optionally set a credit limit on the key.",
|
|
392
|
+
"Copy the key. OpenRouter shows it only once."
|
|
393
|
+
],
|
|
394
|
+
reportsBalance: true
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
331
400
|
// ../shared/dist/model-catalog.js
|
|
332
401
|
function getModelById(id) {
|
|
333
402
|
return MODEL_CATALOG.find((m) => m.id === id);
|
|
@@ -460,7 +529,7 @@ function interviewFieldKeys() {
|
|
|
460
529
|
function buildInterviewSystemPrompt(definition = INTERVIEW_DEFINITION, seedContext) {
|
|
461
530
|
const requiredSections = definition.filter((s) => s.required);
|
|
462
531
|
const optionalSections = definition.filter((s) => !s.required);
|
|
463
|
-
const
|
|
532
|
+
const renderSection2 = (s) => {
|
|
464
533
|
const tag = s.required ? "REQUIRED" : "OPTIONAL \u2014 ask, but the user may decline";
|
|
465
534
|
const followUps = s.followUps.map((q) => ` - ${q}`).join("\n");
|
|
466
535
|
return `- ${s.title} (${tag}) [json keys: ${s.fields.join(", ")}]
|
|
@@ -478,10 +547,10 @@ ${followUps}`;
|
|
|
478
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):
|
|
479
548
|
|
|
480
549
|
REQUIRED sections (an Epic Request cannot omit these):
|
|
481
|
-
${requiredSections.map(
|
|
550
|
+
${requiredSections.map(renderSection2).join("\n")}
|
|
482
551
|
|
|
483
552
|
OPTIONAL-BUT-RECOMMENDED sections (ask about each; omit the field if the employee has nothing):
|
|
484
|
-
${optionalSections.map(
|
|
553
|
+
${optionalSections.map(renderSection2).join("\n")}
|
|
485
554
|
|
|
486
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:
|
|
487
556
|
- problemStatement: at least ${String(INTERVIEW_FIELD_MIN_WORDS.problemStatement)} words.
|
|
@@ -631,10 +700,363 @@ var init_interview_definition = __esm({
|
|
|
631
700
|
}
|
|
632
701
|
});
|
|
633
702
|
|
|
703
|
+
// ../shared/dist/interview-completion.js
|
|
704
|
+
function meetsComplianceTarget(compliance) {
|
|
705
|
+
return !compliance || compliance.tier === TARGET_COMPLIANCE_TIER;
|
|
706
|
+
}
|
|
707
|
+
function buildAuditFeedback(compliance) {
|
|
708
|
+
const ordered = [
|
|
709
|
+
...compliance.issues.filter((i) => i.tier === "critical"),
|
|
710
|
+
...compliance.issues.filter((i) => i.tier === "advisory")
|
|
711
|
+
].slice(0, 8);
|
|
712
|
+
const list = ordered.length > 0 ? ordered.map((i) => `- ${i.message}`).join("\n") : "- (no specific issues returned; expand the required fields with more concrete detail)";
|
|
713
|
+
return `The epic request you just produced scored ${String(compliance.overall)}/100 (tier: ${compliance.tier}). It must reach 90/100 with ZERO critical issues before the interview can finish. Fix these:
|
|
714
|
+
${list}
|
|
715
|
+
|
|
716
|
+
If you can resolve these by EXPANDING what the employee already told you \u2014 a longer, more complete and concrete problemStatement / proposedSolution / impactAssessment / description that comfortably clear the length minimums \u2014 re-emit the completion JSON now with richer fields. Do NOT re-ask something you already have an answer for. Only if you genuinely need NEW information from the employee, ask ONE focused question to get it.`;
|
|
717
|
+
}
|
|
718
|
+
function isEpicRequestDraft(value) {
|
|
719
|
+
if (typeof value !== "object" || value === null)
|
|
720
|
+
return false;
|
|
721
|
+
const v = value;
|
|
722
|
+
const nonEmpty = (x) => typeof x === "string" && x.trim().length > 0;
|
|
723
|
+
const requiredOk = nonEmpty(v.title) && nonEmpty(v.problemStatement) && nonEmpty(v.proposedSolution) && nonEmpty(v.impactAssessment) && nonEmpty(v.description);
|
|
724
|
+
if (!requiredOk)
|
|
725
|
+
return false;
|
|
726
|
+
for (const key of OPTIONAL_DRAFT_KEYS) {
|
|
727
|
+
if (key in v && v[key] !== void 0 && typeof v[key] !== "string")
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
function stripTrailingCommas(json) {
|
|
733
|
+
return json.replace(/,(\s*[}\]])/g, "$1");
|
|
734
|
+
}
|
|
735
|
+
function escapeRawControlCharsInStrings(json) {
|
|
736
|
+
let out = "";
|
|
737
|
+
let inString = false;
|
|
738
|
+
let escaped = false;
|
|
739
|
+
for (const ch of json) {
|
|
740
|
+
if (escaped) {
|
|
741
|
+
out += ch;
|
|
742
|
+
escaped = false;
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
if (ch === "\\") {
|
|
746
|
+
out += ch;
|
|
747
|
+
escaped = true;
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
if (ch === '"') {
|
|
751
|
+
inString = !inString;
|
|
752
|
+
out += ch;
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
if (inString && (ch === "\n" || ch === "\r" || ch === " ")) {
|
|
756
|
+
out += ch === "\n" ? "\\n" : ch === "\r" ? "\\r" : "\\t";
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
out += ch;
|
|
760
|
+
}
|
|
761
|
+
return out;
|
|
762
|
+
}
|
|
763
|
+
function completionJsonCandidates(text) {
|
|
764
|
+
const candidates = [];
|
|
765
|
+
const fenced = /```(?:json)?\s*([\s\S]*?)```/i.exec(text);
|
|
766
|
+
if (fenced?.[1])
|
|
767
|
+
candidates.push(fenced[1]);
|
|
768
|
+
const trimmed = text.trim();
|
|
769
|
+
if (trimmed.startsWith("{"))
|
|
770
|
+
candidates.push(trimmed);
|
|
771
|
+
const first = text.indexOf("{");
|
|
772
|
+
const last = text.lastIndexOf("}");
|
|
773
|
+
if (first !== -1 && last > first)
|
|
774
|
+
candidates.push(text.slice(first, last + 1));
|
|
775
|
+
return candidates;
|
|
776
|
+
}
|
|
777
|
+
function toDraft(parsed) {
|
|
778
|
+
const draft = {
|
|
779
|
+
title: parsed.title,
|
|
780
|
+
problemStatement: parsed.problemStatement,
|
|
781
|
+
proposedSolution: parsed.proposedSolution,
|
|
782
|
+
impactAssessment: parsed.impactAssessment,
|
|
783
|
+
description: parsed.description
|
|
784
|
+
};
|
|
785
|
+
for (const key of OPTIONAL_DRAFT_KEYS) {
|
|
786
|
+
const raw = parsed[key];
|
|
787
|
+
if (typeof raw === "string" && raw.trim().length > 0) {
|
|
788
|
+
draft[key] = raw;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return draft;
|
|
792
|
+
}
|
|
793
|
+
function extractCompletionJson(text) {
|
|
794
|
+
for (const candidate of completionJsonCandidates(text)) {
|
|
795
|
+
for (const variant of [
|
|
796
|
+
candidate,
|
|
797
|
+
stripTrailingCommas(candidate),
|
|
798
|
+
escapeRawControlCharsInStrings(candidate),
|
|
799
|
+
escapeRawControlCharsInStrings(stripTrailingCommas(candidate))
|
|
800
|
+
]) {
|
|
801
|
+
try {
|
|
802
|
+
const parsed = JSON.parse(variant);
|
|
803
|
+
if (isEpicRequestDraft(parsed))
|
|
804
|
+
return toDraft(parsed);
|
|
805
|
+
} catch {
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
function looksLikeCompletionAttempt(text) {
|
|
812
|
+
const hasObject = /```(?:json)?\s*\{/i.test(text) || text.trim().startsWith("{");
|
|
813
|
+
if (!hasObject)
|
|
814
|
+
return false;
|
|
815
|
+
return /"(?:done|title|problemStatement|proposedSolution|impactAssessment|description)"\s*:/i.test(text);
|
|
816
|
+
}
|
|
817
|
+
var TARGET_COMPLIANCE_TIER, OPTIONAL_DRAFT_KEYS, COMPLETION_REPAIR_INSTRUCTION;
|
|
818
|
+
var init_interview_completion = __esm({
|
|
819
|
+
"../shared/dist/interview-completion.js"() {
|
|
820
|
+
"use strict";
|
|
821
|
+
TARGET_COMPLIANCE_TIER = "pass";
|
|
822
|
+
OPTIONAL_DRAFT_KEYS = [
|
|
823
|
+
"successMetrics",
|
|
824
|
+
"targetAudience",
|
|
825
|
+
"alternatives",
|
|
826
|
+
"dependencies",
|
|
827
|
+
"estimatedEffort"
|
|
828
|
+
];
|
|
829
|
+
COMPLETION_REPAIR_INSTRUCTION = 'Your last message looked like a completion but I could NOT parse it as a valid JSON object with all required fields. Re-emit ONLY a single valid JSON object inside a ```json code fence \u2014 no other text \u2014 with every string value properly escaped (escape any internal double-quotes as \\" and any line breaks as \\n), and make sure title, problemStatement, proposedSolution, impactAssessment, and description are all present and non-empty:\n```json\n{"done": true, "title": "...", "problemStatement": "...", "proposedSolution": "...", "impactAssessment": "...", "description": "..."}\n```';
|
|
830
|
+
}
|
|
831
|
+
});
|
|
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
|
+
|
|
634
1053
|
// ../shared/dist/index.js
|
|
635
1054
|
var dist_exports = {};
|
|
636
1055
|
__export(dist_exports, {
|
|
1056
|
+
AI_PROVIDERS: () => AI_PROVIDERS,
|
|
637
1057
|
APPEND_DESCRIPTION_MAX: () => APPEND_DESCRIPTION_MAX,
|
|
1058
|
+
COMPLETION_REPAIR_INSTRUCTION: () => COMPLETION_REPAIR_INSTRUCTION,
|
|
1059
|
+
CONVERSATION_QUALITY_ROUNDS: () => CONVERSATION_QUALITY_ROUNDS,
|
|
638
1060
|
DESCRIPTION_MAX: () => DESCRIPTION_MAX,
|
|
639
1061
|
INTERVIEW_DEFINITION: () => INTERVIEW_DEFINITION,
|
|
640
1062
|
INTERVIEW_FIELD_MIN_WORDS: () => INTERVIEW_FIELD_MIN_WORDS,
|
|
@@ -649,10 +1071,17 @@ __export(dist_exports, {
|
|
|
649
1071
|
SUBSCRIPTION_STATUSES: () => SUBSCRIPTION_STATUSES,
|
|
650
1072
|
SUMMARY_MAX: () => SUMMARY_MAX,
|
|
651
1073
|
SessionEventType: () => SessionEventType,
|
|
1074
|
+
TARGET_COMPLIANCE_TIER: () => TARGET_COMPLIANCE_TIER,
|
|
652
1075
|
TURN_ROLES: () => TURN_ROLES,
|
|
653
1076
|
agentSchema: () => agentSchema,
|
|
1077
|
+
aiProviderValues: () => aiProviderValues,
|
|
1078
|
+
aiRouteValues: () => aiRouteValues,
|
|
1079
|
+
buildAuditFeedback: () => buildAuditFeedback,
|
|
1080
|
+
buildConversationSystemPrompt: () => buildConversationSystemPrompt,
|
|
654
1081
|
buildInterviewSystemPrompt: () => buildInterviewSystemPrompt,
|
|
1082
|
+
complianceFromRecord: () => complianceFromRecord,
|
|
655
1083
|
err: () => err,
|
|
1084
|
+
extractCompletionJson: () => extractCompletionJson,
|
|
656
1085
|
getAnthropicProxyUrl: () => getAnthropicProxyUrl,
|
|
657
1086
|
getModelById: () => getModelById,
|
|
658
1087
|
getModelDisplayName: () => getModelDisplayName,
|
|
@@ -675,18 +1104,25 @@ __export(dist_exports, {
|
|
|
675
1104
|
isSubscriptionStatus: () => isSubscriptionStatus,
|
|
676
1105
|
isTurnRole: () => isTurnRole,
|
|
677
1106
|
isValidModelId: () => isValidModelId,
|
|
1107
|
+
looksLikeCompletionAttempt: () => looksLikeCompletionAttempt,
|
|
678
1108
|
mcpConfigSchema: () => mcpConfigSchema,
|
|
679
1109
|
mcpServerConfigSchema: () => mcpServerConfigSchema,
|
|
1110
|
+
meetsComplianceTarget: () => meetsComplianceTarget,
|
|
680
1111
|
modelRecommendationSourceValues: () => modelRecommendationSourceValues,
|
|
681
1112
|
normalizeGitRemote: () => normalizeGitRemote,
|
|
682
1113
|
ok: () => ok,
|
|
683
1114
|
probeProxyHealth: () => probeProxyHealth,
|
|
1115
|
+
renderConversationPrompt: () => renderConversationPrompt,
|
|
684
1116
|
resolveRouting: () => resolveRouting,
|
|
1117
|
+
runConversationStep: () => runConversationStep,
|
|
685
1118
|
safeValidateSkillPackManifest: () => safeValidateSkillPackManifest,
|
|
1119
|
+
savedContextFromRecord: () => savedContextFromRecord,
|
|
1120
|
+
savedMessage: () => savedMessage,
|
|
686
1121
|
skillPackManifestSchema: () => skillPackManifestSchema,
|
|
687
1122
|
skillSchema: () => skillSchema,
|
|
688
1123
|
sleep: () => sleep,
|
|
689
1124
|
thinkingLevelValues: () => thinkingLevelValues,
|
|
1125
|
+
toConversationMessages: () => toConversationMessages,
|
|
690
1126
|
validatePackName: () => validatePackName,
|
|
691
1127
|
validateSkillPackManifest: () => validateSkillPackManifest
|
|
692
1128
|
});
|
|
@@ -696,9 +1132,12 @@ var init_dist = __esm({
|
|
|
696
1132
|
init_types();
|
|
697
1133
|
init_utils();
|
|
698
1134
|
init_skill_packs();
|
|
1135
|
+
init_ai_providers();
|
|
699
1136
|
init_model_catalog();
|
|
700
1137
|
init_pxpipe();
|
|
701
1138
|
init_interview_definition();
|
|
1139
|
+
init_interview_completion();
|
|
1140
|
+
init_interview_conversation();
|
|
702
1141
|
}
|
|
703
1142
|
});
|
|
704
1143
|
|
|
@@ -10879,7 +11318,7 @@ function withLegacyToolCallRewrite(transport) {
|
|
|
10879
11318
|
var SERVER_NAME = "compbox-mcp";
|
|
10880
11319
|
function resolveServerVersion() {
|
|
10881
11320
|
if (true) {
|
|
10882
|
-
return "1.
|
|
11321
|
+
return "1.20.0";
|
|
10883
11322
|
}
|
|
10884
11323
|
const require2 = createRequire(import.meta.url);
|
|
10885
11324
|
for (const candidate of ["../package.json", "./package.json"]) {
|