karajan-code 1.49.0 → 1.50.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.
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Triage stage logic.
3
+ * Extracted from pre-loop-stages.js for maintainability.
4
+ */
5
+
6
+ import { TriageRole } from "../../roles/triage-role.js";
7
+ import { addCheckpoint } from "../../session-store.js";
8
+ import { emitProgress, makeEvent } from "../../utils/events.js";
9
+ import { selectModelsForRoles } from "../../utils/model-selector.js";
10
+ import { createStallDetector } from "../../utils/stall-detector.js";
11
+
12
+ const ROLE_NAMES = ["planner", "researcher", "architect", "refactorer", "reviewer", "tester", "security", "impeccable"];
13
+
14
+ function buildRoleOverrides(recommendedRoles, pipelineConfig) {
15
+ const overrides = {};
16
+ for (const role of ROLE_NAMES) {
17
+ overrides[`${role}Enabled`] = recommendedRoles.has(role) || Boolean(pipelineConfig[role]?.enabled);
18
+ }
19
+ return overrides;
20
+ }
21
+
22
+ function applyModelSelection(triageOutput, config, emitter, eventBase) {
23
+ if (!triageOutput.ok || !config?.model_selection?.enabled) return null;
24
+ const level = triageOutput.result?.level;
25
+ if (!level) return null;
26
+
27
+ const { modelOverrides, reasoning } = selectModelsForRoles({ level, config });
28
+ for (const [role, model] of Object.entries(modelOverrides)) {
29
+ if (config.roles?.[role] && !config.roles[role].model) {
30
+ config.roles[role].model = model;
31
+ }
32
+ }
33
+ const modelSelection = { modelOverrides, reasoning };
34
+ emitProgress(
35
+ emitter,
36
+ makeEvent("model-selection:applied", { ...eventBase, stage: "triage" }, {
37
+ message: "Smart model selection applied",
38
+ detail: modelSelection
39
+ })
40
+ );
41
+ return modelSelection;
42
+ }
43
+
44
+ export async function runTriageStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget }) {
45
+ logger.setContext({ iteration: 0, stage: "triage" });
46
+
47
+ const triageProvider = config?.roles?.triage?.provider || coderRole.provider;
48
+ emitProgress(
49
+ emitter,
50
+ makeEvent("triage:start", { ...eventBase, stage: "triage" }, {
51
+ message: "Triage classifying task complexity",
52
+ detail: { provider: triageProvider, executorType: "agent" }
53
+ })
54
+ );
55
+
56
+ const triageOnOutput = ({ stream, line }) => {
57
+ emitProgress(emitter, makeEvent("agent:output", { ...eventBase, stage: "triage" }, {
58
+ message: line,
59
+ detail: { stream, agent: triageProvider }
60
+ }));
61
+ };
62
+ const triageStall = createStallDetector({
63
+ onOutput: triageOnOutput, emitter, eventBase, stage: "triage", provider: triageProvider
64
+ });
65
+
66
+ const triage = new TriageRole({ config, logger, emitter });
67
+ await triage.init({ task: session.task, sessionId: session.id, iteration: 0 });
68
+ const triageStart = Date.now();
69
+ let triageOutput;
70
+ try {
71
+ triageOutput = await triage.run({ task: session.task, onOutput: triageStall.onOutput });
72
+ } catch (err) {
73
+ logger.warn(`Triage threw: ${err.message}`);
74
+ triageOutput = { ok: false, summary: `Triage error: ${err.message}`, result: { error: err.message } };
75
+ } finally {
76
+ triageStall.stop();
77
+ }
78
+ trackBudget({
79
+ role: "triage",
80
+ provider: config?.roles?.triage?.provider || coderRole.provider,
81
+ model: config?.roles?.triage?.model || coderRole.model,
82
+ result: triageOutput,
83
+ duration_ms: Date.now() - triageStart
84
+ });
85
+
86
+ await addCheckpoint(session, {
87
+ stage: "triage",
88
+ iteration: 0,
89
+ ok: triageOutput.ok,
90
+ provider: config?.roles?.triage?.provider || coderRole.provider,
91
+ model: config?.roles?.triage?.model || coderRole.model || null
92
+ });
93
+
94
+ const recommendedRoles = new Set(triageOutput.result?.roles || []);
95
+ const roleOverrides = triageOutput.ok
96
+ ? buildRoleOverrides(recommendedRoles, config.pipeline || {})
97
+ : {};
98
+
99
+ const shouldDecompose = triageOutput.result?.shouldDecompose || false;
100
+ const subtasks = triageOutput.result?.subtasks || [];
101
+
102
+ const stageResult = {
103
+ ok: triageOutput.ok,
104
+ level: triageOutput.result?.level || null,
105
+ roles: Array.from(recommendedRoles),
106
+ reasoning: triageOutput.result?.reasoning || null,
107
+ taskType: triageOutput.result?.taskType || "sw",
108
+ shouldDecompose,
109
+ subtasks
110
+ };
111
+
112
+ const modelSelection = applyModelSelection(triageOutput, config, emitter, eventBase);
113
+ if (modelSelection) {
114
+ stageResult.modelSelection = modelSelection;
115
+ }
116
+
117
+ if (shouldDecompose && subtasks.length > 0) {
118
+ emitProgress(
119
+ emitter,
120
+ makeEvent("triage:decompose", { ...eventBase, stage: "triage" }, {
121
+ message: `Task decomposition recommended: ${subtasks.length} subtask${subtasks.length === 1 ? "" : "s"}`,
122
+ detail: { shouldDecompose, subtasks }
123
+ })
124
+ );
125
+ }
126
+
127
+ emitProgress(
128
+ emitter,
129
+ makeEvent("triage:end", { ...eventBase, stage: "triage" }, {
130
+ status: triageOutput.ok ? "ok" : "fail",
131
+ message: triageOutput.ok ? "Triage completed" : `Triage failed: ${triageOutput.summary}`,
132
+ detail: { ...stageResult, provider: triageProvider, executorType: "agent" }
133
+ })
134
+ );
135
+
136
+ return { roleOverrides, stageResult };
137
+ }