@kitsy/coop-ai 2.0.0 → 2.1.1
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.cjs +597 -7
- package/dist/index.d.cts +112 -3
- package/dist/index.d.ts +112 -3
- package/dist/index.js +589 -7
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -30,18 +30,26 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
CliAgentClient: () => CliAgentClient,
|
|
34
|
+
CliCompletionProvider: () => CliCompletionProvider,
|
|
35
|
+
buildIdeaRefinementPrompt: () => buildIdeaRefinementPrompt,
|
|
36
|
+
buildTaskRefinementPrompt: () => buildTaskRefinementPrompt,
|
|
33
37
|
build_contract: () => build_contract,
|
|
34
38
|
build_decomposition_prompt: () => build_decomposition_prompt,
|
|
35
39
|
constraint_violation_reasons: () => constraint_violation_reasons,
|
|
36
40
|
create_provider: () => create_provider,
|
|
37
41
|
create_provider_agent_client: () => create_provider_agent_client,
|
|
38
42
|
create_provider_idea_decomposer: () => create_provider_idea_decomposer,
|
|
43
|
+
create_provider_refinement_client: () => create_provider_refinement_client,
|
|
39
44
|
create_run: () => create_run,
|
|
40
45
|
decompose_idea_to_tasks: () => decompose_idea_to_tasks,
|
|
41
46
|
enforce_constraints: () => enforce_constraints,
|
|
42
47
|
execute_task: () => execute_task,
|
|
43
48
|
finalize_run: () => finalize_run,
|
|
44
49
|
log_step: () => log_step,
|
|
50
|
+
parseRefinementDraftResponse: () => parseRefinementDraftResponse,
|
|
51
|
+
refine_idea_to_draft: () => refine_idea_to_draft,
|
|
52
|
+
refine_task_to_draft: () => refine_task_to_draft,
|
|
45
53
|
resolve_provider_config: () => resolve_provider_config,
|
|
46
54
|
select_agent: () => select_agent,
|
|
47
55
|
validate_command: () => validate_command,
|
|
@@ -101,6 +109,17 @@ function taskAcceptance(task) {
|
|
|
101
109
|
const maybe = asRecord(task).acceptance;
|
|
102
110
|
return asStringArray(maybe) ?? [];
|
|
103
111
|
}
|
|
112
|
+
function taskTestsRequired(task) {
|
|
113
|
+
const maybe = asRecord(task).tests_required;
|
|
114
|
+
return asStringArray(maybe) ?? [];
|
|
115
|
+
}
|
|
116
|
+
function taskOriginRefs(task) {
|
|
117
|
+
const origin = asRecord(asRecord(task).origin);
|
|
118
|
+
return {
|
|
119
|
+
authority_refs: asStringArray(origin.authority_refs) ?? [],
|
|
120
|
+
derived_refs: asStringArray(origin.derived_refs) ?? []
|
|
121
|
+
};
|
|
122
|
+
}
|
|
104
123
|
function collectRelatedArtifacts(task, graph) {
|
|
105
124
|
const context = asRecord(task.execution?.context ?? {});
|
|
106
125
|
const relatedTaskIds = dedupe(asStringArray(context.tasks) ?? []);
|
|
@@ -195,9 +214,12 @@ function build_contract(task, graph, config) {
|
|
|
195
214
|
const taskContextFiles = asStringArray(executionContext.files) ?? [];
|
|
196
215
|
const taskContextTasks = asStringArray(executionContext.tasks) ?? [];
|
|
197
216
|
const acceptance_criteria = taskAcceptance(task);
|
|
217
|
+
const tests_required = taskTestsRequired(task);
|
|
218
|
+
const origin_refs = taskOriginRefs(task);
|
|
198
219
|
const related_task_artifacts = collectRelatedArtifacts(task, graph);
|
|
199
220
|
const output_requirements = [
|
|
200
221
|
...DEFAULT_OUTPUT_REQUIREMENTS,
|
|
222
|
+
...tests_required.length > 0 ? tests_required.map((entry) => `Required test: ${entry}`) : [],
|
|
201
223
|
...(task.artifacts?.produces ?? []).map((artifact) => {
|
|
202
224
|
if (artifact.path) {
|
|
203
225
|
return `Produce ${artifact.type} artifact at ${artifact.path}`;
|
|
@@ -229,6 +251,9 @@ function build_contract(task, graph, config) {
|
|
|
229
251
|
files: taskContextFiles,
|
|
230
252
|
tasks: taskContextTasks,
|
|
231
253
|
acceptance_criteria,
|
|
254
|
+
tests_required,
|
|
255
|
+
authority_refs: origin_refs.authority_refs,
|
|
256
|
+
derived_refs: origin_refs.derived_refs,
|
|
232
257
|
related_task_artifacts
|
|
233
258
|
},
|
|
234
259
|
output_requirements
|
|
@@ -628,7 +653,7 @@ function build_decomposition_prompt(input) {
|
|
|
628
653
|
return [
|
|
629
654
|
"You are a planning agent for COOP.",
|
|
630
655
|
"Decompose the idea into 2-5 implementation tasks.",
|
|
631
|
-
"Each task needs: title, type(feature|bug|chore|spike), priority(p0-p3), and
|
|
656
|
+
"Each task needs: title, type(feature|bug|chore|spike), priority(p0-p3), body, acceptance_criteria, and tests_required.",
|
|
632
657
|
`Idea ID: ${input.idea_id}`,
|
|
633
658
|
`Title: ${input.title}`,
|
|
634
659
|
"Body:",
|
|
@@ -647,12 +672,273 @@ async function decompose_idea_to_tasks(input, client) {
|
|
|
647
672
|
return drafts;
|
|
648
673
|
}
|
|
649
674
|
|
|
675
|
+
// src/refinement/refine.ts
|
|
676
|
+
function sentenceCase2(value) {
|
|
677
|
+
if (value.length === 0) return value;
|
|
678
|
+
return `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}`;
|
|
679
|
+
}
|
|
680
|
+
function nonEmptyLines2(input) {
|
|
681
|
+
return input.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
682
|
+
}
|
|
683
|
+
function extractBulletIdeas2(body) {
|
|
684
|
+
return nonEmptyLines2(body).filter((line) => /^[-*]\s+/.test(line)).map((line) => line.replace(/^[-*]\s+/, "").trim()).filter(Boolean).slice(0, 5);
|
|
685
|
+
}
|
|
686
|
+
function taskBodySections(title) {
|
|
687
|
+
return [
|
|
688
|
+
"## Objective",
|
|
689
|
+
title,
|
|
690
|
+
"",
|
|
691
|
+
"## Scope",
|
|
692
|
+
"- Define implementation boundaries",
|
|
693
|
+
"",
|
|
694
|
+
"## Constraints",
|
|
695
|
+
"- Preserve existing behavior unless explicitly changed",
|
|
696
|
+
"",
|
|
697
|
+
"## References",
|
|
698
|
+
"- Add source refs during refinement/apply",
|
|
699
|
+
"",
|
|
700
|
+
"## Refinement Notes",
|
|
701
|
+
"- Generated by COOP refinement fallback"
|
|
702
|
+
].join("\n");
|
|
703
|
+
}
|
|
704
|
+
function normalizeStringArray(value) {
|
|
705
|
+
if (!Array.isArray(value)) return void 0;
|
|
706
|
+
const entries = value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
|
|
707
|
+
return entries.length > 0 ? entries : void 0;
|
|
708
|
+
}
|
|
709
|
+
function normalizeProposal(value) {
|
|
710
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
const record = value;
|
|
714
|
+
const title = typeof record.title === "string" ? record.title.trim() : "";
|
|
715
|
+
if (!title) return null;
|
|
716
|
+
const action = record.action === "update" ? "update" : "create";
|
|
717
|
+
return {
|
|
718
|
+
action,
|
|
719
|
+
id: typeof record.id === "string" && record.id.trim() ? record.id.trim() : void 0,
|
|
720
|
+
target_id: typeof record.target_id === "string" && record.target_id.trim() ? record.target_id.trim() : void 0,
|
|
721
|
+
title,
|
|
722
|
+
type: record.type === "feature" || record.type === "bug" || record.type === "chore" || record.type === "spike" || record.type === "epic" ? record.type : void 0,
|
|
723
|
+
status: record.status === "todo" || record.status === "blocked" || record.status === "in_progress" || record.status === "in_review" || record.status === "done" || record.status === "canceled" ? record.status : void 0,
|
|
724
|
+
track: typeof record.track === "string" ? record.track.trim() || void 0 : void 0,
|
|
725
|
+
priority: record.priority === "p0" || record.priority === "p1" || record.priority === "p2" || record.priority === "p3" ? record.priority : void 0,
|
|
726
|
+
depends_on: normalizeStringArray(record.depends_on),
|
|
727
|
+
acceptance: normalizeStringArray(record.acceptance),
|
|
728
|
+
tests_required: normalizeStringArray(record.tests_required),
|
|
729
|
+
authority_refs: normalizeStringArray(record.authority_refs),
|
|
730
|
+
derived_refs: normalizeStringArray(record.derived_refs),
|
|
731
|
+
body: typeof record.body === "string" ? record.body : void 0
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
function normalizeDraft(value, mode, sourceId, sourceTitle) {
|
|
735
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
const record = value;
|
|
739
|
+
const proposalsRaw = Array.isArray(record.proposals) ? record.proposals : Array.isArray(record.tasks) ? record.tasks : [];
|
|
740
|
+
const proposals = proposalsRaw.map((entry) => normalizeProposal(entry)).filter((entry) => Boolean(entry));
|
|
741
|
+
if (proposals.length === 0) return null;
|
|
742
|
+
const summary = typeof record.summary === "string" && record.summary.trim() ? record.summary.trim() : `Refined ${mode} ${sourceId}`;
|
|
743
|
+
return {
|
|
744
|
+
kind: "refinement_draft",
|
|
745
|
+
version: 1,
|
|
746
|
+
mode,
|
|
747
|
+
source: {
|
|
748
|
+
entity_type: mode,
|
|
749
|
+
id: sourceId,
|
|
750
|
+
title: sourceTitle
|
|
751
|
+
},
|
|
752
|
+
summary,
|
|
753
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
754
|
+
proposals
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
function parseJsonObject(text) {
|
|
758
|
+
const trimmed = text.trim();
|
|
759
|
+
const candidates = [trimmed];
|
|
760
|
+
const fenced = trimmed.match(/```json\s*([\s\S]*?)```/i);
|
|
761
|
+
if (fenced?.[1]) {
|
|
762
|
+
candidates.push(fenced[1]);
|
|
763
|
+
}
|
|
764
|
+
const start = trimmed.indexOf("{");
|
|
765
|
+
const end = trimmed.lastIndexOf("}");
|
|
766
|
+
if (start >= 0 && end > start) {
|
|
767
|
+
candidates.push(trimmed.slice(start, end + 1));
|
|
768
|
+
}
|
|
769
|
+
for (const candidate of candidates) {
|
|
770
|
+
try {
|
|
771
|
+
const parsed = JSON.parse(candidate);
|
|
772
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
773
|
+
return parsed;
|
|
774
|
+
}
|
|
775
|
+
} catch {
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
function fallbackIdeaRefinement(input) {
|
|
781
|
+
const bullets = extractBulletIdeas2(input.body);
|
|
782
|
+
const tasks = bullets.length > 0 ? bullets.slice(0, 3).map((bullet, index) => ({
|
|
783
|
+
title: sentenceCase2(bullet),
|
|
784
|
+
body: taskBodySections(sentenceCase2(bullet)),
|
|
785
|
+
type: index === 0 ? "spike" : "feature",
|
|
786
|
+
priority: index === 0 ? "p1" : "p2",
|
|
787
|
+
track: "unassigned",
|
|
788
|
+
acceptance_criteria: [`${sentenceCase2(bullet)} is implemented as described`],
|
|
789
|
+
tests_required: ["Relevant integration or regression coverage is added"]
|
|
790
|
+
})) : [
|
|
791
|
+
{
|
|
792
|
+
title: `Define scope and acceptance for ${input.title}`,
|
|
793
|
+
body: taskBodySections(`Define scope and acceptance for ${input.title}`),
|
|
794
|
+
type: "spike",
|
|
795
|
+
priority: "p1",
|
|
796
|
+
track: "unassigned",
|
|
797
|
+
acceptance_criteria: [`Scope and acceptance are explicit for ${input.title}`],
|
|
798
|
+
tests_required: ["Planning review recorded"]
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
title: `Implement ${input.title}`,
|
|
802
|
+
body: taskBodySections(`Implement ${input.title}`),
|
|
803
|
+
type: "feature",
|
|
804
|
+
priority: "p1",
|
|
805
|
+
track: "unassigned",
|
|
806
|
+
acceptance_criteria: [`${input.title} is implemented end-to-end`],
|
|
807
|
+
tests_required: ["Automated test coverage added for the primary path"]
|
|
808
|
+
}
|
|
809
|
+
];
|
|
810
|
+
return {
|
|
811
|
+
kind: "refinement_draft",
|
|
812
|
+
version: 1,
|
|
813
|
+
mode: "idea",
|
|
814
|
+
source: {
|
|
815
|
+
entity_type: "idea",
|
|
816
|
+
id: input.idea_id,
|
|
817
|
+
title: input.title
|
|
818
|
+
},
|
|
819
|
+
summary: `Refined idea ${input.idea_id} into ${tasks.length} proposed task(s).`,
|
|
820
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
821
|
+
proposals: tasks.map((task) => ({
|
|
822
|
+
action: "create",
|
|
823
|
+
title: task.title,
|
|
824
|
+
type: task.type,
|
|
825
|
+
status: "todo",
|
|
826
|
+
track: task.track ?? "unassigned",
|
|
827
|
+
priority: task.priority,
|
|
828
|
+
acceptance: task.acceptance_criteria,
|
|
829
|
+
tests_required: task.tests_required,
|
|
830
|
+
body: task.body
|
|
831
|
+
}))
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
function fallbackTaskRefinement(input) {
|
|
835
|
+
const authorityRefs = input.task.origin?.authority_refs ?? [];
|
|
836
|
+
const derivedRefs = input.task.origin?.derived_refs ?? [];
|
|
837
|
+
const acceptance = input.task.acceptance && input.task.acceptance.length > 0 ? input.task.acceptance : [`${input.task.title} is complete and reviewable against its defined scope`];
|
|
838
|
+
const testsRequired = input.task.tests_required && input.task.tests_required.length > 0 ? input.task.tests_required : ["Automated regression coverage for the changed behavior"];
|
|
839
|
+
return {
|
|
840
|
+
kind: "refinement_draft",
|
|
841
|
+
version: 1,
|
|
842
|
+
mode: "task",
|
|
843
|
+
source: {
|
|
844
|
+
entity_type: "task",
|
|
845
|
+
id: input.task.id,
|
|
846
|
+
title: input.task.title
|
|
847
|
+
},
|
|
848
|
+
summary: `Refined task ${input.task.id} into an execution-ready update proposal.`,
|
|
849
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
850
|
+
proposals: [
|
|
851
|
+
{
|
|
852
|
+
action: "update",
|
|
853
|
+
target_id: input.task.id,
|
|
854
|
+
title: input.task.title,
|
|
855
|
+
type: input.task.type,
|
|
856
|
+
status: input.task.status,
|
|
857
|
+
track: input.task.track,
|
|
858
|
+
priority: input.task.priority,
|
|
859
|
+
depends_on: input.task.depends_on,
|
|
860
|
+
acceptance,
|
|
861
|
+
tests_required: testsRequired,
|
|
862
|
+
authority_refs: authorityRefs,
|
|
863
|
+
derived_refs: derivedRefs,
|
|
864
|
+
body: input.body?.trim() || taskBodySections(input.task.title)
|
|
865
|
+
}
|
|
866
|
+
]
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
function buildIdeaRefinementPrompt(input) {
|
|
870
|
+
return [
|
|
871
|
+
"You are a COOP planning agent.",
|
|
872
|
+
"Return ONLY JSON object with keys: summary, proposals.",
|
|
873
|
+
"proposals must be an array of task proposals.",
|
|
874
|
+
"Each proposal must include: action(create), title, type(feature|bug|chore|spike), priority(p0-p3), track, acceptance(string[]), tests_required(string[]), body.",
|
|
875
|
+
"Use depends_on only for existing task ids when necessary.",
|
|
876
|
+
`Idea ID: ${input.idea_id}`,
|
|
877
|
+
`Title: ${input.title}`,
|
|
878
|
+
"Body:",
|
|
879
|
+
input.body || "(empty)",
|
|
880
|
+
input.supplemental_context?.trim() ? ["Supplemental Context:", input.supplemental_context].join("\n") : ""
|
|
881
|
+
].filter(Boolean).join("\n");
|
|
882
|
+
}
|
|
883
|
+
function buildTaskRefinementPrompt(input) {
|
|
884
|
+
const authorityContext = (input.authority_context ?? []).map((entry) => `Ref: ${entry.ref}
|
|
885
|
+
${entry.content}`).join("\n\n---\n\n");
|
|
886
|
+
return [
|
|
887
|
+
"You are a COOP planning/refinement agent.",
|
|
888
|
+
"Return ONLY JSON object with keys: summary, proposals.",
|
|
889
|
+
"proposals must be an array with one update proposal for the current task.",
|
|
890
|
+
"The proposal must include: action(update), target_id, title, type, status, track, priority, depends_on, acceptance(string[]), tests_required(string[]), authority_refs(string[]), derived_refs(string[]), body.",
|
|
891
|
+
`Task ID: ${input.task.id}`,
|
|
892
|
+
`Title: ${input.task.title}`,
|
|
893
|
+
`Status: ${input.task.status}`,
|
|
894
|
+
`Type: ${input.task.type}`,
|
|
895
|
+
`Priority: ${input.task.priority ?? "p2"}`,
|
|
896
|
+
`Track: ${input.task.track ?? "unassigned"}`,
|
|
897
|
+
`Depends On: ${(input.task.depends_on ?? []).join(", ") || "-"}`,
|
|
898
|
+
"Task Body:",
|
|
899
|
+
input.body || "(empty)",
|
|
900
|
+
authorityContext ? ["Authority Context:", authorityContext].join("\n") : "",
|
|
901
|
+
input.supplemental_context?.trim() ? ["Supplemental Context:", input.supplemental_context].join("\n") : ""
|
|
902
|
+
].filter(Boolean).join("\n");
|
|
903
|
+
}
|
|
904
|
+
async function refine_idea_to_draft(input, client) {
|
|
905
|
+
const prompt = buildIdeaRefinementPrompt(input);
|
|
906
|
+
if (!client) {
|
|
907
|
+
return fallbackIdeaRefinement(input);
|
|
908
|
+
}
|
|
909
|
+
const result = await client.refineIdea(prompt, input);
|
|
910
|
+
return result ?? fallbackIdeaRefinement(input);
|
|
911
|
+
}
|
|
912
|
+
async function refine_task_to_draft(input, client) {
|
|
913
|
+
const prompt = buildTaskRefinementPrompt(input);
|
|
914
|
+
if (!client) {
|
|
915
|
+
return fallbackTaskRefinement(input);
|
|
916
|
+
}
|
|
917
|
+
const result = await client.refineTask(prompt, input);
|
|
918
|
+
return result ?? fallbackTaskRefinement(input);
|
|
919
|
+
}
|
|
920
|
+
function parseRefinementDraftResponse(text, mode, sourceId, sourceTitle) {
|
|
921
|
+
const parsed = parseJsonObject(text);
|
|
922
|
+
if (!parsed) {
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
return normalizeDraft(parsed, mode, sourceId, sourceTitle);
|
|
926
|
+
}
|
|
927
|
+
|
|
650
928
|
// src/providers/config.ts
|
|
651
929
|
var DEFAULT_MODELS = {
|
|
652
930
|
openai: "gpt-5-mini",
|
|
653
931
|
anthropic: "claude-3-5-sonnet-latest",
|
|
654
932
|
gemini: "gemini-2.0-flash",
|
|
655
|
-
ollama: "llama3.2"
|
|
933
|
+
ollama: "llama3.2",
|
|
934
|
+
codex_cli: "gpt-5-codex",
|
|
935
|
+
claude_cli: "sonnet",
|
|
936
|
+
gemini_cli: "gemini-2.5-pro"
|
|
937
|
+
};
|
|
938
|
+
var DEFAULT_COMMAND = {
|
|
939
|
+
codex_cli: "codex",
|
|
940
|
+
claude_cli: "claude",
|
|
941
|
+
gemini_cli: "gemini"
|
|
656
942
|
};
|
|
657
943
|
var DEFAULT_KEY_ENV = {
|
|
658
944
|
openai: "OPENAI_API_KEY",
|
|
@@ -682,7 +968,7 @@ function asFinite(value) {
|
|
|
682
968
|
}
|
|
683
969
|
function readProvider(value) {
|
|
684
970
|
const normalized = asString(value)?.toLowerCase();
|
|
685
|
-
if (normalized === "openai" || normalized === "anthropic" || normalized === "gemini" || normalized === "ollama" || normalized === "mock") {
|
|
971
|
+
if (normalized === "openai" || normalized === "anthropic" || normalized === "gemini" || normalized === "ollama" || normalized === "mock" || normalized === "codex_cli" || normalized === "claude_cli" || normalized === "gemini_cli") {
|
|
686
972
|
return normalized;
|
|
687
973
|
}
|
|
688
974
|
return "mock";
|
|
@@ -703,15 +989,34 @@ function resolve_provider_config(config) {
|
|
|
703
989
|
timeout_ms: 6e4
|
|
704
990
|
};
|
|
705
991
|
}
|
|
992
|
+
if (provider === "codex_cli" || provider === "claude_cli" || provider === "gemini_cli") {
|
|
993
|
+
const section2 = lookupProviderSection(ai, provider);
|
|
994
|
+
const model2 = asString(section2.model) ?? asString(ai.model) ?? DEFAULT_MODELS[provider];
|
|
995
|
+
const timeout_ms2 = asFinite(section2.timeout_ms) ?? asFinite(ai.timeout_ms) ?? 3e5;
|
|
996
|
+
const command = asString(section2.command) ?? DEFAULT_COMMAND[provider];
|
|
997
|
+
const argsRaw = Array.isArray(section2.args) ? section2.args : Array.isArray(ai.args) ? ai.args : [];
|
|
998
|
+
const args = argsRaw.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
999
|
+
return {
|
|
1000
|
+
provider,
|
|
1001
|
+
model: model2,
|
|
1002
|
+
command,
|
|
1003
|
+
args,
|
|
1004
|
+
temperature: 0.2,
|
|
1005
|
+
max_output_tokens: 4096,
|
|
1006
|
+
timeout_ms: timeout_ms2
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
706
1009
|
const section = lookupProviderSection(ai, provider);
|
|
707
|
-
const
|
|
708
|
-
const
|
|
1010
|
+
const cloudProvider = provider;
|
|
1011
|
+
const model = asString(section.model) ?? asString(ai.model) ?? DEFAULT_MODELS[cloudProvider];
|
|
1012
|
+
const base_url = asString(section.base_url) ?? asString(ai.base_url) ?? DEFAULT_BASE_URL[cloudProvider];
|
|
709
1013
|
const temperature = asFinite(section.temperature) ?? asFinite(ai.temperature) ?? 0.2;
|
|
710
1014
|
const max_output_tokens = asFinite(section.max_output_tokens) ?? asFinite(ai.max_output_tokens) ?? 1024;
|
|
711
1015
|
const timeout_ms = asFinite(section.timeout_ms) ?? asFinite(ai.timeout_ms) ?? 6e4;
|
|
712
1016
|
let api_key;
|
|
713
1017
|
if (provider !== "ollama") {
|
|
714
|
-
const
|
|
1018
|
+
const keyedProvider = provider;
|
|
1019
|
+
const envName = asString(section.api_key_env) ?? asString(ai.api_key_env) ?? DEFAULT_KEY_ENV[keyedProvider];
|
|
715
1020
|
const envValue = envName ? asString(process.env[envName]) : null;
|
|
716
1021
|
api_key = asString(section.api_key) ?? asString(ai.api_key) ?? envValue ?? void 0;
|
|
717
1022
|
if (!api_key) {
|
|
@@ -731,6 +1036,250 @@ function resolve_provider_config(config) {
|
|
|
731
1036
|
};
|
|
732
1037
|
}
|
|
733
1038
|
|
|
1039
|
+
// src/providers/cli.ts
|
|
1040
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
1041
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
1042
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
1043
|
+
var import_node_child_process2 = require("child_process");
|
|
1044
|
+
function defaultRunner(invocation) {
|
|
1045
|
+
const result = (0, import_node_child_process2.spawnSync)(invocation.command, invocation.args, {
|
|
1046
|
+
cwd: invocation.cwd,
|
|
1047
|
+
encoding: "utf8",
|
|
1048
|
+
input: invocation.input,
|
|
1049
|
+
timeout: invocation.timeout_ms,
|
|
1050
|
+
windowsHide: true
|
|
1051
|
+
});
|
|
1052
|
+
return {
|
|
1053
|
+
status: result.status,
|
|
1054
|
+
stdout: result.stdout ?? "",
|
|
1055
|
+
stderr: result.stderr ?? "",
|
|
1056
|
+
error: result.error
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
function asText(result) {
|
|
1060
|
+
return [result.stdout, result.stderr].map((value) => value.trim()).filter(Boolean).join("\n").trim();
|
|
1061
|
+
}
|
|
1062
|
+
function requireSuccess(provider, result) {
|
|
1063
|
+
if ((result.status ?? 1) === 0 && !result.error) {
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
const details = asText(result) || result.error?.message || "unknown error";
|
|
1067
|
+
throw new Error(`${provider} execution failed: ${details}`);
|
|
1068
|
+
}
|
|
1069
|
+
function tempOutputFile(prefix) {
|
|
1070
|
+
return import_node_path3.default.join(import_node_os.default.tmpdir(), `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}.txt`);
|
|
1071
|
+
}
|
|
1072
|
+
function combinedPrompt(input) {
|
|
1073
|
+
return [input.system.trim(), input.prompt.trim()].filter(Boolean).join("\n\n");
|
|
1074
|
+
}
|
|
1075
|
+
function completionInvocation(config, input, cwd) {
|
|
1076
|
+
if (config.provider === "codex_cli") {
|
|
1077
|
+
const outputFile = tempOutputFile("coop-codex-complete");
|
|
1078
|
+
return {
|
|
1079
|
+
command: config.command ?? "codex",
|
|
1080
|
+
args: [
|
|
1081
|
+
"exec",
|
|
1082
|
+
"-C",
|
|
1083
|
+
cwd,
|
|
1084
|
+
"--skip-git-repo-check",
|
|
1085
|
+
"--sandbox",
|
|
1086
|
+
"read-only",
|
|
1087
|
+
...config.model ? ["--model", config.model] : [],
|
|
1088
|
+
"--output-last-message",
|
|
1089
|
+
outputFile,
|
|
1090
|
+
...config.args ?? [],
|
|
1091
|
+
"-"
|
|
1092
|
+
],
|
|
1093
|
+
cwd,
|
|
1094
|
+
input: combinedPrompt(input),
|
|
1095
|
+
timeout_ms: config.timeout_ms
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
if (config.provider === "claude_cli") {
|
|
1099
|
+
return {
|
|
1100
|
+
command: config.command ?? "claude",
|
|
1101
|
+
args: [
|
|
1102
|
+
"-p",
|
|
1103
|
+
combinedPrompt(input),
|
|
1104
|
+
"--output-format",
|
|
1105
|
+
"text",
|
|
1106
|
+
"--permission-mode",
|
|
1107
|
+
"plan",
|
|
1108
|
+
...config.model ? ["--model", config.model] : [],
|
|
1109
|
+
...config.args ?? []
|
|
1110
|
+
],
|
|
1111
|
+
cwd,
|
|
1112
|
+
timeout_ms: config.timeout_ms
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
return {
|
|
1116
|
+
command: config.command ?? "gemini",
|
|
1117
|
+
args: [
|
|
1118
|
+
"-p",
|
|
1119
|
+
combinedPrompt(input),
|
|
1120
|
+
"--output-format",
|
|
1121
|
+
"text",
|
|
1122
|
+
"--approval-mode",
|
|
1123
|
+
"plan",
|
|
1124
|
+
...config.model ? ["--model", config.model] : [],
|
|
1125
|
+
...config.args ?? []
|
|
1126
|
+
],
|
|
1127
|
+
cwd,
|
|
1128
|
+
timeout_ms: config.timeout_ms
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
function readCodexOutput(invocation) {
|
|
1132
|
+
const index = invocation.args.indexOf("--output-last-message");
|
|
1133
|
+
const outputFile = index >= 0 ? invocation.args[index + 1] : void 0;
|
|
1134
|
+
if (!outputFile) return null;
|
|
1135
|
+
if (!import_node_fs2.default.existsSync(outputFile)) return null;
|
|
1136
|
+
try {
|
|
1137
|
+
return import_node_fs2.default.readFileSync(outputFile, "utf8").trim();
|
|
1138
|
+
} finally {
|
|
1139
|
+
import_node_fs2.default.rmSync(outputFile, { force: true });
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
var CliCompletionProvider = class {
|
|
1143
|
+
name;
|
|
1144
|
+
config;
|
|
1145
|
+
cwd;
|
|
1146
|
+
runner;
|
|
1147
|
+
constructor(config, options = {}) {
|
|
1148
|
+
this.name = config.provider;
|
|
1149
|
+
this.config = config;
|
|
1150
|
+
this.cwd = import_node_path3.default.resolve(options.cwd ?? process.cwd());
|
|
1151
|
+
this.runner = options.runner ?? defaultRunner;
|
|
1152
|
+
}
|
|
1153
|
+
async complete(input) {
|
|
1154
|
+
const invocation = completionInvocation(this.config, input, this.cwd);
|
|
1155
|
+
const result = this.runner(invocation);
|
|
1156
|
+
requireSuccess(this.name, result);
|
|
1157
|
+
const codexOutput = this.config.provider === "codex_cli" ? readCodexOutput(invocation) : null;
|
|
1158
|
+
return {
|
|
1159
|
+
text: codexOutput || result.stdout.trim() || result.stderr.trim()
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
function buildExecutionPrompt(task, contract, step, prompt, mode) {
|
|
1164
|
+
const sections = [
|
|
1165
|
+
mode === "execute" ? "You are executing a COOP task inside the repo. Make the necessary file changes, run allowed commands when needed, and finish with a concise summary." : "You are reviewing a COOP task inside the repo. Inspect the current workspace and return concise review findings only.",
|
|
1166
|
+
`Task: ${task.id} - ${task.title}`,
|
|
1167
|
+
`Goal: ${contract.goal}`,
|
|
1168
|
+
`Step: ${step.step} (${step.action})`,
|
|
1169
|
+
contract.context.acceptance_criteria.length > 0 ? `Acceptance: ${contract.context.acceptance_criteria.join(" | ")}` : "",
|
|
1170
|
+
contract.context.tests_required.length > 0 ? `Tests Required: ${contract.context.tests_required.join(" | ")}` : "",
|
|
1171
|
+
contract.context.authority_refs.length > 0 ? `Authority Refs: ${contract.context.authority_refs.join(" | ")}` : "",
|
|
1172
|
+
contract.context.derived_refs.length > 0 ? `Derived Refs: ${contract.context.derived_refs.join(" | ")}` : "",
|
|
1173
|
+
contract.permissions.read_paths.length > 0 ? `Allowed Read Paths: ${contract.permissions.read_paths.join(", ")}` : "",
|
|
1174
|
+
contract.permissions.write_paths.length > 0 ? `Allowed Write Paths: ${contract.permissions.write_paths.join(", ")}` : "",
|
|
1175
|
+
contract.permissions.allowed_commands.length > 0 ? `Allowed Commands: ${contract.permissions.allowed_commands.join(", ")}` : "",
|
|
1176
|
+
contract.permissions.forbidden_commands.length > 0 ? `Forbidden Commands: ${contract.permissions.forbidden_commands.join(", ")}` : "",
|
|
1177
|
+
prompt
|
|
1178
|
+
].filter(Boolean);
|
|
1179
|
+
return sections.join("\n\n");
|
|
1180
|
+
}
|
|
1181
|
+
function countGitChanges(repoRoot, runner, timeoutMs) {
|
|
1182
|
+
const result = runner({
|
|
1183
|
+
command: "git",
|
|
1184
|
+
args: ["status", "--porcelain"],
|
|
1185
|
+
cwd: repoRoot,
|
|
1186
|
+
timeout_ms: timeoutMs
|
|
1187
|
+
});
|
|
1188
|
+
if ((result.status ?? 1) !== 0) return 0;
|
|
1189
|
+
return result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).length;
|
|
1190
|
+
}
|
|
1191
|
+
function executionInvocation(config, prompt, repoRoot, mode) {
|
|
1192
|
+
if (config.provider === "codex_cli") {
|
|
1193
|
+
const outputFile = tempOutputFile("coop-codex-run");
|
|
1194
|
+
return {
|
|
1195
|
+
command: config.command ?? "codex",
|
|
1196
|
+
args: [
|
|
1197
|
+
"exec",
|
|
1198
|
+
"-C",
|
|
1199
|
+
repoRoot,
|
|
1200
|
+
"--skip-git-repo-check",
|
|
1201
|
+
"--sandbox",
|
|
1202
|
+
mode === "execute" ? "workspace-write" : "read-only",
|
|
1203
|
+
...mode === "execute" ? ["--full-auto"] : [],
|
|
1204
|
+
...config.model ? ["--model", config.model] : [],
|
|
1205
|
+
"--output-last-message",
|
|
1206
|
+
outputFile,
|
|
1207
|
+
...config.args ?? [],
|
|
1208
|
+
"-"
|
|
1209
|
+
],
|
|
1210
|
+
cwd: repoRoot,
|
|
1211
|
+
input: prompt,
|
|
1212
|
+
timeout_ms: config.timeout_ms
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
if (config.provider === "claude_cli") {
|
|
1216
|
+
return {
|
|
1217
|
+
command: config.command ?? "claude",
|
|
1218
|
+
args: [
|
|
1219
|
+
"-p",
|
|
1220
|
+
prompt,
|
|
1221
|
+
"--output-format",
|
|
1222
|
+
"text",
|
|
1223
|
+
"--permission-mode",
|
|
1224
|
+
mode === "execute" ? "bypassPermissions" : "plan",
|
|
1225
|
+
...config.model ? ["--model", config.model] : [],
|
|
1226
|
+
...config.args ?? []
|
|
1227
|
+
],
|
|
1228
|
+
cwd: repoRoot,
|
|
1229
|
+
timeout_ms: config.timeout_ms
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
return {
|
|
1233
|
+
command: config.command ?? "gemini",
|
|
1234
|
+
args: [
|
|
1235
|
+
"-p",
|
|
1236
|
+
prompt,
|
|
1237
|
+
"--output-format",
|
|
1238
|
+
"text",
|
|
1239
|
+
"--approval-mode",
|
|
1240
|
+
mode === "execute" ? "yolo" : "plan",
|
|
1241
|
+
...config.model ? ["--model", config.model] : [],
|
|
1242
|
+
...config.args ?? []
|
|
1243
|
+
],
|
|
1244
|
+
cwd: repoRoot,
|
|
1245
|
+
timeout_ms: config.timeout_ms
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
var CliAgentClient = class {
|
|
1249
|
+
config;
|
|
1250
|
+
repoRoot;
|
|
1251
|
+
runner;
|
|
1252
|
+
constructor(config, options = {}) {
|
|
1253
|
+
this.config = config;
|
|
1254
|
+
this.repoRoot = import_node_path3.default.resolve(options.cwd ?? process.cwd());
|
|
1255
|
+
this.runner = options.runner ?? defaultRunner;
|
|
1256
|
+
}
|
|
1257
|
+
async generate(input) {
|
|
1258
|
+
const before = countGitChanges(this.repoRoot, this.runner, this.config.timeout_ms);
|
|
1259
|
+
const prompt = buildExecutionPrompt(input.task, input.contract, input.step, input.prompt, "execute");
|
|
1260
|
+
const invocation = executionInvocation(this.config, prompt, this.repoRoot, "execute");
|
|
1261
|
+
const result = this.runner(invocation);
|
|
1262
|
+
requireSuccess(this.config.provider, result);
|
|
1263
|
+
const after = countGitChanges(this.repoRoot, this.runner, this.config.timeout_ms);
|
|
1264
|
+
const codexOutput = this.config.provider === "codex_cli" ? readCodexOutput(invocation) : null;
|
|
1265
|
+
return {
|
|
1266
|
+
summary: codexOutput || result.stdout.trim() || result.stderr.trim() || `Completed ${input.step.step}.`,
|
|
1267
|
+
file_changes: Math.max(0, after - before)
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
async review(input) {
|
|
1271
|
+
const prompt = buildExecutionPrompt(input.task, input.contract, input.step, input.prompt, "review");
|
|
1272
|
+
const invocation = executionInvocation(this.config, prompt, this.repoRoot, "review");
|
|
1273
|
+
const result = this.runner(invocation);
|
|
1274
|
+
requireSuccess(this.config.provider, result);
|
|
1275
|
+
const codexOutput = this.config.provider === "codex_cli" ? readCodexOutput(invocation) : null;
|
|
1276
|
+
return {
|
|
1277
|
+
summary: codexOutput || result.stdout.trim() || result.stderr.trim() || `Reviewed ${input.step.step}.`,
|
|
1278
|
+
file_changes: 0
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
};
|
|
1282
|
+
|
|
734
1283
|
// src/providers/http.ts
|
|
735
1284
|
async function post_json(url, init) {
|
|
736
1285
|
const controller = new AbortController();
|
|
@@ -927,6 +1476,10 @@ function create_provider(config) {
|
|
|
927
1476
|
return new GeminiProvider(resolved);
|
|
928
1477
|
case "ollama":
|
|
929
1478
|
return new OllamaProvider(resolved);
|
|
1479
|
+
case "codex_cli":
|
|
1480
|
+
case "claude_cli":
|
|
1481
|
+
case "gemini_cli":
|
|
1482
|
+
return new CliCompletionProvider(resolved);
|
|
930
1483
|
case "mock":
|
|
931
1484
|
default:
|
|
932
1485
|
return new MockProvider();
|
|
@@ -978,13 +1531,23 @@ function toTaskDrafts(value) {
|
|
|
978
1531
|
}
|
|
979
1532
|
return out;
|
|
980
1533
|
}
|
|
1534
|
+
function refinementSystemPrompt(mode) {
|
|
1535
|
+
if (mode === "idea") {
|
|
1536
|
+
return "Return ONLY JSON object with keys summary and proposals. proposals must be an array of task proposals with action(create), title, type, priority, track, acceptance, tests_required, body.";
|
|
1537
|
+
}
|
|
1538
|
+
return "Return ONLY JSON object with keys summary and proposals. proposals must be an array with one update proposal containing action(update), target_id, title, type, status, track, priority, depends_on, acceptance, tests_required, authority_refs, derived_refs, body.";
|
|
1539
|
+
}
|
|
981
1540
|
function asAgentResponse(text, tokens) {
|
|
982
1541
|
return {
|
|
983
1542
|
summary: text.trim(),
|
|
984
1543
|
tokens_used: tokens
|
|
985
1544
|
};
|
|
986
1545
|
}
|
|
987
|
-
function create_provider_agent_client(config) {
|
|
1546
|
+
function create_provider_agent_client(config, runtime = {}) {
|
|
1547
|
+
const resolved = resolve_provider_config(config);
|
|
1548
|
+
if (resolved.provider === "codex_cli" || resolved.provider === "claude_cli" || resolved.provider === "gemini_cli") {
|
|
1549
|
+
return new CliAgentClient(resolved, runtime);
|
|
1550
|
+
}
|
|
988
1551
|
const provider = create_provider(config);
|
|
989
1552
|
return {
|
|
990
1553
|
async generate(input) {
|
|
@@ -1019,20 +1582,47 @@ function create_provider_idea_decomposer(config) {
|
|
|
1019
1582
|
}
|
|
1020
1583
|
};
|
|
1021
1584
|
}
|
|
1585
|
+
function create_provider_refinement_client(config) {
|
|
1586
|
+
const provider = create_provider(config);
|
|
1587
|
+
return {
|
|
1588
|
+
async refineIdea(prompt, input) {
|
|
1589
|
+
const result = await provider.complete({
|
|
1590
|
+
system: refinementSystemPrompt("idea"),
|
|
1591
|
+
prompt
|
|
1592
|
+
});
|
|
1593
|
+
return parseRefinementDraftResponse(result.text, "idea", input.idea_id, input.title);
|
|
1594
|
+
},
|
|
1595
|
+
async refineTask(prompt, input) {
|
|
1596
|
+
const result = await provider.complete({
|
|
1597
|
+
system: refinementSystemPrompt("task"),
|
|
1598
|
+
prompt
|
|
1599
|
+
});
|
|
1600
|
+
return parseRefinementDraftResponse(result.text, "task", input.task.id, input.task.title);
|
|
1601
|
+
}
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1022
1604
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1023
1605
|
0 && (module.exports = {
|
|
1606
|
+
CliAgentClient,
|
|
1607
|
+
CliCompletionProvider,
|
|
1608
|
+
buildIdeaRefinementPrompt,
|
|
1609
|
+
buildTaskRefinementPrompt,
|
|
1024
1610
|
build_contract,
|
|
1025
1611
|
build_decomposition_prompt,
|
|
1026
1612
|
constraint_violation_reasons,
|
|
1027
1613
|
create_provider,
|
|
1028
1614
|
create_provider_agent_client,
|
|
1029
1615
|
create_provider_idea_decomposer,
|
|
1616
|
+
create_provider_refinement_client,
|
|
1030
1617
|
create_run,
|
|
1031
1618
|
decompose_idea_to_tasks,
|
|
1032
1619
|
enforce_constraints,
|
|
1033
1620
|
execute_task,
|
|
1034
1621
|
finalize_run,
|
|
1035
1622
|
log_step,
|
|
1623
|
+
parseRefinementDraftResponse,
|
|
1624
|
+
refine_idea_to_draft,
|
|
1625
|
+
refine_task_to_draft,
|
|
1036
1626
|
resolve_provider_config,
|
|
1037
1627
|
select_agent,
|
|
1038
1628
|
validate_command,
|