@jmanuelcorral/openteam 0.17.6 → 0.18.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/README.es.md +4 -4
- package/README.md +4 -4
- package/dist/certificates/graph-release-certificate.json +2 -2
- package/dist/certificates/graph-shadow-certificate.json +2 -2
- package/dist/cli.js +893 -2537
- package/dist/commands/dispatch.d.ts +2 -135
- package/dist/commands/dispatch.d.ts.map +1 -1
- package/dist/config/schema.d.ts +2 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6151 -6398
- package/dist/opencodeArtifacts/orchestratorAgent.d.ts.map +1 -1
- package/dist/orchestrator/coordinator.d.ts +39 -1
- package/dist/orchestrator/coordinator.d.ts.map +1 -1
- package/dist/orchestrator/nodeIteration.d.ts +36 -0
- package/dist/orchestrator/nodeIteration.d.ts.map +1 -0
- package/dist/orchestrator/outputContract.d.ts +117 -0
- package/dist/orchestrator/outputContract.d.ts.map +1 -0
- package/dist/orchestrator/reportArtifactProbe.d.ts +40 -0
- package/dist/orchestrator/reportArtifactProbe.d.ts.map +1 -0
- package/dist/orchestrator/reportArtifactReader.d.ts +36 -0
- package/dist/orchestrator/reportArtifactReader.d.ts.map +1 -0
- package/dist/orchestrator/reportDurability.d.ts +70 -0
- package/dist/orchestrator/reportDurability.d.ts.map +1 -0
- package/dist/orchestrator/subsessions.d.ts +1 -1
- package/dist/orchestrator/subsessions.d.ts.map +1 -1
- package/dist/plugin/orchestrateTool.d.ts +59 -1
- package/dist/plugin/orchestrateTool.d.ts.map +1 -1
- package/dist/telemetry/diagnostics.d.ts +1 -1
- package/dist/telemetry/diagnostics.d.ts.map +1 -1
- package/dist/telemetry/events.d.ts +14 -0
- package/dist/telemetry/events.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/orchestrator/ralphLoop.d.ts +0 -57
- package/dist/orchestrator/ralphLoop.d.ts.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
confirm as clackConfirm,
|
|
13
13
|
isCancel as clackIsCancel
|
|
14
14
|
} from "@clack/prompts";
|
|
15
|
-
import { createOpencodeClient } from "@opencode-ai/sdk";
|
|
16
15
|
|
|
17
16
|
// src/cli/consoleServe.ts
|
|
18
17
|
import { randomBytes } from "node:crypto";
|
|
@@ -48,65 +47,6 @@ import { z as z2 } from "zod";
|
|
|
48
47
|
|
|
49
48
|
// src/orchestrator/roles.ts
|
|
50
49
|
import { z } from "zod";
|
|
51
|
-
|
|
52
|
-
// src/capabilities/select.ts
|
|
53
|
-
var BLENDED_COST_INPUT_WEIGHT = 0.75;
|
|
54
|
-
var BLENDED_COST_OUTPUT_WEIGHT = 1 - BLENDED_COST_INPUT_WEIGHT;
|
|
55
|
-
var availabilityRank = {
|
|
56
|
-
available: 0,
|
|
57
|
-
degraded: 1,
|
|
58
|
-
unavailable: 2
|
|
59
|
-
};
|
|
60
|
-
var tierRequirements = {
|
|
61
|
-
trivial: { reasoning: 0, code: 1, context: 8000 },
|
|
62
|
-
simple: { reasoning: 1, code: 2, context: 16000 },
|
|
63
|
-
moderate: { reasoning: 3, code: 3, context: 32000 },
|
|
64
|
-
hard: { reasoning: 4, code: 5, context: 64000 }
|
|
65
|
-
};
|
|
66
|
-
function deriveRequirement(tier, task) {
|
|
67
|
-
const base = tierRequirements[tier];
|
|
68
|
-
const estimatedContext = (task.estimatedInputTokens ?? 0) + (task.estimatedOutputTokens ?? 0);
|
|
69
|
-
const contextFromPrompt = Math.ceil(task.promptChars / 4);
|
|
70
|
-
const minContextWindow = Math.max(base.context, estimatedContext, contextFromPrompt);
|
|
71
|
-
const codeBump = task.hasCode ? 1 : 0;
|
|
72
|
-
return {
|
|
73
|
-
minReasoningTier: base.reasoning,
|
|
74
|
-
minCodeQualityTier: Math.min(5, base.code + codeBump),
|
|
75
|
-
minContextWindow,
|
|
76
|
-
needsTools: task.requiresTools === true,
|
|
77
|
-
needsVision: task.requiresVision === true
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
function blendedCostPer1M(profile) {
|
|
81
|
-
return profile.costPer1M.inputUSD * BLENDED_COST_INPUT_WEIGHT + profile.costPer1M.outputUSD * BLENDED_COST_OUTPUT_WEIGHT;
|
|
82
|
-
}
|
|
83
|
-
function modelKey(profile) {
|
|
84
|
-
return `${profile.ref.providerID}/${profile.ref.modelID}`;
|
|
85
|
-
}
|
|
86
|
-
function compareCapableFrontier(req, left, right) {
|
|
87
|
-
const costDelta = blendedCostPer1M(left) - blendedCostPer1M(right);
|
|
88
|
-
if (costDelta !== 0) {
|
|
89
|
-
return costDelta;
|
|
90
|
-
}
|
|
91
|
-
const availabilityDelta = availabilityRank[left.availability] - availabilityRank[right.availability];
|
|
92
|
-
if (availabilityDelta !== 0) {
|
|
93
|
-
return availabilityDelta;
|
|
94
|
-
}
|
|
95
|
-
const reasoningWasteDelta = left.reasoningTier - req.minReasoningTier - (right.reasoningTier - req.minReasoningTier);
|
|
96
|
-
if (reasoningWasteDelta !== 0) {
|
|
97
|
-
return reasoningWasteDelta;
|
|
98
|
-
}
|
|
99
|
-
return modelKey(left).localeCompare(modelKey(right));
|
|
100
|
-
}
|
|
101
|
-
function satisfiesRequirement(req, profile) {
|
|
102
|
-
return profile.kind === "frontier" && profile.availability !== "unavailable" && profile.contextWindow >= req.minContextWindow && profile.reasoningTier >= req.minReasoningTier && profile.codeQualityTier >= req.minCodeQualityTier && (!req.needsTools || profile.supportsToolCalling) && (!req.needsVision || profile.supportsVision);
|
|
103
|
-
}
|
|
104
|
-
function selectCheapestCapableFrontier(req, profiles) {
|
|
105
|
-
const selected = profiles.filter((profile) => satisfiesRequirement(req, profile)).sort((left, right) => compareCapableFrontier(req, left, right))[0];
|
|
106
|
-
return selected?.ref ?? null;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// src/orchestrator/roles.ts
|
|
110
50
|
var modelSelectionSchema = z.object({
|
|
111
51
|
providerID: z.string().min(1),
|
|
112
52
|
modelID: z.string().min(1)
|
|
@@ -276,15 +216,6 @@ function synthesiseFallbackProfile(roleID) {
|
|
|
276
216
|
localFirst: true
|
|
277
217
|
};
|
|
278
218
|
}
|
|
279
|
-
function roleToRequirement(role, task, tier) {
|
|
280
|
-
const baseRequirement = deriveRequirement(tier ?? role.defaultTier, task);
|
|
281
|
-
return {
|
|
282
|
-
...baseRequirement,
|
|
283
|
-
minReasoningTier: Math.max(baseRequirement.minReasoningTier, role.minReasoningTier),
|
|
284
|
-
minCodeQualityTier: Math.max(baseRequirement.minCodeQualityTier, role.minCodeQualityTier),
|
|
285
|
-
needsTools: baseRequirement.needsTools || role.needsToolCallingByDefault
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
219
|
|
|
289
220
|
// src/config/migrations.ts
|
|
290
221
|
var CURRENT_CONFIG_VERSION = 1;
|
|
@@ -440,7 +371,8 @@ var OrchestratorRolesSchema = z2.record(z2.string().min(1), z2.unknown()).defaul
|
|
|
440
371
|
return parsedRoles;
|
|
441
372
|
});
|
|
442
373
|
var OrchestratorConfigSchema = z2.object({
|
|
443
|
-
roles: OrchestratorRolesSchema
|
|
374
|
+
roles: OrchestratorRolesSchema,
|
|
375
|
+
maxRetries: z2.number().int().min(0).optional()
|
|
444
376
|
}).strict();
|
|
445
377
|
var defaultFrontierModel = {
|
|
446
378
|
providerID: "anthropic",
|
|
@@ -609,456 +541,6 @@ import { z as z7 } from "zod";
|
|
|
609
541
|
// src/orchestrator/coordinator.ts
|
|
610
542
|
import { z as z6 } from "zod";
|
|
611
543
|
|
|
612
|
-
// src/capabilities/classify.ts
|
|
613
|
-
var hardKeywords = [
|
|
614
|
-
"security",
|
|
615
|
-
"auth",
|
|
616
|
-
"authentication",
|
|
617
|
-
"authorization",
|
|
618
|
-
"concurrency",
|
|
619
|
-
"race condition",
|
|
620
|
-
"architecture",
|
|
621
|
-
"refactor",
|
|
622
|
-
"migration",
|
|
623
|
-
"debug",
|
|
624
|
-
"root cause"
|
|
625
|
-
];
|
|
626
|
-
function scoreOverride(override) {
|
|
627
|
-
if (override === "alwaysFrontier") {
|
|
628
|
-
return 30;
|
|
629
|
-
}
|
|
630
|
-
if (override === "alwaysLocal") {
|
|
631
|
-
return -20;
|
|
632
|
-
}
|
|
633
|
-
return 0;
|
|
634
|
-
}
|
|
635
|
-
function tierFromScore(score) {
|
|
636
|
-
if (score <= 15) {
|
|
637
|
-
return "trivial";
|
|
638
|
-
}
|
|
639
|
-
if (score <= 35) {
|
|
640
|
-
return "simple";
|
|
641
|
-
}
|
|
642
|
-
if (score <= 65) {
|
|
643
|
-
return "moderate";
|
|
644
|
-
}
|
|
645
|
-
return "hard";
|
|
646
|
-
}
|
|
647
|
-
function confidenceFromScore(score) {
|
|
648
|
-
if (score >= 75 || score <= 10) {
|
|
649
|
-
return 0.9;
|
|
650
|
-
}
|
|
651
|
-
if (score >= 30 && score <= 70) {
|
|
652
|
-
return 0.58;
|
|
653
|
-
}
|
|
654
|
-
return 0.72;
|
|
655
|
-
}
|
|
656
|
-
function addScore(condition, amount, reason, state) {
|
|
657
|
-
if (condition) {
|
|
658
|
-
state.score += amount;
|
|
659
|
-
state.reasons.push(reason);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
function classifyHeuristic(task) {
|
|
663
|
-
const state = { score: scoreOverride(task.explicitOverride), reasons: [] };
|
|
664
|
-
const prompt = task.prompt?.toLowerCase() ?? "";
|
|
665
|
-
const estimatedTokens = (task.estimatedInputTokens ?? 0) + (task.estimatedOutputTokens ?? 0);
|
|
666
|
-
addScore(task.promptChars <= 80 && !task.hasCode && task.requiresTools !== true, -5, "short-text-prompt", state);
|
|
667
|
-
addScore(task.promptChars > 1500 || estimatedTokens > 700, 10, "large-prompt", state);
|
|
668
|
-
addScore(task.promptChars > 5000 || estimatedTokens > 3000, 15, "very-large-context", state);
|
|
669
|
-
addScore(task.hasCode, 15, "code-present", state);
|
|
670
|
-
addScore(task.requiresTools === true, 15, "tools-required", state);
|
|
671
|
-
addScore(task.requiresVision === true, 15, "vision-required", state);
|
|
672
|
-
addScore((task.fileCount ?? 0) > 1, 20, "multi-file-task", state);
|
|
673
|
-
addScore((task.diffHunks ?? 0) > 2, 15, "multi-hunk-diff", state);
|
|
674
|
-
addScore(hardKeywords.some((keyword) => prompt.includes(keyword)), 25, "hard-keyword", state);
|
|
675
|
-
addScore(task.userAskedForQuality === true, 20, "quality-requested", state);
|
|
676
|
-
addScore(task.privacySensitive === true, 10, "privacy-sensitive", state);
|
|
677
|
-
const normalizedScore = Math.max(0, Math.min(100, state.score));
|
|
678
|
-
return {
|
|
679
|
-
tier: tierFromScore(normalizedScore),
|
|
680
|
-
confidence: confidenceFromScore(normalizedScore),
|
|
681
|
-
reasons: state.reasons.length > 0 ? state.reasons : ["no-hard-signals"],
|
|
682
|
-
ambiguous: normalizedScore >= 30 && normalizedScore <= 70
|
|
683
|
-
};
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
// src/local/slotPool.ts
|
|
687
|
-
var POOL_SEPARATOR = "::";
|
|
688
|
-
function slotPoolKey(runtime, modelID) {
|
|
689
|
-
if (modelID === undefined) {
|
|
690
|
-
return runtime.id;
|
|
691
|
-
}
|
|
692
|
-
const declared = runtime.modelConcurrency?.[modelID];
|
|
693
|
-
return declared === undefined ? runtime.id : `${runtime.id}${POOL_SEPARATOR}${modelID}`;
|
|
694
|
-
}
|
|
695
|
-
function withReportedModelSlots(runtime, reported) {
|
|
696
|
-
if (reported === undefined || Object.keys(reported).length === 0) {
|
|
697
|
-
return runtime;
|
|
698
|
-
}
|
|
699
|
-
return {
|
|
700
|
-
...runtime,
|
|
701
|
-
modelConcurrency: { ...reported, ...runtime.modelConcurrency }
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
// src/capabilities/budget.ts
|
|
706
|
-
var WARNING_UTILIZATION = 0.8;
|
|
707
|
-
function projectedSpend(current, estimatedCostUSD) {
|
|
708
|
-
return Math.max(0, current) + Math.max(0, estimatedCostUSD);
|
|
709
|
-
}
|
|
710
|
-
function budgetExceeded(projected, limit) {
|
|
711
|
-
return limit !== undefined && projected >= limit;
|
|
712
|
-
}
|
|
713
|
-
function budgetNearLimit(projected, limit) {
|
|
714
|
-
return limit !== undefined && projected >= limit * WARNING_UTILIZATION && projected < limit;
|
|
715
|
-
}
|
|
716
|
-
function exhaustedDecision(budgets, reason) {
|
|
717
|
-
return {
|
|
718
|
-
action: budgets.hardStopOnBudgetExhaustion ? "blockFrontier" : "forceLocal",
|
|
719
|
-
reason
|
|
720
|
-
};
|
|
721
|
-
}
|
|
722
|
-
function evaluateBudget(state, estimatedCostUSD, budgets) {
|
|
723
|
-
const projectedSessionUSD = projectedSpend(state.sessionUSD, estimatedCostUSD);
|
|
724
|
-
const projectedMonthlyUSD = budgets.monthlyUSD === undefined ? undefined : projectedSpend(state.monthlyUSD ?? 0, estimatedCostUSD);
|
|
725
|
-
if (budgetExceeded(projectedSessionUSD, budgets.sessionUSD)) {
|
|
726
|
-
return exhaustedDecision(budgets, "session-budget-exhausted");
|
|
727
|
-
}
|
|
728
|
-
if (budgetExceeded(projectedMonthlyUSD ?? 0, budgets.monthlyUSD)) {
|
|
729
|
-
return exhaustedDecision(budgets, "monthly-budget-exhausted");
|
|
730
|
-
}
|
|
731
|
-
if (state.frontierTokens !== undefined && budgets.frontierTokensPerSession !== undefined && state.frontierTokens >= budgets.frontierTokensPerSession) {
|
|
732
|
-
return exhaustedDecision(budgets, "frontier-token-budget-exhausted");
|
|
733
|
-
}
|
|
734
|
-
if (budgetNearLimit(projectedSessionUSD, budgets.sessionUSD)) {
|
|
735
|
-
return { action: "warn", reason: "session-budget-near-limit" };
|
|
736
|
-
}
|
|
737
|
-
if (budgetNearLimit(projectedMonthlyUSD ?? 0, budgets.monthlyUSD)) {
|
|
738
|
-
return { action: "warn", reason: "monthly-budget-near-limit" };
|
|
739
|
-
}
|
|
740
|
-
return { action: "ok" };
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
// src/router/modelSelection.ts
|
|
744
|
-
function modelKey2(model) {
|
|
745
|
-
return `${model.providerID}/${model.modelID}`;
|
|
746
|
-
}
|
|
747
|
-
function sameModel(left, right) {
|
|
748
|
-
return left.providerID === right.providerID && left.modelID === right.modelID;
|
|
749
|
-
}
|
|
750
|
-
function compareModels(left, right) {
|
|
751
|
-
return modelKey2(left).localeCompare(modelKey2(right));
|
|
752
|
-
}
|
|
753
|
-
function uniqueModelSelections(models) {
|
|
754
|
-
const byKey = new Map;
|
|
755
|
-
for (const model of models) {
|
|
756
|
-
byKey.set(modelKey2(model), model);
|
|
757
|
-
}
|
|
758
|
-
return [...byKey.values()].sort(compareModels);
|
|
759
|
-
}
|
|
760
|
-
function localDefault(config) {
|
|
761
|
-
return config.local.runtimes.find((runtime) => runtime.enabled)?.defaultModel ?? config.router.localDefault;
|
|
762
|
-
}
|
|
763
|
-
function resolveConfiguredFrontierBaseline(config) {
|
|
764
|
-
if (config.baseline.mode === "pinned" && config.baseline.pinnedModel !== null) {
|
|
765
|
-
return config.baseline.pinnedModel;
|
|
766
|
-
}
|
|
767
|
-
return config.baseline.hardDefault;
|
|
768
|
-
}
|
|
769
|
-
function compareAvailableModels(left, right) {
|
|
770
|
-
return compareModels(left, right);
|
|
771
|
-
}
|
|
772
|
-
function defaultAvailableModels(config) {
|
|
773
|
-
const local = localDefault(config);
|
|
774
|
-
const models = [];
|
|
775
|
-
if (local !== null) {
|
|
776
|
-
models.push({ ...local, kind: "local", available: true });
|
|
777
|
-
}
|
|
778
|
-
models.push({
|
|
779
|
-
...resolveConfiguredFrontierBaseline(config),
|
|
780
|
-
kind: "frontier",
|
|
781
|
-
available: true
|
|
782
|
-
});
|
|
783
|
-
return models.sort(compareAvailableModels);
|
|
784
|
-
}
|
|
785
|
-
function availableModels(input) {
|
|
786
|
-
return (input.availableModels ?? defaultAvailableModels(input.config)).slice().sort(compareAvailableModels);
|
|
787
|
-
}
|
|
788
|
-
function listedModel(input, model) {
|
|
789
|
-
return availableModels(input).find((candidate) => sameModel(candidate, model));
|
|
790
|
-
}
|
|
791
|
-
function frontierAvailable(input, model) {
|
|
792
|
-
return listedModel(input, model)?.available !== false;
|
|
793
|
-
}
|
|
794
|
-
function localCandidateIsUsable(input, model) {
|
|
795
|
-
return model.kind === "local" && model.available && !(input.task.requiresTools === true && model.supportsTools === false);
|
|
796
|
-
}
|
|
797
|
-
function availableLocalFallbacks(input, selected) {
|
|
798
|
-
return uniqueModelSelections(availableModels(input).filter((model) => localCandidateIsUsable(input, model)).filter((model) => !sameModel(model, selected)).map((model) => ({
|
|
799
|
-
providerID: model.providerID,
|
|
800
|
-
modelID: model.modelID
|
|
801
|
-
})));
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
// src/router/cost.ts
|
|
805
|
-
function modelProfile(input, model) {
|
|
806
|
-
return input.profiles?.find((profile) => sameModel(profile.ref, model));
|
|
807
|
-
}
|
|
808
|
-
function estimatedCostUSD(input, model) {
|
|
809
|
-
const profile = modelProfile(input, model);
|
|
810
|
-
if (profile === undefined) {
|
|
811
|
-
return 0;
|
|
812
|
-
}
|
|
813
|
-
const inputTokens = input.task.estimatedInputTokens ?? 0;
|
|
814
|
-
const outputTokens = input.task.estimatedOutputTokens ?? 0;
|
|
815
|
-
return inputTokens / 1e6 * profile.costPer1M.inputUSD + outputTokens / 1e6 * profile.costPer1M.outputUSD;
|
|
816
|
-
}
|
|
817
|
-
function roundUSD(value) {
|
|
818
|
-
return Math.round(value * 1000000000000) / 1000000000000;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
// src/router/frontierBaseline.ts
|
|
822
|
-
function resolveFrontierBaseline(input, tier) {
|
|
823
|
-
if (input.config.baseline.mode === "pinned" && input.config.baseline.pinnedModel !== null) {
|
|
824
|
-
return { model: input.config.baseline.pinnedModel, rationale: [] };
|
|
825
|
-
}
|
|
826
|
-
if (input.profiles !== undefined) {
|
|
827
|
-
const selected = selectCheapestCapableFrontier(deriveRequirement(tier, input.task), input.profiles);
|
|
828
|
-
if (selected !== null) {
|
|
829
|
-
return {
|
|
830
|
-
model: selected,
|
|
831
|
-
rationale: ["auto-cheapest-capable-frontier"]
|
|
832
|
-
};
|
|
833
|
-
}
|
|
834
|
-
return {
|
|
835
|
-
model: input.config.baseline.hardDefault,
|
|
836
|
-
rationale: ["auto-cheapest-capable-unavailable-hard-default"]
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
|
-
return { model: input.config.baseline.hardDefault, rationale: [] };
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// src/router/chooseModel.ts
|
|
843
|
-
function localStatus(input, model) {
|
|
844
|
-
const listed = listedModel(input, model);
|
|
845
|
-
return {
|
|
846
|
-
available: input.availableModels === undefined ? listed?.available !== false : listed?.available === true,
|
|
847
|
-
missingTools: input.task.requiresTools === true && listed?.supportsTools === false
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
function buildFallbackChain(input, selected, frontier, allowFrontier) {
|
|
851
|
-
const chain = availableLocalFallbacks(input, selected);
|
|
852
|
-
if (allowFrontier && !sameModel(frontier, selected) && frontierAvailable(input, frontier)) {
|
|
853
|
-
chain.push(frontier);
|
|
854
|
-
}
|
|
855
|
-
return chain;
|
|
856
|
-
}
|
|
857
|
-
function inferRouteKind(config, selected, listed) {
|
|
858
|
-
if (listed !== undefined) {
|
|
859
|
-
return listed.kind;
|
|
860
|
-
}
|
|
861
|
-
return config.local.runtimes.some((runtime) => sameModel(runtime.defaultModel, selected)) ? "local" : "frontier";
|
|
862
|
-
}
|
|
863
|
-
function localFallbackReason(status) {
|
|
864
|
-
return status.missingTools ? "local-missing-tools-fallback" : "local-unavailable-fallback";
|
|
865
|
-
}
|
|
866
|
-
function decision(selected, routeKind, rationale, fallbackChain) {
|
|
867
|
-
return {
|
|
868
|
-
selected,
|
|
869
|
-
routeKind,
|
|
870
|
-
rationale,
|
|
871
|
-
fallbackChain
|
|
872
|
-
};
|
|
873
|
-
}
|
|
874
|
-
function blockedDecision(frontier, rationale, blockReason) {
|
|
875
|
-
return {
|
|
876
|
-
selected: frontier,
|
|
877
|
-
routeKind: "blocked",
|
|
878
|
-
rationale,
|
|
879
|
-
fallbackChain: [],
|
|
880
|
-
blockReason
|
|
881
|
-
};
|
|
882
|
-
}
|
|
883
|
-
function resolveLocalPrimary(input, primary, frontier, options) {
|
|
884
|
-
const status = localStatus(input, primary);
|
|
885
|
-
if (status.available && !status.missingTools) {
|
|
886
|
-
return decision(primary, "local", options.baseRationale, buildFallbackChain(input, primary, frontier, options.allowFrontier));
|
|
887
|
-
}
|
|
888
|
-
const fallbackReason = localFallbackReason(status);
|
|
889
|
-
const nextLocal = availableLocalFallbacks(input, primary)[0];
|
|
890
|
-
if (nextLocal !== undefined) {
|
|
891
|
-
return decision(nextLocal, "local", [...options.baseRationale, fallbackReason], buildFallbackChain(input, nextLocal, frontier, options.allowFrontier));
|
|
892
|
-
}
|
|
893
|
-
if (!options.allowFrontier) {
|
|
894
|
-
return decision(primary, "local", [...options.baseRationale, fallbackReason, options.noLocalRationale], []);
|
|
895
|
-
}
|
|
896
|
-
if (frontierAvailable(input, frontier)) {
|
|
897
|
-
return decision(frontier, "frontier", [...options.baseRationale, fallbackReason, "no-local-available-frontier"], []);
|
|
898
|
-
}
|
|
899
|
-
return decision(primary, "local", [...options.baseRationale, fallbackReason, "frontier-unavailable"], []);
|
|
900
|
-
}
|
|
901
|
-
function resolveFrontierPrimary(input, frontier, rationale) {
|
|
902
|
-
if (frontierAvailable(input, frontier)) {
|
|
903
|
-
return decision(frontier, "frontier", rationale, []);
|
|
904
|
-
}
|
|
905
|
-
const nextLocal = availableLocalFallbacks(input, frontier)[0];
|
|
906
|
-
if (nextLocal !== undefined) {
|
|
907
|
-
return decision(nextLocal, "local", [...rationale, "frontier-unavailable-fallback"], buildFallbackChain(input, nextLocal, frontier, false));
|
|
908
|
-
}
|
|
909
|
-
return decision(frontier, "frontier", [...rationale, "frontier-unavailable"], []);
|
|
910
|
-
}
|
|
911
|
-
function tierForInput(input) {
|
|
912
|
-
return input.tier ?? classifyHeuristic(input.task).tier;
|
|
913
|
-
}
|
|
914
|
-
function finalizeDecision(input, tier, frontier, budgetAction, core) {
|
|
915
|
-
const selectedCostUSD = estimatedCostUSD(input, core.selected);
|
|
916
|
-
const baselineCostUSD = estimatedCostUSD(input, frontier.model);
|
|
917
|
-
const savingsUSD = core.routeKind === "frontier" && sameModel(core.selected, frontier.model) ? 0 : baselineCostUSD - selectedCostUSD;
|
|
918
|
-
const result = {
|
|
919
|
-
...core,
|
|
920
|
-
tier,
|
|
921
|
-
rationale: [...frontier.rationale, ...core.rationale],
|
|
922
|
-
estimatedCostUSD: roundUSD(selectedCostUSD),
|
|
923
|
-
baselineCostUSD: roundUSD(baselineCostUSD),
|
|
924
|
-
estimatedSavingsUSD: roundUSD(savingsUSD),
|
|
925
|
-
budgetAction
|
|
926
|
-
};
|
|
927
|
-
if (core.blockReason !== undefined) {
|
|
928
|
-
result.blockReason = core.blockReason;
|
|
929
|
-
}
|
|
930
|
-
return result;
|
|
931
|
-
}
|
|
932
|
-
function chooseModel(input) {
|
|
933
|
-
const tier = tierForInput(input);
|
|
934
|
-
const local = localDefault(input.config);
|
|
935
|
-
const frontier = resolveFrontierBaseline(input, tier);
|
|
936
|
-
const frontierCostUSD = estimatedCostUSD(input, frontier.model);
|
|
937
|
-
const budgetAction = input.budgetState === undefined ? { action: "ok" } : evaluateBudget(input.budgetState, frontierCostUSD, input.config.budgets);
|
|
938
|
-
const override = input.task.explicitOverride;
|
|
939
|
-
if (input.task.privacySensitive === true && input.config.privacyMode === "forceLocalOnSensitive") {
|
|
940
|
-
if (local === null) {
|
|
941
|
-
if (input.config.router.localOnly) {
|
|
942
|
-
return finalizeDecision(input, tier, frontier, budgetAction, blockedDecision(frontier.model, [
|
|
943
|
-
"privacy-force-local",
|
|
944
|
-
"privacy-force-local-none-configured",
|
|
945
|
-
"router-local-only"
|
|
946
|
-
], "privacy-local-only-none-configured"));
|
|
947
|
-
}
|
|
948
|
-
return finalizeDecision(input, tier, frontier, budgetAction, resolveFrontierPrimary(input, frontier.model, [
|
|
949
|
-
"privacy-force-local",
|
|
950
|
-
"privacy-force-local-none-configured"
|
|
951
|
-
]));
|
|
952
|
-
}
|
|
953
|
-
return finalizeDecision(input, tier, frontier, budgetAction, resolveLocalPrimary(input, local, frontier.model, {
|
|
954
|
-
baseRationale: ["privacy-force-local"],
|
|
955
|
-
allowFrontier: false,
|
|
956
|
-
noLocalRationale: "privacy-force-local-unavailable"
|
|
957
|
-
}));
|
|
958
|
-
}
|
|
959
|
-
if (budgetAction.action === "forceLocal" || budgetAction.action === "blockFrontier") {
|
|
960
|
-
if (local === null) {
|
|
961
|
-
if (budgetAction.action === "blockFrontier") {
|
|
962
|
-
return finalizeDecision(input, tier, frontier, budgetAction, blockedDecision(frontier.model, ["budget-blockFrontier", "budget-local-none-configured"], "budget-blockFrontier-none-configured"));
|
|
963
|
-
}
|
|
964
|
-
return finalizeDecision(input, tier, frontier, budgetAction, resolveFrontierPrimary(input, frontier.model, [
|
|
965
|
-
"budget-forceLocal",
|
|
966
|
-
"budget-local-none-configured"
|
|
967
|
-
]));
|
|
968
|
-
}
|
|
969
|
-
return finalizeDecision(input, tier, frontier, budgetAction, resolveLocalPrimary(input, local, frontier.model, {
|
|
970
|
-
baseRationale: [`budget-${budgetAction.action}`],
|
|
971
|
-
allowFrontier: false,
|
|
972
|
-
noLocalRationale: "budget-local-unavailable"
|
|
973
|
-
}));
|
|
974
|
-
}
|
|
975
|
-
if (typeof override === "object") {
|
|
976
|
-
return finalizeDecision(input, tier, frontier, budgetAction, decision(override, inferRouteKind(input.config, override, listedModel(input, override)), ["explicit-model-override"], buildFallbackChain(input, override, frontier.model, true)));
|
|
977
|
-
}
|
|
978
|
-
if (override === "alwaysLocal") {
|
|
979
|
-
if (local === null) {
|
|
980
|
-
return finalizeDecision(input, tier, frontier, budgetAction, blockedDecision(frontier.model, ["explicit-always-local", "forced-local-none-configured"], "forced-local-none-configured"));
|
|
981
|
-
}
|
|
982
|
-
return finalizeDecision(input, tier, frontier, budgetAction, resolveLocalPrimary(input, local, frontier.model, {
|
|
983
|
-
baseRationale: ["explicit-always-local"],
|
|
984
|
-
allowFrontier: false,
|
|
985
|
-
noLocalRationale: "forced-local-unavailable"
|
|
986
|
-
}));
|
|
987
|
-
}
|
|
988
|
-
if (input.config.router.localOnly) {
|
|
989
|
-
if (local === null) {
|
|
990
|
-
return finalizeDecision(input, tier, frontier, budgetAction, blockedDecision(frontier.model, ["router-local-only", "forced-local-none-configured"], "forced-local-none-configured"));
|
|
991
|
-
}
|
|
992
|
-
return finalizeDecision(input, tier, frontier, budgetAction, resolveLocalPrimary(input, local, frontier.model, {
|
|
993
|
-
baseRationale: ["router-local-only"],
|
|
994
|
-
allowFrontier: false,
|
|
995
|
-
noLocalRationale: "forced-local-unavailable"
|
|
996
|
-
}));
|
|
997
|
-
}
|
|
998
|
-
if (override === "alwaysFrontier") {
|
|
999
|
-
return finalizeDecision(input, tier, frontier, budgetAction, decision(frontier.model, "frontier", ["explicit-always-frontier"], []));
|
|
1000
|
-
}
|
|
1001
|
-
if (input.config.router.frontierOnly) {
|
|
1002
|
-
return finalizeDecision(input, tier, frontier, budgetAction, decision(frontier.model, "frontier", ["router-frontier-only"], []));
|
|
1003
|
-
}
|
|
1004
|
-
const shortEnoughForLocal = input.task.promptChars <= input.config.router.trivialPromptMaxChars;
|
|
1005
|
-
const largeEnoughForFrontier = input.task.promptChars >= input.config.router.frontierPromptMinChars;
|
|
1006
|
-
if (!input.task.hasCode && shortEnoughForLocal && !largeEnoughForFrontier) {
|
|
1007
|
-
if (local === null) {
|
|
1008
|
-
return finalizeDecision(input, tier, frontier, budgetAction, resolveFrontierPrimary(input, frontier.model, [
|
|
1009
|
-
"trivial-short-no-code",
|
|
1010
|
-
"no-local-runtime-configured"
|
|
1011
|
-
]));
|
|
1012
|
-
}
|
|
1013
|
-
return finalizeDecision(input, tier, frontier, budgetAction, resolveLocalPrimary(input, local, frontier.model, {
|
|
1014
|
-
baseRationale: ["trivial-short-no-code"],
|
|
1015
|
-
allowFrontier: true,
|
|
1016
|
-
noLocalRationale: "forced-local-unavailable"
|
|
1017
|
-
}));
|
|
1018
|
-
}
|
|
1019
|
-
return finalizeDecision(input, tier, frontier, budgetAction, resolveFrontierPrimary(input, frontier.model, [
|
|
1020
|
-
input.task.hasCode ? "code-detected" : "large-or-nontrivial-prompt"
|
|
1021
|
-
]));
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
// src/telemetry/cost.ts
|
|
1025
|
-
function routeKind(decision2) {
|
|
1026
|
-
if (decision2.routeKind === "local")
|
|
1027
|
-
return "local";
|
|
1028
|
-
if (decision2.routeKind === "blocked")
|
|
1029
|
-
return "blocked";
|
|
1030
|
-
return "frontier";
|
|
1031
|
-
}
|
|
1032
|
-
function budgetAction(decision2) {
|
|
1033
|
-
const reason = decision2.budgetAction.reason;
|
|
1034
|
-
return reason === undefined ? decision2.budgetAction.action : `${decision2.budgetAction.action}:${reason}`;
|
|
1035
|
-
}
|
|
1036
|
-
function toCostRecord(decision2, context) {
|
|
1037
|
-
const record = {
|
|
1038
|
-
ts: context.ts,
|
|
1039
|
-
promptHash: context.promptHash,
|
|
1040
|
-
promptChars: context.promptChars,
|
|
1041
|
-
tier: decision2.tier,
|
|
1042
|
-
routeKind: routeKind(decision2),
|
|
1043
|
-
selected: decision2.selected,
|
|
1044
|
-
rationale: decision2.rationale.join(","),
|
|
1045
|
-
estimatedCostUSD: decision2.estimatedCostUSD,
|
|
1046
|
-
baselineCostUSD: decision2.baselineCostUSD,
|
|
1047
|
-
estimatedSavingsUSD: decision2.estimatedSavingsUSD,
|
|
1048
|
-
budgetAction: budgetAction(decision2)
|
|
1049
|
-
};
|
|
1050
|
-
if (context.sessionID !== undefined) {
|
|
1051
|
-
record.sessionID = context.sessionID;
|
|
1052
|
-
}
|
|
1053
|
-
if (context.tokensIn !== undefined) {
|
|
1054
|
-
record.tokensIn = context.tokensIn;
|
|
1055
|
-
}
|
|
1056
|
-
if (context.tokensOut !== undefined) {
|
|
1057
|
-
record.tokensOut = context.tokensOut;
|
|
1058
|
-
}
|
|
1059
|
-
return record;
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
544
|
// src/storage/path.ts
|
|
1063
545
|
class StoragePathError extends Error {
|
|
1064
546
|
constructor(message) {
|
|
@@ -1137,6 +619,7 @@ var DIAGNOSTIC_CODES = [
|
|
|
1137
619
|
"graph-gate-denied",
|
|
1138
620
|
"memory-extraction-warning",
|
|
1139
621
|
"memory-runtime-warning",
|
|
622
|
+
"node-budget-exhausted",
|
|
1140
623
|
"opencode-server-url",
|
|
1141
624
|
"opencode-version-read-failed",
|
|
1142
625
|
"privacy-inert",
|
|
@@ -1159,6 +642,8 @@ function diagnosticDescription(code) {
|
|
|
1159
642
|
return "Semantic memory extraction reported a non-fatal local-runtime warning; no raw conversation content was recorded.";
|
|
1160
643
|
case "memory-runtime-warning":
|
|
1161
644
|
return "Semantic memory runtime setup reported a non-fatal warning; memory recall may be incomplete.";
|
|
645
|
+
case "node-budget-exhausted":
|
|
646
|
+
return "A graph node consumed its entire per-node pass budget without ever reporting a substantive done; the node's iteration stopped at the bound and the role produced no substantive report across its passes. Inspect the role's report artifact; the task likely needs a larger orchestrator.maxRetries or a clearer assignment.";
|
|
1162
647
|
case "opencode-server-url":
|
|
1163
648
|
return "openteam observed the opencode server URL during plugin initialization.";
|
|
1164
649
|
case "opencode-version-read-failed":
|
|
@@ -1284,7 +769,9 @@ var DiagnosticCodeSchema = z4.enum(DIAGNOSTIC_CODES);
|
|
|
1284
769
|
var DiagnosticEventSchema = EventBaseSchema.extend({
|
|
1285
770
|
type: z4.literal("diagnostic"),
|
|
1286
771
|
level: DiagnosticLevelSchema,
|
|
1287
|
-
code: DiagnosticCodeSchema
|
|
772
|
+
code: DiagnosticCodeSchema,
|
|
773
|
+
roleID: z4.string().min(1).optional(),
|
|
774
|
+
passCount: z4.number().int().min(0).optional()
|
|
1288
775
|
});
|
|
1289
776
|
var OpenTeamEventSchema = z4.discriminatedUnion("type", [
|
|
1290
777
|
RouteEventSchema,
|
|
@@ -1457,9 +944,6 @@ function warnCostRecordRejections(result, source) {
|
|
|
1457
944
|
// src/telemetry/eventLog.ts
|
|
1458
945
|
var DEFAULT_SESSIONS_DIR = ".opencode/openteam-local/sessions";
|
|
1459
946
|
var LEGACY_SESSION_ID = "legacy";
|
|
1460
|
-
function createNullEventSink() {
|
|
1461
|
-
return { emit: () => {} };
|
|
1462
|
-
}
|
|
1463
947
|
function parseEventsJsonl(text) {
|
|
1464
948
|
const events = [];
|
|
1465
949
|
const rejections = [];
|
|
@@ -1600,939 +1084,319 @@ function hashPrompt(prompt) {
|
|
|
1600
1084
|
return `fnv1a32:${(hash >>> 0).toString(16).padStart(8, "0")}`;
|
|
1601
1085
|
}
|
|
1602
1086
|
|
|
1603
|
-
// src/orchestrator/
|
|
1604
|
-
var
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1087
|
+
// src/orchestrator/coordinator.ts
|
|
1088
|
+
var TelemetryFailureClassSchema = z6.enum([
|
|
1089
|
+
"create-rejected",
|
|
1090
|
+
"create-no-id",
|
|
1091
|
+
"prompt-rejected",
|
|
1092
|
+
"prompt-timeout",
|
|
1093
|
+
"prompt-rate-limited",
|
|
1094
|
+
"prompt-auth",
|
|
1095
|
+
"prompt-content-filter",
|
|
1096
|
+
"prompt-context-length",
|
|
1097
|
+
"prompt-validation",
|
|
1098
|
+
"prompt-connection",
|
|
1099
|
+
"prompt-server",
|
|
1100
|
+
"unknown"
|
|
1101
|
+
]);
|
|
1102
|
+
var FAILURE_CAUSE_RULES = [
|
|
1103
|
+
[
|
|
1104
|
+
[
|
|
1105
|
+
"rate limit",
|
|
1106
|
+
"rate-limit",
|
|
1107
|
+
"ratelimit",
|
|
1108
|
+
/(?<!\d)429(?!\d)/,
|
|
1109
|
+
"too many requests",
|
|
1110
|
+
"quota exceeded"
|
|
1111
|
+
],
|
|
1112
|
+
"prompt-rate-limited"
|
|
1612
1113
|
],
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1114
|
+
[
|
|
1115
|
+
[
|
|
1116
|
+
"unauthorized",
|
|
1117
|
+
"authentication",
|
|
1118
|
+
/(?<!\d)(?:401|403)(?!\d)/,
|
|
1119
|
+
"forbidden",
|
|
1120
|
+
"api key",
|
|
1121
|
+
"invalid key",
|
|
1122
|
+
"permission denied"
|
|
1123
|
+
],
|
|
1124
|
+
"prompt-auth"
|
|
1125
|
+
],
|
|
1126
|
+
[
|
|
1127
|
+
[
|
|
1128
|
+
"content policy",
|
|
1129
|
+
"content filter",
|
|
1130
|
+
"content_filter",
|
|
1131
|
+
"moderation",
|
|
1132
|
+
"safety system",
|
|
1133
|
+
"flagged by"
|
|
1134
|
+
],
|
|
1135
|
+
"prompt-content-filter"
|
|
1136
|
+
],
|
|
1137
|
+
[
|
|
1138
|
+
[
|
|
1139
|
+
"context length",
|
|
1140
|
+
"context window",
|
|
1141
|
+
"maximum context",
|
|
1142
|
+
"too many tokens",
|
|
1143
|
+
"token limit",
|
|
1144
|
+
"context_length_exceeded"
|
|
1145
|
+
],
|
|
1146
|
+
"prompt-context-length"
|
|
1147
|
+
],
|
|
1148
|
+
[
|
|
1149
|
+
[
|
|
1150
|
+
"invalid request",
|
|
1151
|
+
"invalid_request",
|
|
1152
|
+
"validation",
|
|
1153
|
+
"bad request",
|
|
1154
|
+
/(?<!\d)400(?!\d)/,
|
|
1155
|
+
"unprocessable",
|
|
1156
|
+
"malformed"
|
|
1157
|
+
],
|
|
1158
|
+
"prompt-validation"
|
|
1159
|
+
],
|
|
1160
|
+
[
|
|
1161
|
+
[
|
|
1162
|
+
"econnrefused",
|
|
1163
|
+
"econnreset",
|
|
1164
|
+
"enotfound",
|
|
1165
|
+
"network error",
|
|
1166
|
+
"connection refused",
|
|
1167
|
+
"connection reset",
|
|
1168
|
+
"fetch failed",
|
|
1169
|
+
"socket hang up"
|
|
1170
|
+
],
|
|
1171
|
+
"prompt-connection"
|
|
1172
|
+
],
|
|
1173
|
+
[
|
|
1174
|
+
[
|
|
1175
|
+
"internal server error",
|
|
1176
|
+
/(?<!\d)(?:500|502|503|504)(?!\d)/,
|
|
1177
|
+
"service unavailable",
|
|
1178
|
+
"bad gateway",
|
|
1179
|
+
"overloaded"
|
|
1180
|
+
],
|
|
1181
|
+
"prompt-server"
|
|
1182
|
+
]
|
|
1183
|
+
];
|
|
1184
|
+
var TIMEOUT_NEEDLES = [
|
|
1185
|
+
"timed out",
|
|
1186
|
+
"timeout",
|
|
1187
|
+
"timedout",
|
|
1188
|
+
"etimedout",
|
|
1189
|
+
"deadline exceeded"
|
|
1190
|
+
];
|
|
1191
|
+
function matchFailureCause(lowerReason) {
|
|
1192
|
+
for (const [needles, cls] of FAILURE_CAUSE_RULES) {
|
|
1193
|
+
if (needles.some((needle) => typeof needle === "string" ? lowerReason.includes(needle) : needle.test(lowerReason))) {
|
|
1194
|
+
return cls;
|
|
1195
|
+
}
|
|
1651
1196
|
}
|
|
1652
|
-
return
|
|
1197
|
+
return;
|
|
1653
1198
|
}
|
|
1654
|
-
function
|
|
1655
|
-
|
|
1656
|
-
|
|
1199
|
+
function classifyFailureForTelemetry(failureReason, failureStage) {
|
|
1200
|
+
const lower = failureReason.toLowerCase();
|
|
1201
|
+
if (TIMEOUT_NEEDLES.some((needle) => lower.includes(needle))) {
|
|
1202
|
+
return "prompt-timeout";
|
|
1657
1203
|
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
for (const runtimeId of listed) {
|
|
1661
|
-
if (!reachable.has(runtimeId)) {
|
|
1662
|
-
continue;
|
|
1663
|
-
}
|
|
1664
|
-
const runtime = configuredRuntime(runtimeId, config);
|
|
1665
|
-
if (runtime === undefined) {
|
|
1666
|
-
continue;
|
|
1667
|
-
}
|
|
1668
|
-
const model = roleLocalModel(role, runtimeId, runtime.defaultModel, runtime);
|
|
1669
|
-
return {
|
|
1670
|
-
runtimeId,
|
|
1671
|
-
decision: { ...decision2, selected: model }
|
|
1672
|
-
};
|
|
1673
|
-
}
|
|
1204
|
+
if (lower.includes("did not include a session id") || lower.includes("missing session")) {
|
|
1205
|
+
return "create-no-id";
|
|
1674
1206
|
}
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
const pinned = roleLocalModel(role, resolvedRuntimeId, decision2.selected, configuredRuntime(resolvedRuntimeId, config));
|
|
1678
|
-
if (pinned !== decision2.selected) {
|
|
1679
|
-
return {
|
|
1680
|
-
runtimeId: resolvedRuntimeId,
|
|
1681
|
-
decision: { ...decision2, selected: pinned }
|
|
1682
|
-
};
|
|
1683
|
-
}
|
|
1207
|
+
if (failureStage === "create") {
|
|
1208
|
+
return "create-rejected";
|
|
1684
1209
|
}
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
};
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
|
-
// src/orchestrator/subsessions.ts
|
|
1692
|
-
var UNKNOWN_ERROR_REASON_MAX_CHARS = 500;
|
|
1693
|
-
var TRUNCATED_REASON_SUFFIX = "…[truncated]";
|
|
1694
|
-
var MAX_SERIALIZED_ARRAY_ITEMS = 20;
|
|
1695
|
-
var MAX_SERIALIZED_OBJECT_PROPERTIES = 25;
|
|
1696
|
-
var MAX_SERIALIZED_DEPTH = 4;
|
|
1697
|
-
function isObject(value) {
|
|
1698
|
-
return typeof value === "object" && value !== null;
|
|
1699
|
-
}
|
|
1700
|
-
function isError(value) {
|
|
1701
|
-
try {
|
|
1702
|
-
return value instanceof Error;
|
|
1703
|
-
} catch {
|
|
1704
|
-
return false;
|
|
1210
|
+
const cause = matchFailureCause(lower);
|
|
1211
|
+
if (cause !== undefined) {
|
|
1212
|
+
return cause;
|
|
1705
1213
|
}
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
try {
|
|
1709
|
-
return String(value);
|
|
1710
|
-
} catch {
|
|
1711
|
-
return "unserializable error value";
|
|
1214
|
+
if (failureStage === "prompt") {
|
|
1215
|
+
return "prompt-rejected";
|
|
1712
1216
|
}
|
|
1217
|
+
return "unknown";
|
|
1713
1218
|
}
|
|
1714
|
-
function
|
|
1715
|
-
|
|
1716
|
-
return
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1219
|
+
function classifyOpencodeSessionError(error) {
|
|
1220
|
+
if (error === undefined) {
|
|
1221
|
+
return "unknown";
|
|
1222
|
+
}
|
|
1223
|
+
const text = `${error.name ?? ""} ${error.message ?? ""}`.trim();
|
|
1224
|
+
if (text.length === 0) {
|
|
1225
|
+
return "unknown";
|
|
1719
1226
|
}
|
|
1227
|
+
return classifyFailureForTelemetry(text, undefined);
|
|
1720
1228
|
}
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1229
|
+
|
|
1230
|
+
// src/console/protocol.ts
|
|
1231
|
+
function redactFrameText(text) {
|
|
1232
|
+
return `[redacted · ${text.length} chars · ${hashPrompt(text)}]`;
|
|
1724
1233
|
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
const
|
|
1731
|
-
return
|
|
1234
|
+
var MAX_INPUT_CHARS = 1e5;
|
|
1235
|
+
var SessionInputSchema = z7.object({
|
|
1236
|
+
text: z7.string().min(1).max(MAX_INPUT_CHARS)
|
|
1237
|
+
}).strict();
|
|
1238
|
+
function parseSessionInput(raw) {
|
|
1239
|
+
const result = SessionInputSchema.safeParse(raw);
|
|
1240
|
+
return result.success ? result.data : undefined;
|
|
1241
|
+
}
|
|
1242
|
+
function asRecord(value) {
|
|
1243
|
+
return typeof value === "object" && value !== null ? value : undefined;
|
|
1732
1244
|
}
|
|
1733
|
-
function
|
|
1734
|
-
return value.
|
|
1245
|
+
function str(value) {
|
|
1246
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
1735
1247
|
}
|
|
1736
|
-
function
|
|
1737
|
-
|
|
1738
|
-
|
|
1248
|
+
function eventSessionID(event) {
|
|
1249
|
+
const props = asRecord(event.properties);
|
|
1250
|
+
if (props === undefined) {
|
|
1251
|
+
return;
|
|
1739
1252
|
}
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
return
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
case "undefined":
|
|
1750
|
-
return "[undefined]";
|
|
1751
|
-
case "function":
|
|
1752
|
-
return `[Function${functionName(value)}]`;
|
|
1753
|
-
case "object":
|
|
1754
|
-
break;
|
|
1253
|
+
return str(props.sessionID) ?? str(asRecord(props.part)?.sessionID) ?? str(asRecord(props.info)?.sessionID);
|
|
1254
|
+
}
|
|
1255
|
+
function partToFrame(part, options) {
|
|
1256
|
+
if (part.type === "text") {
|
|
1257
|
+
const text = str(part.text);
|
|
1258
|
+
if (text === undefined) {
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
return options.diagnostic === true ? { kind: "text", text } : { kind: "text", text: redactFrameText(text), redacted: true };
|
|
1755
1262
|
}
|
|
1756
|
-
if (
|
|
1757
|
-
|
|
1263
|
+
if (part.type === "tool") {
|
|
1264
|
+
const tool = str(part.tool) ?? "tool";
|
|
1265
|
+
const status = str(asRecord(part.state)?.status) ?? "run";
|
|
1266
|
+
return { kind: "tool", text: `⚙ ${tool} · ${status}` };
|
|
1758
1267
|
}
|
|
1759
|
-
|
|
1760
|
-
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
function messagesToFrames(raw, sessionID, options = {}) {
|
|
1271
|
+
if (!Array.isArray(raw)) {
|
|
1272
|
+
return [];
|
|
1761
1273
|
}
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1274
|
+
const frames = [];
|
|
1275
|
+
for (const entry of raw) {
|
|
1276
|
+
const record = asRecord(entry);
|
|
1277
|
+
if (record === undefined) {
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
const info = asRecord(record.info);
|
|
1281
|
+
const entrySession = str(info?.sessionID) ?? str(record.sessionID);
|
|
1282
|
+
if (entrySession !== undefined && entrySession !== sessionID) {
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
const parts = record.parts;
|
|
1286
|
+
if (!Array.isArray(parts)) {
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1289
|
+
for (const rawPart of parts) {
|
|
1290
|
+
const part = asRecord(rawPart);
|
|
1291
|
+
if (part === undefined) {
|
|
1292
|
+
continue;
|
|
1768
1293
|
}
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
const entries = Object.entries(descriptors).filter(([, descriptor]) => descriptor.enumerable);
|
|
1773
|
-
const result = {};
|
|
1774
|
-
for (const [index, [key, descriptor]] of entries.entries()) {
|
|
1775
|
-
if (index >= MAX_SERIALIZED_OBJECT_PROPERTIES) {
|
|
1776
|
-
result["..."] = `${entries.length - MAX_SERIALIZED_OBJECT_PROPERTIES} more properties`;
|
|
1777
|
-
break;
|
|
1294
|
+
const frame = partToFrame(part, options);
|
|
1295
|
+
if (frame !== undefined) {
|
|
1296
|
+
frames.push(frame);
|
|
1778
1297
|
}
|
|
1779
|
-
result[key] = "value" in descriptor ? jsonCompatible(descriptor.value, seen, depth + 1) : "[Getter]";
|
|
1780
1298
|
}
|
|
1781
|
-
return result;
|
|
1782
|
-
} catch (serializationError) {
|
|
1783
|
-
return `[Unserializable object: ${safeToString(serializationError)}]`;
|
|
1784
|
-
} finally {
|
|
1785
|
-
seen.delete(value);
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
function truncateReason(reason) {
|
|
1789
|
-
if (reason.length <= UNKNOWN_ERROR_REASON_MAX_CHARS) {
|
|
1790
|
-
return reason;
|
|
1791
1299
|
}
|
|
1792
|
-
return
|
|
1793
|
-
}
|
|
1794
|
-
function objectReason(error) {
|
|
1795
|
-
try {
|
|
1796
|
-
const serialized = JSON.stringify(jsonCompatible(error, new WeakSet, 0));
|
|
1797
|
-
if (serialized !== undefined && serialized !== "[object Object]") {
|
|
1798
|
-
return truncateReason(serialized);
|
|
1799
|
-
}
|
|
1800
|
-
} catch {}
|
|
1801
|
-
const fallback = safeToString(error);
|
|
1802
|
-
return truncateReason(fallback === "[object Object]" ? "unserializable error object" : fallback);
|
|
1300
|
+
return frames;
|
|
1803
1301
|
}
|
|
1804
|
-
function
|
|
1805
|
-
if (
|
|
1806
|
-
|
|
1807
|
-
if (message !== undefined) {
|
|
1808
|
-
return message;
|
|
1809
|
-
}
|
|
1302
|
+
function eventToSessionFrame(event, sessionID, options = {}) {
|
|
1303
|
+
if (eventSessionID(event) !== sessionID) {
|
|
1304
|
+
return;
|
|
1810
1305
|
}
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1306
|
+
const props = asRecord(event.properties) ?? {};
|
|
1307
|
+
switch (event.type) {
|
|
1308
|
+
case "message.part.updated": {
|
|
1309
|
+
const part = asRecord(props.part);
|
|
1310
|
+
return part === undefined ? undefined : partToFrame(part, options);
|
|
1815
1311
|
}
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1312
|
+
case "session.idle":
|
|
1313
|
+
return { kind: "status", text: "● session ready" };
|
|
1314
|
+
case "session.error": {
|
|
1315
|
+
const err = asRecord(props.error);
|
|
1316
|
+
const cls = classifyOpencodeSessionError(err === undefined ? undefined : { name: str(err.name), message: str(err.message) });
|
|
1317
|
+
return { kind: "error", text: `✖ ${cls}` };
|
|
1819
1318
|
}
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1319
|
+
case "permission.updated": {
|
|
1320
|
+
const title = str(props.title) ?? "permission requested";
|
|
1321
|
+
const id = str(props.id);
|
|
1322
|
+
const frame = { kind: "permission", text: `⚠ ${title}` };
|
|
1323
|
+
if (id !== undefined) {
|
|
1324
|
+
frame.permissionID = id;
|
|
1325
|
+
}
|
|
1326
|
+
return frame;
|
|
1823
1327
|
}
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
return safeToString(error);
|
|
1827
|
-
}
|
|
1828
|
-
function createArgs(req) {
|
|
1829
|
-
const body = {};
|
|
1830
|
-
const args = { body };
|
|
1831
|
-
if (req.parentSessionID !== undefined) {
|
|
1832
|
-
body.parentID = req.parentSessionID;
|
|
1833
|
-
}
|
|
1834
|
-
if (req.title !== undefined) {
|
|
1835
|
-
body.title = req.title;
|
|
1836
|
-
}
|
|
1837
|
-
if (req.directory !== undefined) {
|
|
1838
|
-
args.query = { directory: req.directory };
|
|
1839
|
-
}
|
|
1840
|
-
return args;
|
|
1841
|
-
}
|
|
1842
|
-
function promptArgs(sessionID, req) {
|
|
1843
|
-
const body = {
|
|
1844
|
-
model: req.model,
|
|
1845
|
-
parts: [{ type: "text", text: req.prompt }]
|
|
1846
|
-
};
|
|
1847
|
-
if (req.agent !== undefined) {
|
|
1848
|
-
body.agent = req.agent;
|
|
1849
|
-
}
|
|
1850
|
-
if (req.systemPrompt !== undefined) {
|
|
1851
|
-
body.system = req.systemPrompt;
|
|
1328
|
+
default:
|
|
1329
|
+
return;
|
|
1852
1330
|
}
|
|
1853
|
-
return {
|
|
1854
|
-
path: { id: sessionID },
|
|
1855
|
-
body
|
|
1856
|
-
};
|
|
1857
1331
|
}
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
const timeout = new Promise((_, reject) => {
|
|
1864
|
-
timeoutID = setTimeout(() => {
|
|
1865
|
-
reject(new Error(`prompt timed out after ${timeoutMs}ms`));
|
|
1866
|
-
}, timeoutMs);
|
|
1867
|
-
});
|
|
1868
|
-
try {
|
|
1869
|
-
return await Promise.race([promise, timeout]);
|
|
1870
|
-
} finally {
|
|
1871
|
-
clearTimeout(timeoutID);
|
|
1872
|
-
}
|
|
1332
|
+
function frameToSse(frame) {
|
|
1333
|
+
return `event: frame
|
|
1334
|
+
data: ${JSON.stringify(frame)}
|
|
1335
|
+
|
|
1336
|
+
`;
|
|
1873
1337
|
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1338
|
+
|
|
1339
|
+
// src/console/sse.ts
|
|
1340
|
+
function pushSse(buffer, chunk) {
|
|
1341
|
+
const combined = (buffer + chunk).replace(/\r\n/g, `
|
|
1342
|
+
`);
|
|
1343
|
+
const parts = combined.split(`
|
|
1344
|
+
|
|
1345
|
+
`);
|
|
1346
|
+
const rest = parts.pop() ?? "";
|
|
1347
|
+
const messages = [];
|
|
1348
|
+
for (const block of parts) {
|
|
1349
|
+
if (block.length === 0) {
|
|
1350
|
+
continue;
|
|
1886
1351
|
}
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1352
|
+
let event;
|
|
1353
|
+
const dataLines = [];
|
|
1354
|
+
for (const line of block.split(`
|
|
1355
|
+
`)) {
|
|
1356
|
+
if (line.startsWith(":") || line.length === 0) {
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
if (line.startsWith("event:")) {
|
|
1360
|
+
event = line.slice("event:".length).trimStart();
|
|
1361
|
+
} else if (line.startsWith("data:")) {
|
|
1362
|
+
dataLines.push(line.slice("data:".length).replace(/^ /, ""));
|
|
1363
|
+
}
|
|
1896
1364
|
}
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
success: false,
|
|
1900
|
-
roleID: req.roleID,
|
|
1901
|
-
model: req.model,
|
|
1902
|
-
failureStage: "create",
|
|
1903
|
-
failureReason: errorReason(error)
|
|
1904
|
-
};
|
|
1905
|
-
}
|
|
1906
|
-
try {
|
|
1907
|
-
const prompted = await withTimeout(client.session.prompt(promptArgs(sessionID, req)), deps.timeoutMs);
|
|
1908
|
-
if (prompted.error !== undefined) {
|
|
1909
|
-
return {
|
|
1910
|
-
success: false,
|
|
1911
|
-
sessionID,
|
|
1912
|
-
roleID: req.roleID,
|
|
1913
|
-
model: req.model,
|
|
1914
|
-
failureStage: "prompt",
|
|
1915
|
-
failureReason: errorReason(prompted.error)
|
|
1916
|
-
};
|
|
1365
|
+
if (dataLines.length === 0 && event === undefined) {
|
|
1366
|
+
continue;
|
|
1917
1367
|
}
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
};
|
|
1925
|
-
} catch (error) {
|
|
1926
|
-
return {
|
|
1927
|
-
success: false,
|
|
1928
|
-
sessionID,
|
|
1929
|
-
roleID: req.roleID,
|
|
1930
|
-
model: req.model,
|
|
1931
|
-
failureStage: "prompt",
|
|
1932
|
-
failureReason: errorReason(error)
|
|
1933
|
-
};
|
|
1368
|
+
const message = { data: dataLines.join(`
|
|
1369
|
+
`) };
|
|
1370
|
+
if (event !== undefined) {
|
|
1371
|
+
message.event = event;
|
|
1372
|
+
}
|
|
1373
|
+
messages.push(message);
|
|
1934
1374
|
}
|
|
1375
|
+
return { messages, rest };
|
|
1935
1376
|
}
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
function warnUnknownRole(roleID, warn, warnedRoles) {
|
|
1939
|
-
if (isKnownNonWorkerRole(roleID))
|
|
1377
|
+
function parseEventData(data) {
|
|
1378
|
+
if (data.length === 0) {
|
|
1940
1379
|
return;
|
|
1941
|
-
if (warnedRoles !== undefined) {
|
|
1942
|
-
if (warnedRoles.has(roleID))
|
|
1943
|
-
return;
|
|
1944
|
-
warnedRoles.add(roleID);
|
|
1945
1380
|
}
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
return getRoleProfile(input.roleID, input.config.orchestrator?.roles) ?? synthesiseFallbackProfile(input.roleID);
|
|
1950
|
-
}
|
|
1951
|
-
function priorityForRole(role) {
|
|
1952
|
-
return role.roleID === "scribe" ? "background" : "worker";
|
|
1953
|
-
}
|
|
1954
|
-
function mandatoryLocalRuntimeMessage(roleID) {
|
|
1955
|
-
return `openteam: role "${roleID}" requires a local runtime, but no local runtime is reachable. ` + `This role must run locally and will not fall back to a frontier provider. ` + `Start one of the supported local runtimes (Ollama, LM Studio, or Lemonade) and try again. ` + "Run `openteam doctor` to diagnose your local runtime configuration.";
|
|
1956
|
-
}
|
|
1957
|
-
function assertLocalRuntimeForRole(role, roleID, reachable) {
|
|
1958
|
-
if (role.requiresLocalRuntime !== true) {
|
|
1381
|
+
try {
|
|
1382
|
+
return JSON.parse(data);
|
|
1383
|
+
} catch {
|
|
1959
1384
|
return;
|
|
1960
1385
|
}
|
|
1961
|
-
if (reachable !== true) {
|
|
1962
|
-
throw new Error(mandatoryLocalRuntimeMessage(roleID));
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
function roleHasRuntimeList(role) {
|
|
1966
|
-
return role.localRuntimes !== undefined && role.localRuntimes.length > 0;
|
|
1967
|
-
}
|
|
1968
|
-
async function resolveBatchReachability(inputs, deps) {
|
|
1969
|
-
const roles = inputs.map(roleFor);
|
|
1970
|
-
const anyMandatory = roles.some((role) => role.requiresLocalRuntime === true);
|
|
1971
|
-
const anyList = roles.some(roleHasRuntimeList);
|
|
1972
|
-
if (!anyMandatory && !anyList) {
|
|
1973
|
-
return { any: undefined, set: undefined };
|
|
1974
|
-
}
|
|
1975
|
-
if (anyList && deps.reachableRuntimeIds !== undefined) {
|
|
1976
|
-
const set = await deps.reachableRuntimeIds();
|
|
1977
|
-
return { any: set.size > 0, set };
|
|
1978
|
-
}
|
|
1979
|
-
const any = await deps.localRuntimeReachable();
|
|
1980
|
-
return { any, set: undefined };
|
|
1981
|
-
}
|
|
1982
|
-
function roleLocalReachable(role, batch) {
|
|
1983
|
-
const listed = role.localRuntimes;
|
|
1984
|
-
if (listed !== undefined && listed.length > 0 && batch.set !== undefined) {
|
|
1985
|
-
return listed.some((id) => batch.set?.has(id) === true);
|
|
1986
|
-
}
|
|
1987
|
-
return batch.any;
|
|
1988
|
-
}
|
|
1989
|
-
function permissionsFor(roleID) {
|
|
1990
|
-
return ROLE_PERMISSIONS[roleID] ?? [];
|
|
1991
|
-
}
|
|
1992
|
-
function roleRequiresFrontier(role, task, config) {
|
|
1993
|
-
return task.explicitOverride === undefined && config.router.localOnly !== true && role.localFirst === false && role.defaultTier === "hard" && role.preferredFrontierModels.length > 0;
|
|
1994
|
-
}
|
|
1995
|
-
function roleAdjustedTask(role, input, requirement) {
|
|
1996
|
-
const task = {
|
|
1997
|
-
...input.task,
|
|
1998
|
-
prompt: input.task.prompt ?? input.prompt
|
|
1999
|
-
};
|
|
2000
|
-
if (requirement.needsTools) {
|
|
2001
|
-
task.requiresTools = true;
|
|
2002
|
-
}
|
|
2003
|
-
if (roleRequiresFrontier(role, task, input.config)) {
|
|
2004
|
-
task.explicitOverride = "alwaysFrontier";
|
|
2005
|
-
}
|
|
2006
|
-
return task;
|
|
2007
|
-
}
|
|
2008
|
-
function roleAdjustedTaskForRole(input) {
|
|
2009
|
-
const role = roleFor(input);
|
|
2010
|
-
const tier = elevateTierForPermissions(classifyHeuristic({
|
|
2011
|
-
...input.task,
|
|
2012
|
-
prompt: input.task.prompt ?? input.prompt
|
|
2013
|
-
}).tier, permissionsFor(role.roleID));
|
|
2014
|
-
return roleAdjustedTask(role, input, roleToRequirement(role, input.task, tier));
|
|
2015
1386
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
if (profiles === undefined) {
|
|
1387
|
+
|
|
1388
|
+
// src/console/opencodeClient.ts
|
|
1389
|
+
function unwrapEvent(value) {
|
|
1390
|
+
if (typeof value !== "object" || value === null) {
|
|
2021
1391
|
return;
|
|
2022
1392
|
}
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
const classifiedTier = classifyHeuristic({
|
|
2027
|
-
...input.task,
|
|
2028
|
-
prompt: input.task.prompt ?? input.prompt
|
|
2029
|
-
}).tier;
|
|
2030
|
-
const permissionTier = elevateTierForPermissions(classifiedTier, permissionsFor(role.roleID));
|
|
2031
|
-
const tier = permissionTier;
|
|
2032
|
-
const requirement = roleToRequirement(role, input.task, tier);
|
|
2033
|
-
const task = roleAdjustedTaskForRole(input);
|
|
2034
|
-
const baseInput = {
|
|
2035
|
-
config: input.config,
|
|
2036
|
-
task,
|
|
2037
|
-
tier
|
|
2038
|
-
};
|
|
2039
|
-
const profiles = roleCapableProfiles(input.profiles, requirement);
|
|
2040
|
-
if (profiles !== undefined) {
|
|
2041
|
-
baseInput.profiles = profiles;
|
|
2042
|
-
}
|
|
2043
|
-
if (input.availableModels !== undefined) {
|
|
2044
|
-
baseInput.availableModels = input.availableModels;
|
|
2045
|
-
}
|
|
2046
|
-
if (input.budgetState !== undefined) {
|
|
2047
|
-
baseInput.budgetState = input.budgetState;
|
|
2048
|
-
}
|
|
2049
|
-
return baseInput;
|
|
1393
|
+
const record = value;
|
|
1394
|
+
const payload = typeof record.payload === "object" && record.payload !== null ? record.payload : record;
|
|
1395
|
+
return typeof payload.type === "string" ? payload : undefined;
|
|
2050
1396
|
}
|
|
2051
|
-
function
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
function buildRecord(input, decision2, subsession, deps, decisionID, batchID) {
|
|
2055
|
-
const context = {
|
|
2056
|
-
ts: deps.now(),
|
|
2057
|
-
promptHash: hashPrompt(input.prompt),
|
|
2058
|
-
promptChars: input.task.promptChars
|
|
2059
|
-
};
|
|
2060
|
-
const sessionID = recordSessionID(input, subsession);
|
|
2061
|
-
if (sessionID !== undefined) {
|
|
2062
|
-
context.sessionID = sessionID;
|
|
2063
|
-
}
|
|
2064
|
-
if (input.task.estimatedInputTokens !== undefined) {
|
|
2065
|
-
context.tokensIn = input.task.estimatedInputTokens;
|
|
2066
|
-
}
|
|
2067
|
-
if (input.task.estimatedOutputTokens !== undefined) {
|
|
2068
|
-
context.tokensOut = input.task.estimatedOutputTokens;
|
|
2069
|
-
}
|
|
2070
|
-
const base = toCostRecord(decision2, context);
|
|
2071
|
-
const record = {
|
|
2072
|
-
...base,
|
|
2073
|
-
decisionID,
|
|
2074
|
-
agent: input.roleID,
|
|
2075
|
-
success: subsession.success
|
|
2076
|
-
};
|
|
2077
|
-
if (batchID !== undefined) {
|
|
2078
|
-
record.batchID = batchID;
|
|
2079
|
-
}
|
|
2080
|
-
if (input.taskID !== undefined) {
|
|
2081
|
-
record.taskID = input.taskID;
|
|
2082
|
-
}
|
|
2083
|
-
if (subsession.failureReason !== undefined) {
|
|
2084
|
-
record.failureReason = subsession.failureReason;
|
|
2085
|
-
}
|
|
2086
|
-
if (subsession.failureStage !== undefined) {
|
|
2087
|
-
record.failureStage = subsession.failureStage;
|
|
2088
|
-
}
|
|
2089
|
-
return record;
|
|
2090
|
-
}
|
|
2091
|
-
var TelemetryFailureClassSchema = z6.enum([
|
|
2092
|
-
"create-rejected",
|
|
2093
|
-
"create-no-id",
|
|
2094
|
-
"prompt-rejected",
|
|
2095
|
-
"prompt-timeout",
|
|
2096
|
-
"prompt-rate-limited",
|
|
2097
|
-
"prompt-auth",
|
|
2098
|
-
"prompt-content-filter",
|
|
2099
|
-
"prompt-context-length",
|
|
2100
|
-
"prompt-validation",
|
|
2101
|
-
"prompt-connection",
|
|
2102
|
-
"prompt-server",
|
|
2103
|
-
"unknown"
|
|
2104
|
-
]);
|
|
2105
|
-
var FAILURE_CAUSE_RULES = [
|
|
2106
|
-
[
|
|
2107
|
-
[
|
|
2108
|
-
"rate limit",
|
|
2109
|
-
"rate-limit",
|
|
2110
|
-
"ratelimit",
|
|
2111
|
-
/(?<!\d)429(?!\d)/,
|
|
2112
|
-
"too many requests",
|
|
2113
|
-
"quota exceeded"
|
|
2114
|
-
],
|
|
2115
|
-
"prompt-rate-limited"
|
|
2116
|
-
],
|
|
2117
|
-
[
|
|
2118
|
-
[
|
|
2119
|
-
"unauthorized",
|
|
2120
|
-
"authentication",
|
|
2121
|
-
/(?<!\d)(?:401|403)(?!\d)/,
|
|
2122
|
-
"forbidden",
|
|
2123
|
-
"api key",
|
|
2124
|
-
"invalid key",
|
|
2125
|
-
"permission denied"
|
|
2126
|
-
],
|
|
2127
|
-
"prompt-auth"
|
|
2128
|
-
],
|
|
2129
|
-
[
|
|
2130
|
-
[
|
|
2131
|
-
"content policy",
|
|
2132
|
-
"content filter",
|
|
2133
|
-
"content_filter",
|
|
2134
|
-
"moderation",
|
|
2135
|
-
"safety system",
|
|
2136
|
-
"flagged by"
|
|
2137
|
-
],
|
|
2138
|
-
"prompt-content-filter"
|
|
2139
|
-
],
|
|
2140
|
-
[
|
|
2141
|
-
[
|
|
2142
|
-
"context length",
|
|
2143
|
-
"context window",
|
|
2144
|
-
"maximum context",
|
|
2145
|
-
"too many tokens",
|
|
2146
|
-
"token limit",
|
|
2147
|
-
"context_length_exceeded"
|
|
2148
|
-
],
|
|
2149
|
-
"prompt-context-length"
|
|
2150
|
-
],
|
|
2151
|
-
[
|
|
2152
|
-
[
|
|
2153
|
-
"invalid request",
|
|
2154
|
-
"invalid_request",
|
|
2155
|
-
"validation",
|
|
2156
|
-
"bad request",
|
|
2157
|
-
/(?<!\d)400(?!\d)/,
|
|
2158
|
-
"unprocessable",
|
|
2159
|
-
"malformed"
|
|
2160
|
-
],
|
|
2161
|
-
"prompt-validation"
|
|
2162
|
-
],
|
|
2163
|
-
[
|
|
2164
|
-
[
|
|
2165
|
-
"econnrefused",
|
|
2166
|
-
"econnreset",
|
|
2167
|
-
"enotfound",
|
|
2168
|
-
"network error",
|
|
2169
|
-
"connection refused",
|
|
2170
|
-
"connection reset",
|
|
2171
|
-
"fetch failed",
|
|
2172
|
-
"socket hang up"
|
|
2173
|
-
],
|
|
2174
|
-
"prompt-connection"
|
|
2175
|
-
],
|
|
2176
|
-
[
|
|
2177
|
-
[
|
|
2178
|
-
"internal server error",
|
|
2179
|
-
/(?<!\d)(?:500|502|503|504)(?!\d)/,
|
|
2180
|
-
"service unavailable",
|
|
2181
|
-
"bad gateway",
|
|
2182
|
-
"overloaded"
|
|
2183
|
-
],
|
|
2184
|
-
"prompt-server"
|
|
2185
|
-
]
|
|
2186
|
-
];
|
|
2187
|
-
var TIMEOUT_NEEDLES = [
|
|
2188
|
-
"timed out",
|
|
2189
|
-
"timeout",
|
|
2190
|
-
"timedout",
|
|
2191
|
-
"etimedout",
|
|
2192
|
-
"deadline exceeded"
|
|
2193
|
-
];
|
|
2194
|
-
function matchFailureCause(lowerReason) {
|
|
2195
|
-
for (const [needles, cls] of FAILURE_CAUSE_RULES) {
|
|
2196
|
-
if (needles.some((needle) => typeof needle === "string" ? lowerReason.includes(needle) : needle.test(lowerReason))) {
|
|
2197
|
-
return cls;
|
|
2198
|
-
}
|
|
2199
|
-
}
|
|
2200
|
-
return;
|
|
2201
|
-
}
|
|
2202
|
-
function classifyFailureForTelemetry(failureReason, failureStage) {
|
|
2203
|
-
const lower = failureReason.toLowerCase();
|
|
2204
|
-
if (TIMEOUT_NEEDLES.some((needle) => lower.includes(needle))) {
|
|
2205
|
-
return "prompt-timeout";
|
|
2206
|
-
}
|
|
2207
|
-
if (lower.includes("did not include a session id") || lower.includes("missing session")) {
|
|
2208
|
-
return "create-no-id";
|
|
2209
|
-
}
|
|
2210
|
-
if (failureStage === "create") {
|
|
2211
|
-
return "create-rejected";
|
|
2212
|
-
}
|
|
2213
|
-
const cause = matchFailureCause(lower);
|
|
2214
|
-
if (cause !== undefined) {
|
|
2215
|
-
return cause;
|
|
2216
|
-
}
|
|
2217
|
-
if (failureStage === "prompt") {
|
|
2218
|
-
return "prompt-rejected";
|
|
2219
|
-
}
|
|
2220
|
-
return "unknown";
|
|
2221
|
-
}
|
|
2222
|
-
function classifyOpencodeSessionError(error) {
|
|
2223
|
-
if (error === undefined) {
|
|
2224
|
-
return "unknown";
|
|
2225
|
-
}
|
|
2226
|
-
const text = `${error.name ?? ""} ${error.message ?? ""}`.trim();
|
|
2227
|
-
if (text.length === 0) {
|
|
2228
|
-
return "unknown";
|
|
2229
|
-
}
|
|
2230
|
-
return classifyFailureForTelemetry(text, undefined);
|
|
2231
|
-
}
|
|
2232
|
-
function correlatedCostRecordToRouteEvent(record) {
|
|
2233
|
-
const event = {
|
|
2234
|
-
...costRecordToRouteEvent(record),
|
|
2235
|
-
decisionID: record.decisionID,
|
|
2236
|
-
agent: record.agent,
|
|
2237
|
-
success: record.success
|
|
2238
|
-
};
|
|
2239
|
-
if (record.batchID !== undefined) {
|
|
2240
|
-
event.batchID = record.batchID;
|
|
2241
|
-
}
|
|
2242
|
-
if (record.taskID !== undefined) {
|
|
2243
|
-
event.taskID = record.taskID;
|
|
2244
|
-
}
|
|
2245
|
-
if (record.failureReason !== undefined) {
|
|
2246
|
-
event.failureReason = classifyFailureForTelemetry(record.failureReason, record.failureStage);
|
|
2247
|
-
}
|
|
2248
|
-
if (record.failureStage !== undefined) {
|
|
2249
|
-
event.failureStage = record.failureStage;
|
|
2250
|
-
}
|
|
2251
|
-
return event;
|
|
2252
|
-
}
|
|
2253
|
-
async function safeEmit(sink, event) {
|
|
2254
|
-
try {
|
|
2255
|
-
await sink.emit(event);
|
|
2256
|
-
} catch {}
|
|
2257
|
-
}
|
|
2258
|
-
function buildQueueEvent(observation, sessionID, ts, correlation) {
|
|
2259
|
-
const event = {
|
|
2260
|
-
v: EVENT_SCHEMA_VERSION,
|
|
2261
|
-
type: "queue",
|
|
2262
|
-
ts,
|
|
2263
|
-
sessionID,
|
|
2264
|
-
runtimeId: observation.runtimeId,
|
|
2265
|
-
waitedMs: observation.waitedMs,
|
|
2266
|
-
activeCount: observation.activeCount,
|
|
2267
|
-
queuedCount: observation.queuedCount
|
|
2268
|
-
};
|
|
2269
|
-
if (correlation.decisionID !== undefined) {
|
|
2270
|
-
event.decisionID = correlation.decisionID;
|
|
2271
|
-
}
|
|
2272
|
-
if (correlation.agent !== undefined) {
|
|
2273
|
-
event.agent = correlation.agent;
|
|
2274
|
-
}
|
|
2275
|
-
if (correlation.batchID !== undefined) {
|
|
2276
|
-
event.batchID = correlation.batchID;
|
|
2277
|
-
}
|
|
2278
|
-
return event;
|
|
2279
|
-
}
|
|
2280
|
-
async function runRoleTaskWithCorrelation(input, deps, correlation, batch) {
|
|
2281
|
-
if (getRoleProfile(input.roleID, input.config.orchestrator?.roles) === undefined) {
|
|
2282
|
-
warnUnknownRole(input.roleID, deps.warn, deps.warnedRoles);
|
|
2283
|
-
}
|
|
2284
|
-
const role = roleFor(input);
|
|
2285
|
-
assertLocalRuntimeForRole(role, input.roleID, roleLocalReachable(role, batch));
|
|
2286
|
-
const routed = chooseModel(routingInput(input, role));
|
|
2287
|
-
if (routed.routeKind === "blocked") {
|
|
2288
|
-
throw new Error(`openteam coordinator: request for role "${input.roleID}" blocked — ${routed.blockReason ?? "no-local-runtime-configured"}. Configure a local runtime or disable the constraint.`);
|
|
2289
|
-
}
|
|
2290
|
-
const selection = selectDispatchRuntime(role, routed, batch.set, input.config);
|
|
2291
|
-
const decision2 = selection.decision;
|
|
2292
|
-
const request = {
|
|
2293
|
-
roleID: input.roleID,
|
|
2294
|
-
model: decision2.selected,
|
|
2295
|
-
prompt: input.prompt
|
|
2296
|
-
};
|
|
2297
|
-
if (input.parentSessionID !== undefined) {
|
|
2298
|
-
request.parentSessionID = input.parentSessionID;
|
|
2299
|
-
}
|
|
2300
|
-
if (input.title !== undefined) {
|
|
2301
|
-
request.title = input.title;
|
|
2302
|
-
}
|
|
2303
|
-
if (input.systemPrompt !== undefined) {
|
|
2304
|
-
request.systemPrompt = input.systemPrompt;
|
|
2305
|
-
}
|
|
2306
|
-
if (input.agentOverride !== undefined) {
|
|
2307
|
-
request.agent = input.agentOverride;
|
|
2308
|
-
} else if (role.opencodeAgent !== undefined) {
|
|
2309
|
-
request.agent = role.opencodeAgent;
|
|
2310
|
-
}
|
|
2311
|
-
if (input.directory !== undefined) {
|
|
2312
|
-
request.directory = input.directory;
|
|
2313
|
-
}
|
|
2314
|
-
const subsessionDeps = deps.timeoutMs === undefined ? {} : { timeoutMs: deps.timeoutMs };
|
|
2315
|
-
const runtimeId = selection.runtimeId;
|
|
2316
|
-
let release;
|
|
2317
|
-
let queueObservation;
|
|
2318
|
-
if (runtimeId !== undefined && deps.runtimeLimiter !== undefined) {
|
|
2319
|
-
const limiter = deps.runtimeLimiter;
|
|
2320
|
-
const priority = priorityForRole(role);
|
|
2321
|
-
const dispatchRuntime = configuredRuntime(runtimeId, input.config) ?? { id: runtimeId };
|
|
2322
|
-
const poolKey = slotPoolKey(withReportedModelSlots(dispatchRuntime, deps.reportedModelSlots?.(runtimeId)), decision2.selected.modelID);
|
|
2323
|
-
release = limiter.tryAcquire(poolKey, priority);
|
|
2324
|
-
let waitedMs = 0;
|
|
2325
|
-
if (release === undefined) {
|
|
2326
|
-
const acquisition = await limiter.acquire(poolKey, priority);
|
|
2327
|
-
release = acquisition.release;
|
|
2328
|
-
waitedMs = acquisition.waitedMs;
|
|
2329
|
-
}
|
|
2330
|
-
queueObservation = {
|
|
2331
|
-
runtimeId,
|
|
2332
|
-
waitedMs,
|
|
2333
|
-
activeCount: limiter.activeCount(poolKey),
|
|
2334
|
-
queuedCount: limiter.queuedCount(poolKey)
|
|
2335
|
-
};
|
|
2336
|
-
}
|
|
2337
|
-
try {
|
|
2338
|
-
const subsession = await runSubsession(deps.client, request, subsessionDeps);
|
|
2339
|
-
const record = buildRecord(input, decision2, subsession, deps, correlation.decisionID, correlation.batchID);
|
|
2340
|
-
await safeEmit(deps.sink, correlatedCostRecordToRouteEvent(record));
|
|
2341
|
-
if (queueObservation !== undefined) {
|
|
2342
|
-
const sessionID = recordSessionID(input, subsession);
|
|
2343
|
-
if (sessionID !== undefined) {
|
|
2344
|
-
const queueCorrelation = {
|
|
2345
|
-
decisionID: correlation.decisionID,
|
|
2346
|
-
agent: input.roleID
|
|
2347
|
-
};
|
|
2348
|
-
if (correlation.batchID !== undefined) {
|
|
2349
|
-
queueCorrelation.batchID = correlation.batchID;
|
|
2350
|
-
}
|
|
2351
|
-
await safeEmit(deps.sink, buildQueueEvent(queueObservation, sessionID, deps.now(), queueCorrelation));
|
|
2352
|
-
}
|
|
2353
|
-
}
|
|
2354
|
-
return { decision: decision2, subsession, record };
|
|
2355
|
-
} finally {
|
|
2356
|
-
release?.();
|
|
2357
|
-
}
|
|
2358
|
-
}
|
|
2359
|
-
async function runRoleTask(input, deps) {
|
|
2360
|
-
const batch = await resolveBatchReachability([input], deps);
|
|
2361
|
-
return runRoleTaskWithCorrelation(input, deps, {
|
|
2362
|
-
decisionID: deps.newDecisionID()
|
|
2363
|
-
}, batch);
|
|
2364
|
-
}
|
|
2365
|
-
|
|
2366
|
-
// src/console/protocol.ts
|
|
2367
|
-
function redactFrameText(text) {
|
|
2368
|
-
return `[redacted · ${text.length} chars · ${hashPrompt(text)}]`;
|
|
2369
|
-
}
|
|
2370
|
-
var MAX_INPUT_CHARS = 1e5;
|
|
2371
|
-
var SessionInputSchema = z7.object({
|
|
2372
|
-
text: z7.string().min(1).max(MAX_INPUT_CHARS)
|
|
2373
|
-
}).strict();
|
|
2374
|
-
function parseSessionInput(raw) {
|
|
2375
|
-
const result = SessionInputSchema.safeParse(raw);
|
|
2376
|
-
return result.success ? result.data : undefined;
|
|
2377
|
-
}
|
|
2378
|
-
function asRecord(value) {
|
|
2379
|
-
return typeof value === "object" && value !== null ? value : undefined;
|
|
2380
|
-
}
|
|
2381
|
-
function str(value) {
|
|
2382
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
2383
|
-
}
|
|
2384
|
-
function eventSessionID(event) {
|
|
2385
|
-
const props = asRecord(event.properties);
|
|
2386
|
-
if (props === undefined) {
|
|
2387
|
-
return;
|
|
2388
|
-
}
|
|
2389
|
-
return str(props.sessionID) ?? str(asRecord(props.part)?.sessionID) ?? str(asRecord(props.info)?.sessionID);
|
|
2390
|
-
}
|
|
2391
|
-
function partToFrame(part, options) {
|
|
2392
|
-
if (part.type === "text") {
|
|
2393
|
-
const text = str(part.text);
|
|
2394
|
-
if (text === undefined) {
|
|
2395
|
-
return;
|
|
2396
|
-
}
|
|
2397
|
-
return options.diagnostic === true ? { kind: "text", text } : { kind: "text", text: redactFrameText(text), redacted: true };
|
|
2398
|
-
}
|
|
2399
|
-
if (part.type === "tool") {
|
|
2400
|
-
const tool = str(part.tool) ?? "tool";
|
|
2401
|
-
const status = str(asRecord(part.state)?.status) ?? "run";
|
|
2402
|
-
return { kind: "tool", text: `⚙ ${tool} · ${status}` };
|
|
2403
|
-
}
|
|
2404
|
-
return;
|
|
2405
|
-
}
|
|
2406
|
-
function messagesToFrames(raw, sessionID, options = {}) {
|
|
2407
|
-
if (!Array.isArray(raw)) {
|
|
2408
|
-
return [];
|
|
2409
|
-
}
|
|
2410
|
-
const frames = [];
|
|
2411
|
-
for (const entry of raw) {
|
|
2412
|
-
const record = asRecord(entry);
|
|
2413
|
-
if (record === undefined) {
|
|
2414
|
-
continue;
|
|
2415
|
-
}
|
|
2416
|
-
const info = asRecord(record.info);
|
|
2417
|
-
const entrySession = str(info?.sessionID) ?? str(record.sessionID);
|
|
2418
|
-
if (entrySession !== undefined && entrySession !== sessionID) {
|
|
2419
|
-
continue;
|
|
2420
|
-
}
|
|
2421
|
-
const parts = record.parts;
|
|
2422
|
-
if (!Array.isArray(parts)) {
|
|
2423
|
-
continue;
|
|
2424
|
-
}
|
|
2425
|
-
for (const rawPart of parts) {
|
|
2426
|
-
const part = asRecord(rawPart);
|
|
2427
|
-
if (part === undefined) {
|
|
2428
|
-
continue;
|
|
2429
|
-
}
|
|
2430
|
-
const frame = partToFrame(part, options);
|
|
2431
|
-
if (frame !== undefined) {
|
|
2432
|
-
frames.push(frame);
|
|
2433
|
-
}
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
|
-
return frames;
|
|
2437
|
-
}
|
|
2438
|
-
function eventToSessionFrame(event, sessionID, options = {}) {
|
|
2439
|
-
if (eventSessionID(event) !== sessionID) {
|
|
2440
|
-
return;
|
|
2441
|
-
}
|
|
2442
|
-
const props = asRecord(event.properties) ?? {};
|
|
2443
|
-
switch (event.type) {
|
|
2444
|
-
case "message.part.updated": {
|
|
2445
|
-
const part = asRecord(props.part);
|
|
2446
|
-
return part === undefined ? undefined : partToFrame(part, options);
|
|
2447
|
-
}
|
|
2448
|
-
case "session.idle":
|
|
2449
|
-
return { kind: "status", text: "● session ready" };
|
|
2450
|
-
case "session.error": {
|
|
2451
|
-
const err = asRecord(props.error);
|
|
2452
|
-
const cls = classifyOpencodeSessionError(err === undefined ? undefined : { name: str(err.name), message: str(err.message) });
|
|
2453
|
-
return { kind: "error", text: `✖ ${cls}` };
|
|
2454
|
-
}
|
|
2455
|
-
case "permission.updated": {
|
|
2456
|
-
const title = str(props.title) ?? "permission requested";
|
|
2457
|
-
const id = str(props.id);
|
|
2458
|
-
const frame = { kind: "permission", text: `⚠ ${title}` };
|
|
2459
|
-
if (id !== undefined) {
|
|
2460
|
-
frame.permissionID = id;
|
|
2461
|
-
}
|
|
2462
|
-
return frame;
|
|
2463
|
-
}
|
|
2464
|
-
default:
|
|
2465
|
-
return;
|
|
2466
|
-
}
|
|
2467
|
-
}
|
|
2468
|
-
function frameToSse(frame) {
|
|
2469
|
-
return `event: frame
|
|
2470
|
-
data: ${JSON.stringify(frame)}
|
|
2471
|
-
|
|
2472
|
-
`;
|
|
2473
|
-
}
|
|
2474
|
-
|
|
2475
|
-
// src/console/sse.ts
|
|
2476
|
-
function pushSse(buffer, chunk) {
|
|
2477
|
-
const combined = (buffer + chunk).replace(/\r\n/g, `
|
|
2478
|
-
`);
|
|
2479
|
-
const parts = combined.split(`
|
|
2480
|
-
|
|
2481
|
-
`);
|
|
2482
|
-
const rest = parts.pop() ?? "";
|
|
2483
|
-
const messages = [];
|
|
2484
|
-
for (const block of parts) {
|
|
2485
|
-
if (block.length === 0) {
|
|
2486
|
-
continue;
|
|
2487
|
-
}
|
|
2488
|
-
let event;
|
|
2489
|
-
const dataLines = [];
|
|
2490
|
-
for (const line of block.split(`
|
|
2491
|
-
`)) {
|
|
2492
|
-
if (line.startsWith(":") || line.length === 0) {
|
|
2493
|
-
continue;
|
|
2494
|
-
}
|
|
2495
|
-
if (line.startsWith("event:")) {
|
|
2496
|
-
event = line.slice("event:".length).trimStart();
|
|
2497
|
-
} else if (line.startsWith("data:")) {
|
|
2498
|
-
dataLines.push(line.slice("data:".length).replace(/^ /, ""));
|
|
2499
|
-
}
|
|
2500
|
-
}
|
|
2501
|
-
if (dataLines.length === 0 && event === undefined) {
|
|
2502
|
-
continue;
|
|
2503
|
-
}
|
|
2504
|
-
const message = { data: dataLines.join(`
|
|
2505
|
-
`) };
|
|
2506
|
-
if (event !== undefined) {
|
|
2507
|
-
message.event = event;
|
|
2508
|
-
}
|
|
2509
|
-
messages.push(message);
|
|
2510
|
-
}
|
|
2511
|
-
return { messages, rest };
|
|
2512
|
-
}
|
|
2513
|
-
function parseEventData(data) {
|
|
2514
|
-
if (data.length === 0) {
|
|
2515
|
-
return;
|
|
2516
|
-
}
|
|
2517
|
-
try {
|
|
2518
|
-
return JSON.parse(data);
|
|
2519
|
-
} catch {
|
|
2520
|
-
return;
|
|
2521
|
-
}
|
|
2522
|
-
}
|
|
2523
|
-
|
|
2524
|
-
// src/console/opencodeClient.ts
|
|
2525
|
-
function unwrapEvent(value) {
|
|
2526
|
-
if (typeof value !== "object" || value === null) {
|
|
2527
|
-
return;
|
|
2528
|
-
}
|
|
2529
|
-
const record = value;
|
|
2530
|
-
const payload = typeof record.payload === "object" && record.payload !== null ? record.payload : record;
|
|
2531
|
-
return typeof payload.type === "string" ? payload : undefined;
|
|
2532
|
-
}
|
|
2533
|
-
function messageToFrame(message, sessionID, options = {}) {
|
|
2534
|
-
const event = unwrapEvent(parseEventData(message.data));
|
|
2535
|
-
return event === undefined ? undefined : eventToSessionFrame(event, sessionID, options);
|
|
1397
|
+
function messageToFrame(message, sessionID, options = {}) {
|
|
1398
|
+
const event = unwrapEvent(parseEventData(message.data));
|
|
1399
|
+
return event === undefined ? undefined : eventToSessionFrame(event, sessionID, options);
|
|
2536
1400
|
}
|
|
2537
1401
|
function createConsoleClient(deps) {
|
|
2538
1402
|
const doFetch = deps.fetch ?? fetch;
|
|
@@ -3199,7 +2063,7 @@ var KNOWN_CONFIG_KEYS_BY_PATH = new Map([
|
|
|
3199
2063
|
]
|
|
3200
2064
|
],
|
|
3201
2065
|
["graph.worktrees", ["enabled"]],
|
|
3202
|
-
["orchestrator", ["roles"]]
|
|
2066
|
+
["orchestrator", ["roles", "maxRetries"]]
|
|
3203
2067
|
]);
|
|
3204
2068
|
function isUnrecognizedKeysIssue(issue) {
|
|
3205
2069
|
return issue.code === "unrecognized_keys" && Array.isArray(issue.keys);
|
|
@@ -4322,10 +3186,10 @@ function sessionsPanel(sessions) {
|
|
|
4322
3186
|
"</section>"
|
|
4323
3187
|
].join("");
|
|
4324
3188
|
}
|
|
4325
|
-
function decisionRow(
|
|
4326
|
-
const who =
|
|
4327
|
-
const tags =
|
|
4328
|
-
return `<li class="decision"><span class="kind">decision</span> ${who}${escapeHtml(
|
|
3189
|
+
function decisionRow(decision) {
|
|
3190
|
+
const who = decision.agent === undefined ? "" : `<span class="who">${escapeHtml(decision.agent)}</span> `;
|
|
3191
|
+
const tags = decision.tags === undefined || decision.tags.length === 0 ? "" : ` ${decision.tags.map((tag) => `<span class="tag">#${escapeHtml(tag)}</span>`).join(" ")}`;
|
|
3192
|
+
return `<li class="decision"><span class="kind">decision</span> ${who}${escapeHtml(decision.summary)}${tags}</li>`;
|
|
4329
3193
|
}
|
|
4330
3194
|
function decisionsPanel(decisions, idSuffix) {
|
|
4331
3195
|
const id = panelId("panel-decisions", idSuffix);
|
|
@@ -5198,14 +4062,14 @@ function isRoute(event) {
|
|
|
5198
4062
|
function recentRoutes(records, limit) {
|
|
5199
4063
|
return [...records].sort((a, b) => b.ts - a.ts).slice(0, Math.max(0, limit)).map(toRouteView);
|
|
5200
4064
|
}
|
|
5201
|
-
function decisionToActivity(
|
|
4065
|
+
function decisionToActivity(decision) {
|
|
5202
4066
|
const entry = {
|
|
5203
|
-
ts:
|
|
4067
|
+
ts: decision.ts,
|
|
5204
4068
|
kind: "decision",
|
|
5205
|
-
summary:
|
|
4069
|
+
summary: decision.summary
|
|
5206
4070
|
};
|
|
5207
|
-
if (
|
|
5208
|
-
entry.agent =
|
|
4071
|
+
if (decision.agent !== undefined) {
|
|
4072
|
+
entry.agent = decision.agent;
|
|
5209
4073
|
}
|
|
5210
4074
|
return entry;
|
|
5211
4075
|
}
|
|
@@ -5243,9 +4107,9 @@ function buildSessionTabs(aggregate, costRecords, nowMs, limits, options) {
|
|
|
5243
4107
|
return aggregate.sessions.map((summary) => {
|
|
5244
4108
|
const id = summary.sessionID;
|
|
5245
4109
|
const routes = costRecords.filter((record) => record.sessionID === id);
|
|
5246
|
-
const decisions = aggregate.decisions.filter((
|
|
4110
|
+
const decisions = aggregate.decisions.filter((decision) => decision.sessionID === id).slice(0, Math.max(0, limits.decisionsLimit));
|
|
5247
4111
|
const meetings = aggregate.meetings.filter((meeting) => meeting.sessionID === id).slice(0, Math.max(0, limits.meetingsLimit));
|
|
5248
|
-
const activity = composeActivity(aggregate.activity.filter((view) => view.sessionID === id), aggregate.decisions.filter((
|
|
4112
|
+
const activity = composeActivity(aggregate.activity.filter((view) => view.sessionID === id), aggregate.decisions.filter((decision) => decision.sessionID === id), aggregate.meetings.filter((meeting) => meeting.sessionID === id), limits.activityLimit);
|
|
5249
4113
|
const tab = {
|
|
5250
4114
|
sessionID: id,
|
|
5251
4115
|
status: deriveSessionStatus(summary.lastTs, nowMs, {
|
|
@@ -8299,11 +7163,11 @@ function toErrorOutcome(error) {
|
|
|
8299
7163
|
function createGraphIngress(deps) {
|
|
8300
7164
|
const { policy, journal, lease, effects, holder } = deps;
|
|
8301
7165
|
function taskGate(taskID) {
|
|
8302
|
-
const
|
|
8303
|
-
return
|
|
7166
|
+
const decision = decideIngress(policy, taskID);
|
|
7167
|
+
return decision.route === "legacy" ? {
|
|
8304
7168
|
kind: "legacy",
|
|
8305
|
-
reason:
|
|
8306
|
-
...
|
|
7169
|
+
reason: decision.reason,
|
|
7170
|
+
...decision.gateViolations !== undefined ? { gateViolations: decision.gateViolations } : {}
|
|
8307
7171
|
} : undefined;
|
|
8308
7172
|
}
|
|
8309
7173
|
function runGate() {
|
|
@@ -9277,7 +8141,34 @@ function buildOrchestratorAgent(model, options = {}) {
|
|
|
9277
8141
|
"- `directory` (optional): working directory if it differs from the current one.",
|
|
9278
8142
|
"",
|
|
9279
8143
|
"The tool returns a per-role summary: selected model, success or",
|
|
9280
|
-
"failure,
|
|
8144
|
+
"failure, the created session, and — for each role — a `report` file path",
|
|
8145
|
+
"the role was required to write its findings to. The summary carries **no**",
|
|
8146
|
+
"findings text itself (privacy boundary): the actual work lives only in those",
|
|
8147
|
+
"report files. A durability gate verifies in code that each report was",
|
|
8148
|
+
"actually written: a role that runs but leaves no usable report is reported",
|
|
8149
|
+
"as failed with reason `report-missing-or-empty`, never as a plain success.",
|
|
8150
|
+
"If one role fails, the rest keep running.",
|
|
8151
|
+
"",
|
|
8152
|
+
"### After `openteam-orchestrate` returns — READ the reports, then report",
|
|
8153
|
+
"",
|
|
8154
|
+
"When the tool returns, the orchestrated work is **already finished** — the",
|
|
8155
|
+
"call is synchronous and every subsession has completed (or failed) by then.",
|
|
8156
|
+
"You MUST:",
|
|
8157
|
+
"",
|
|
8158
|
+
"1. **READ each `report` file path** the tool returned (use the `read` tool).",
|
|
8159
|
+
" That file is the only place a role's findings exist; the tool result",
|
|
8160
|
+
" deliberately omits them.",
|
|
8161
|
+
"2. **Synthesise the real findings** from those files for the user: what was",
|
|
8162
|
+
" found, what changed, conclusions, risks and follow-ups.",
|
|
8163
|
+
"3. **Never report the task as `in_progress`, `pending`, or “the agent is",
|
|
8164
|
+
" working on it” after the tool has returned** — it has not; it is done.",
|
|
8165
|
+
"4. **Never describe, summarise, or invent work you have not read** from a",
|
|
8166
|
+
" report file. If a role is reported as failed with reason",
|
|
8167
|
+
" `report-missing-or-empty`, the durability gate ran it but its findings",
|
|
8168
|
+
" were **not preserved to disk**: tell the user plainly that this role's",
|
|
8169
|
+
" work was done but not saved and must be re-run — never fabricate what it",
|
|
8170
|
+
" might have found. If a role failed for any other reason, report the",
|
|
8171
|
+
" failure and its reason.",
|
|
9281
8172
|
"",
|
|
9282
8173
|
"**Use `task` directly** only for ad-hoc subagents that are not in the",
|
|
9283
8174
|
"roster (e.g. an agent created on demand for a one-off job).",
|
|
@@ -9851,7 +8742,7 @@ async function listOpenAICompatibleModels(baseURL, fetch2) {
|
|
|
9851
8742
|
return parseOpenAIModels(payload);
|
|
9852
8743
|
}
|
|
9853
8744
|
function parseOpenAIModels(payload) {
|
|
9854
|
-
if (!
|
|
8745
|
+
if (!isObject(payload) || !Array.isArray(payload.data)) {
|
|
9855
8746
|
throw new Error("Malformed /models response: expected data array");
|
|
9856
8747
|
}
|
|
9857
8748
|
return payload.data.map(parseOpenAIModel);
|
|
@@ -9886,7 +8777,7 @@ async function probeOpenAICompatibleRuntime(input) {
|
|
|
9886
8777
|
}
|
|
9887
8778
|
}
|
|
9888
8779
|
function parseOpenAIModel(value) {
|
|
9889
|
-
if (!
|
|
8780
|
+
if (!isObject(value) || typeof value.id !== "string" || value.id.length === 0) {
|
|
9890
8781
|
throw new Error("Malformed /models response: model id must be a string");
|
|
9891
8782
|
}
|
|
9892
8783
|
const contextWindow = readContextWindow(value);
|
|
@@ -9932,7 +8823,7 @@ function readCapabilityBoolean(value) {
|
|
|
9932
8823
|
}
|
|
9933
8824
|
return;
|
|
9934
8825
|
}
|
|
9935
|
-
if (!
|
|
8826
|
+
if (!isObject(value)) {
|
|
9936
8827
|
return;
|
|
9937
8828
|
}
|
|
9938
8829
|
return readBoolean(value, [
|
|
@@ -9956,7 +8847,7 @@ function readContextWindow(model) {
|
|
|
9956
8847
|
if (direct !== undefined) {
|
|
9957
8848
|
return direct;
|
|
9958
8849
|
}
|
|
9959
|
-
if (
|
|
8850
|
+
if (isObject(model.limit)) {
|
|
9960
8851
|
return readPositiveInteger(model.limit, ["context"]);
|
|
9961
8852
|
}
|
|
9962
8853
|
return;
|
|
@@ -9979,7 +8870,7 @@ function readPositiveInteger(object, keys) {
|
|
|
9979
8870
|
}
|
|
9980
8871
|
return;
|
|
9981
8872
|
}
|
|
9982
|
-
function
|
|
8873
|
+
function isObject(value) {
|
|
9983
8874
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9984
8875
|
}
|
|
9985
8876
|
|
|
@@ -10139,11 +9030,11 @@ async function listLemonadeModels(baseURL, fetch2) {
|
|
|
10139
9030
|
}
|
|
10140
9031
|
function readNonTextModelIDs(payload) {
|
|
10141
9032
|
const ids = new Set;
|
|
10142
|
-
if (!
|
|
9033
|
+
if (!isObject2(payload) || !Array.isArray(payload.data)) {
|
|
10143
9034
|
return ids;
|
|
10144
9035
|
}
|
|
10145
9036
|
for (const entry of payload.data) {
|
|
10146
|
-
if (!
|
|
9037
|
+
if (!isObject2(entry) || typeof entry.id !== "string") {
|
|
10147
9038
|
continue;
|
|
10148
9039
|
}
|
|
10149
9040
|
if (isNonTextModel(entry)) {
|
|
@@ -10172,12 +9063,12 @@ async function readLoadedModels(baseURL, fetch2) {
|
|
|
10172
9063
|
} catch {
|
|
10173
9064
|
return;
|
|
10174
9065
|
}
|
|
10175
|
-
if (!
|
|
9066
|
+
if (!isObject2(payload) || !Array.isArray(payload.all_models_loaded)) {
|
|
10176
9067
|
return;
|
|
10177
9068
|
}
|
|
10178
9069
|
const loaded = new Map;
|
|
10179
9070
|
for (const entry of payload.all_models_loaded) {
|
|
10180
|
-
if (!
|
|
9071
|
+
if (!isObject2(entry) || typeof entry.model_name !== "string") {
|
|
10181
9072
|
continue;
|
|
10182
9073
|
}
|
|
10183
9074
|
if (entry.loaded === false) {
|
|
@@ -10192,7 +9083,7 @@ function readSlotCount(entry) {
|
|
|
10192
9083
|
}
|
|
10193
9084
|
function slotsFromLlamacppArgs(entry) {
|
|
10194
9085
|
const options = entry.recipe_options;
|
|
10195
|
-
if (!
|
|
9086
|
+
if (!isObject2(options) || typeof options.llamacpp_args !== "string") {
|
|
10196
9087
|
return;
|
|
10197
9088
|
}
|
|
10198
9089
|
const match = PARALLEL_ARG.exec(options.llamacpp_args);
|
|
@@ -10245,7 +9136,7 @@ function enrichWithLoadState(models, loaded) {
|
|
|
10245
9136
|
return hit.slots === undefined ? { ...model, loaded: true } : { ...model, loaded: true, slots: hit.slots };
|
|
10246
9137
|
});
|
|
10247
9138
|
}
|
|
10248
|
-
function
|
|
9139
|
+
function isObject2(value) {
|
|
10249
9140
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10250
9141
|
}
|
|
10251
9142
|
|
|
@@ -10327,598 +9218,364 @@ class RuntimeRegistry {
|
|
|
10327
9218
|
error: `Adapter threw unexpectedly: ${errorMessage(error)}`
|
|
10328
9219
|
}));
|
|
10329
9220
|
}
|
|
10330
|
-
}
|
|
10331
|
-
return snapshots;
|
|
10332
|
-
}
|
|
10333
|
-
createProbeOptions(runtime, probedAt) {
|
|
10334
|
-
const options = {
|
|
10335
|
-
fetch: this.fetch,
|
|
10336
|
-
probedAt
|
|
10337
|
-
};
|
|
10338
|
-
if (runtime.baseURL !== undefined) {
|
|
10339
|
-
options.baseURL = runtime.baseURL;
|
|
10340
|
-
}
|
|
10341
|
-
if (localRuntimeKind(runtime) === "foundry-local" && runtime.discovery !== "manual" && this.foundryDiscovery !== undefined) {
|
|
10342
|
-
options.discovery = this.foundryDiscovery;
|
|
10343
|
-
}
|
|
10344
|
-
return options;
|
|
10345
|
-
}
|
|
10346
|
-
}
|
|
10347
|
-
function anyRuntimeReachable(snapshots) {
|
|
10348
|
-
return snapshots.some((snapshot) => snapshot.reachable);
|
|
10349
|
-
}
|
|
10350
|
-
|
|
10351
|
-
// src/cli/setupAdapters.ts
|
|
10352
|
-
class PromptCancelledError extends Error {
|
|
10353
|
-
constructor() {
|
|
10354
|
-
super("prompt cancelled");
|
|
10355
|
-
this.name = "PromptCancelledError";
|
|
10356
|
-
}
|
|
10357
|
-
}
|
|
10358
|
-
function unwrap(value) {
|
|
10359
|
-
if (isCancel(value)) {
|
|
10360
|
-
cancel("Operation cancelled.");
|
|
10361
|
-
throw new PromptCancelledError;
|
|
10362
|
-
}
|
|
10363
|
-
return value;
|
|
10364
|
-
}
|
|
10365
|
-
function toOption(choice) {
|
|
10366
|
-
const option = { value: choice.value, label: choice.label };
|
|
10367
|
-
if (choice.hint !== undefined) {
|
|
10368
|
-
option.hint = choice.hint;
|
|
10369
|
-
}
|
|
10370
|
-
return option;
|
|
10371
|
-
}
|
|
10372
|
-
var clackPrompter = {
|
|
10373
|
-
intro(title) {
|
|
10374
|
-
intro(title);
|
|
10375
|
-
},
|
|
10376
|
-
outro(message) {
|
|
10377
|
-
outro(message);
|
|
10378
|
-
},
|
|
10379
|
-
note(message, title) {
|
|
10380
|
-
note(message, title);
|
|
10381
|
-
},
|
|
10382
|
-
async select(opts) {
|
|
10383
|
-
const result = await select({
|
|
10384
|
-
message: opts.message,
|
|
10385
|
-
options: opts.choices.map(toOption),
|
|
10386
|
-
...opts.initial !== undefined ? { initialValue: opts.initial } : {}
|
|
10387
|
-
});
|
|
10388
|
-
return unwrap(result);
|
|
10389
|
-
},
|
|
10390
|
-
async autocomplete(opts) {
|
|
10391
|
-
const result = await autocomplete({
|
|
10392
|
-
message: opts.message,
|
|
10393
|
-
options: opts.choices.map(toOption),
|
|
10394
|
-
...opts.initial !== undefined ? { initialValue: opts.initial } : {},
|
|
10395
|
-
...opts.placeholder !== undefined ? { placeholder: opts.placeholder } : {}
|
|
10396
|
-
});
|
|
10397
|
-
return unwrap(result);
|
|
10398
|
-
},
|
|
10399
|
-
async multiselect(opts) {
|
|
10400
|
-
const initialValues = opts.choices.filter((choice) => choice.selected === true).map((choice) => choice.value);
|
|
10401
|
-
const result = await multiselect({
|
|
10402
|
-
message: opts.message,
|
|
10403
|
-
options: opts.choices.map(toOption),
|
|
10404
|
-
initialValues,
|
|
10405
|
-
required: opts.required ?? false
|
|
10406
|
-
});
|
|
10407
|
-
return unwrap(result);
|
|
10408
|
-
},
|
|
10409
|
-
async confirm(opts) {
|
|
10410
|
-
const result = await confirm({
|
|
10411
|
-
message: opts.message,
|
|
10412
|
-
initialValue: opts.initial ?? false
|
|
10413
|
-
});
|
|
10414
|
-
return unwrap(result);
|
|
10415
|
-
},
|
|
10416
|
-
async text(opts) {
|
|
10417
|
-
const result = await text({
|
|
10418
|
-
message: opts.message,
|
|
10419
|
-
...opts.placeholder !== undefined ? { placeholder: opts.placeholder } : {},
|
|
10420
|
-
...opts.initial !== undefined ? { initialValue: opts.initial } : {}
|
|
10421
|
-
});
|
|
10422
|
-
return unwrap(result);
|
|
10423
|
-
}
|
|
10424
|
-
};
|
|
10425
|
-
var DETECTION_RUNTIMES = [
|
|
10426
|
-
{
|
|
10427
|
-
id: "ollama",
|
|
10428
|
-
enabled: true,
|
|
10429
|
-
baseURL: "http://localhost:11434/v1",
|
|
10430
|
-
defaultModel: { providerID: "ollama", modelID: "probe" }
|
|
10431
|
-
},
|
|
10432
|
-
{
|
|
10433
|
-
id: "lmstudio",
|
|
10434
|
-
enabled: true,
|
|
10435
|
-
baseURL: "http://localhost:1234/v1",
|
|
10436
|
-
defaultModel: { providerID: "lmstudio", modelID: "probe" }
|
|
10437
|
-
},
|
|
10438
|
-
{
|
|
10439
|
-
id: "lemonade",
|
|
10440
|
-
enabled: true,
|
|
10441
|
-
baseURL: LEMONADE_DEFAULT_BASE_URL,
|
|
10442
|
-
defaultModel: { providerID: "lemonade", modelID: "probe" }
|
|
10443
|
-
},
|
|
10444
|
-
{
|
|
10445
|
-
id: "foundry-local",
|
|
10446
|
-
enabled: true,
|
|
10447
|
-
discovery: "cli",
|
|
10448
|
-
defaultModel: { providerID: "foundry-local", modelID: "probe" }
|
|
10449
|
-
}
|
|
10450
|
-
];
|
|
10451
|
-
function createLoadFrontierProfiles() {
|
|
10452
|
-
return async () => {
|
|
10453
|
-
try {
|
|
10454
|
-
const result = await loadCapabilityProfiles({
|
|
10455
|
-
fetch: globalThis.fetch,
|
|
10456
|
-
now: () => Date.now()
|
|
10457
|
-
});
|
|
10458
|
-
return result.profiles.length > 0 ? result.profiles : [...CURATED_FRONTIER_PROFILES];
|
|
10459
|
-
} catch {
|
|
10460
|
-
return [...CURATED_FRONTIER_PROFILES];
|
|
10461
|
-
}
|
|
10462
|
-
};
|
|
10463
|
-
}
|
|
10464
|
-
function createDetect(exec) {
|
|
10465
|
-
return async () => {
|
|
10466
|
-
const registry = new RuntimeRegistry({
|
|
10467
|
-
fetch: globalThis.fetch,
|
|
10468
|
-
clock: () => new Date().toISOString(),
|
|
10469
|
-
foundryDiscovery: foundryDiscoveryFromExec(exec)
|
|
10470
|
-
});
|
|
10471
|
-
const snapshots = await registry.probe(DETECTION_RUNTIMES);
|
|
10472
|
-
return snapshots.map((snapshot) => {
|
|
10473
|
-
const detected = {
|
|
10474
|
-
id: snapshot.id,
|
|
10475
|
-
baseURL: snapshot.baseURL,
|
|
10476
|
-
reachable: snapshot.reachable,
|
|
10477
|
-
models: snapshot.models.map((model) => model.modelID)
|
|
10478
|
-
};
|
|
10479
|
-
if (snapshot.models.some((model) => model.loaded !== undefined)) {
|
|
10480
|
-
detected.modelInfo = snapshot.models;
|
|
10481
|
-
}
|
|
10482
|
-
if (snapshot.error !== undefined) {
|
|
10483
|
-
detected.error = snapshot.error;
|
|
10484
|
-
}
|
|
10485
|
-
return detected;
|
|
10486
|
-
});
|
|
10487
|
-
};
|
|
10488
|
-
}
|
|
10489
|
-
function createProbeRuntime(exec) {
|
|
10490
|
-
return async (id, baseURL) => {
|
|
10491
|
-
const registry = new RuntimeRegistry({
|
|
10492
|
-
fetch: globalThis.fetch,
|
|
10493
|
-
clock: () => new Date().toISOString(),
|
|
10494
|
-
foundryDiscovery: foundryDiscoveryFromExec(exec)
|
|
10495
|
-
});
|
|
10496
|
-
const runtime = {
|
|
10497
|
-
id,
|
|
10498
|
-
enabled: true,
|
|
10499
|
-
baseURL,
|
|
10500
|
-
defaultModel: { providerID: id, modelID: "probe" }
|
|
10501
|
-
};
|
|
10502
|
-
const [snapshot] = await registry.probe([runtime]);
|
|
10503
|
-
const detected = {
|
|
10504
|
-
id,
|
|
10505
|
-
baseURL: snapshot?.baseURL ?? baseURL,
|
|
10506
|
-
reachable: snapshot?.reachable ?? false,
|
|
10507
|
-
models: snapshot?.models.map((model) => model.modelID) ?? []
|
|
9221
|
+
}
|
|
9222
|
+
return snapshots;
|
|
9223
|
+
}
|
|
9224
|
+
createProbeOptions(runtime, probedAt) {
|
|
9225
|
+
const options = {
|
|
9226
|
+
fetch: this.fetch,
|
|
9227
|
+
probedAt
|
|
10508
9228
|
};
|
|
10509
|
-
if (
|
|
10510
|
-
|
|
9229
|
+
if (runtime.baseURL !== undefined) {
|
|
9230
|
+
options.baseURL = runtime.baseURL;
|
|
10511
9231
|
}
|
|
10512
|
-
if (
|
|
10513
|
-
|
|
9232
|
+
if (localRuntimeKind(runtime) === "foundry-local" && runtime.discovery !== "manual" && this.foundryDiscovery !== undefined) {
|
|
9233
|
+
options.discovery = this.foundryDiscovery;
|
|
10514
9234
|
}
|
|
10515
|
-
return
|
|
10516
|
-
}
|
|
9235
|
+
return options;
|
|
9236
|
+
}
|
|
10517
9237
|
}
|
|
10518
9238
|
|
|
10519
|
-
// src/
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
detail: `${name} certificate not found — run the ${name} certification suite to produce it`
|
|
10525
|
-
};
|
|
9239
|
+
// src/cli/setupAdapters.ts
|
|
9240
|
+
class PromptCancelledError extends Error {
|
|
9241
|
+
constructor() {
|
|
9242
|
+
super("prompt cancelled");
|
|
9243
|
+
this.name = "PromptCancelledError";
|
|
10526
9244
|
}
|
|
10527
|
-
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
9245
|
+
}
|
|
9246
|
+
function unwrap(value) {
|
|
9247
|
+
if (isCancel(value)) {
|
|
9248
|
+
cancel("Operation cancelled.");
|
|
9249
|
+
throw new PromptCancelledError;
|
|
10532
9250
|
}
|
|
10533
|
-
return;
|
|
9251
|
+
return value;
|
|
10534
9252
|
}
|
|
10535
|
-
function
|
|
10536
|
-
const
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
|
|
10548
|
-
|
|
10549
|
-
|
|
10550
|
-
|
|
9253
|
+
function toOption(choice) {
|
|
9254
|
+
const option = { value: choice.value, label: choice.label };
|
|
9255
|
+
if (choice.hint !== undefined) {
|
|
9256
|
+
option.hint = choice.hint;
|
|
9257
|
+
}
|
|
9258
|
+
return option;
|
|
9259
|
+
}
|
|
9260
|
+
var clackPrompter = {
|
|
9261
|
+
intro(title) {
|
|
9262
|
+
intro(title);
|
|
9263
|
+
},
|
|
9264
|
+
outro(message) {
|
|
9265
|
+
outro(message);
|
|
9266
|
+
},
|
|
9267
|
+
note(message, title) {
|
|
9268
|
+
note(message, title);
|
|
9269
|
+
},
|
|
9270
|
+
async select(opts) {
|
|
9271
|
+
const result = await select({
|
|
9272
|
+
message: opts.message,
|
|
9273
|
+
options: opts.choices.map(toOption),
|
|
9274
|
+
...opts.initial !== undefined ? { initialValue: opts.initial } : {}
|
|
10551
9275
|
});
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
9276
|
+
return unwrap(result);
|
|
9277
|
+
},
|
|
9278
|
+
async autocomplete(opts) {
|
|
9279
|
+
const result = await autocomplete({
|
|
9280
|
+
message: opts.message,
|
|
9281
|
+
options: opts.choices.map(toOption),
|
|
9282
|
+
...opts.initial !== undefined ? { initialValue: opts.initial } : {},
|
|
9283
|
+
...opts.placeholder !== undefined ? { placeholder: opts.placeholder } : {}
|
|
10556
9284
|
});
|
|
10557
|
-
|
|
10558
|
-
|
|
10559
|
-
|
|
10560
|
-
|
|
10561
|
-
|
|
9285
|
+
return unwrap(result);
|
|
9286
|
+
},
|
|
9287
|
+
async multiselect(opts) {
|
|
9288
|
+
const initialValues = opts.choices.filter((choice) => choice.selected === true).map((choice) => choice.value);
|
|
9289
|
+
const result = await multiselect({
|
|
9290
|
+
message: opts.message,
|
|
9291
|
+
options: opts.choices.map(toOption),
|
|
9292
|
+
initialValues,
|
|
9293
|
+
required: opts.required ?? false
|
|
10562
9294
|
});
|
|
10563
|
-
|
|
10564
|
-
return {
|
|
10565
|
-
allowed: violations.length === 0,
|
|
10566
|
-
violations,
|
|
10567
|
-
advisories
|
|
10568
|
-
};
|
|
10569
|
-
}
|
|
10570
|
-
function isGateApplicable(mode) {
|
|
10571
|
-
return mode === "active";
|
|
10572
|
-
}
|
|
10573
|
-
|
|
10574
|
-
// src/messages/commands.ts
|
|
10575
|
-
var clearCacheMessages = {
|
|
10576
|
-
header: "openteam clear-cache — frozen plugin cache entries:",
|
|
10577
|
-
columns: {
|
|
10578
|
-
specDir: "spec dir",
|
|
10579
|
-
pinned: "spec-pinned",
|
|
10580
|
-
installed: "installed",
|
|
10581
|
-
mtime: "mtime"
|
|
9295
|
+
return unwrap(result);
|
|
10582
9296
|
},
|
|
10583
|
-
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
};
|
|
10590
|
-
var baselineMessages = {
|
|
10591
|
-
effectiveAuto: "cheapest-capable (auto)",
|
|
10592
|
-
pinnedSuffix: (ref) => `${ref} (pinned)`,
|
|
10593
|
-
summary: (params) => [
|
|
10594
|
-
"openteam baseline:",
|
|
10595
|
-
` mode: ${params.mode}`,
|
|
10596
|
-
` pinned: ${params.pinned}`,
|
|
10597
|
-
` hardDefault: ${params.hardDefault}`,
|
|
10598
|
-
` effective: ${params.effective}`
|
|
10599
|
-
],
|
|
10600
|
-
invalidModel: (input) => `Invalid model "${input}". Use the provider/model format, e.g. anthropic/claude-sonnet-4-5.`,
|
|
10601
|
-
pinnedTo: (ref) => `Baseline pinned to ${ref} (pinned mode).`,
|
|
10602
|
-
autoMode: "Baseline set to auto mode (cheapest-capable)."
|
|
10603
|
-
};
|
|
10604
|
-
var localMessages = {
|
|
10605
|
-
help: {
|
|
10606
|
-
status: " openteam local status Show the routing mode (local-first / local-only / frontier-only)",
|
|
10607
|
-
off: " openteam local off Use frontier models only (no local runtime)",
|
|
10608
|
-
only: " openteam local only Use local models only (never frontier)",
|
|
10609
|
-
on: " openteam local on Re-enable local-first routing"
|
|
9297
|
+
async confirm(opts) {
|
|
9298
|
+
const result = await confirm({
|
|
9299
|
+
message: opts.message,
|
|
9300
|
+
initialValue: opts.initial ?? false
|
|
9301
|
+
});
|
|
9302
|
+
return unwrap(result);
|
|
10610
9303
|
},
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
` localOnly: ${params.localOnly}`,
|
|
10620
|
-
` privacy: ${params.privacy}`,
|
|
10621
|
-
` runtimes: ${params.runtimes}`
|
|
10622
|
-
],
|
|
10623
|
-
frontierOnlyNoChange: "No change: already in frontier-only mode.",
|
|
10624
|
-
privacyAdjustedNote: " Privacy adjusted to 'consentBeforeFrontier' (forceLocalOnSensitive requires a local runtime).",
|
|
10625
|
-
frontierOnlyEnabled: "Frontier-only mode enabled (frontierOnly=true, localOnly=false).",
|
|
10626
|
-
localOnlyNoRuntimes: "Cannot enable local-only mode: no enabled local runtime is configured. Run 'openteam setup' or enable a local runtime in .opencode/openteam.json first.",
|
|
10627
|
-
localOnlyNoChange: "No change: already in local-only mode.",
|
|
10628
|
-
localOnlyEnabled: "Local-only mode enabled (localOnly=true, frontierOnly=false).",
|
|
10629
|
-
localFirstNoChange: "No change: local-first routing is already active.",
|
|
10630
|
-
localFirstReEnabled: "Local-first routing re-enabled (frontierOnly=false, localOnly=false). Make sure a local runtime is reachable.",
|
|
10631
|
-
unknownSubcommand: (subcommand, help) => `Unknown local subcommand: ${subcommand}
|
|
10632
|
-
|
|
10633
|
-
${help}`
|
|
10634
|
-
};
|
|
10635
|
-
var frontierCatalogMessages = {
|
|
10636
|
-
free: "free",
|
|
10637
|
-
costPerMillion: (inputUSD, outputUSD) => `$${inputUSD}/$${outputUSD} per 1M`
|
|
10638
|
-
};
|
|
10639
|
-
var migrateMessages = {
|
|
10640
|
-
header: "openteam migrate:",
|
|
10641
|
-
nothingToMigrate: " nothing to migrate — the P15b layout is already in place.",
|
|
10642
|
-
counts: (params) => [
|
|
10643
|
-
` moved: ${params.moved} file(s)`,
|
|
10644
|
-
` deduped: ${params.deduped} file(s) (destination already identical)`,
|
|
10645
|
-
` conflicts: ${params.conflicts} file(s)`
|
|
10646
|
-
],
|
|
10647
|
-
relocatedHeader: " relocated:",
|
|
10648
|
-
relocatedEntry: (from, to, deduped) => ` · ${from} → ${to}${deduped ? " (deduped)" : ""}`,
|
|
10649
|
-
conflictsHeader: " ⚠ left in place (destination exists with different content — no data lost):",
|
|
10650
|
-
conflictEntry: (from, to) => ` · ${from} → ${to}`,
|
|
10651
|
-
manualWorktreesHeader: " ⚠ legacy git worktree(s) need a manual move (run `git worktree move`):",
|
|
10652
|
-
manualWorktreeEntry: (label, from, to) => ` · ${label}: ${from} → ${to}`,
|
|
10653
|
-
success: " ✓ machine-local runtime output relocated under .opencode/openteam-local/."
|
|
10654
|
-
};
|
|
10655
|
-
var rolesInitMessages = {
|
|
10656
|
-
header: "openteam roles init:",
|
|
10657
|
-
title: "openteam roles init",
|
|
10658
|
-
noRoster: (path) => ` ✗ no roster at ${path}.`,
|
|
10659
|
-
noRosterRemedy: " Cast the team first: run `openteam setup`, then ask the orchestrator to register the cast.",
|
|
10660
|
-
unparseableRoster: (path, error) => ` ✗ ${path} is present but unparseable: ${error}`,
|
|
10661
|
-
unparseableRosterRemedy: " Run `openteam doctor` for the remedy, then re-run this command.",
|
|
10662
|
-
allProfiled: (count) => ` ✓ ${count} roster role(s) checked; no unprofiled roles — every role resolves a routing profile.`,
|
|
10663
|
-
whyTitle: "Why you are being asked",
|
|
10664
|
-
why: (count) => [
|
|
10665
|
-
`${count} roster role(s) resolve no routing profile.`,
|
|
10666
|
-
"Until a profile exists they are frontier-eligible: their work can escalate",
|
|
10667
|
-
"to a frontier model. A 'local only' note in the roster prose does not",
|
|
10668
|
-
"prevent this — prose outside the JSON fence is never read."
|
|
10669
|
-
].join(`
|
|
10670
|
-
`),
|
|
10671
|
-
policyQuestion: (roleID, agentName) => `Routing policy for "${roleID}" (${agentName})`,
|
|
10672
|
-
localOnlyLabel: (roleID, agentName) => `Local only — ${roleID} (${agentName}) never leaves this machine`,
|
|
10673
|
-
localOnlyHint: "sets requiresLocalRuntime; the role fails closed when no local runtime is reachable",
|
|
10674
|
-
frontierOkLabel: "Local first, frontier allowed",
|
|
10675
|
-
frontierOkHint: "records today's implicit behaviour: cheapest-capable frontier when the task warrants it",
|
|
10676
|
-
skipLabel: "Skip for now",
|
|
10677
|
-
skipHint: "leaves the role unprofiled, so it stays frontier-eligible and doctor keeps reporting it",
|
|
10678
|
-
runtimeQuestion: (roleID) => `Preferred local runtime(s) for "${roleID}" (in failover order)`,
|
|
10679
|
-
runtimeHint: "machine-local — written to the git-ignored overlay",
|
|
10680
|
-
cancelled: (reason) => ` ✗ cancelled (${reason}); nothing was written.`,
|
|
10681
|
-
invalidResult: (reason) => ` ✗ refusing to write: the resulting config is invalid — ${reason}`,
|
|
10682
|
-
nothingWritten: (skipped) => ` · skipped ${skipped}; nothing written.`,
|
|
10683
|
-
nothingToWriteOutro: "Nothing to write.",
|
|
10684
|
-
pinnedLocal: (roleIDs, path) => ` ✓ pinned local-only: ${roleIDs} → ${path} (tracked, travels with the roster)`,
|
|
10685
|
-
frontierAllowed: (roleIDs, path) => ` ✓ frontier allowed: ${roleIDs} → ${path} (model choice stays "auto": cheapest-capable per task)`,
|
|
10686
|
-
runtimeBinding: (path) => ` ✓ runtime binding: ${path} (git-ignored, machine-local)`,
|
|
10687
|
-
skippedSummary: (roleIDs) => ` · skipped: ${roleIDs} — still frontier-eligible; re-run this command to revisit.`,
|
|
10688
|
-
outro: "Role policy updated."
|
|
9304
|
+
async text(opts) {
|
|
9305
|
+
const result = await text({
|
|
9306
|
+
message: opts.message,
|
|
9307
|
+
...opts.placeholder !== undefined ? { placeholder: opts.placeholder } : {},
|
|
9308
|
+
...opts.initial !== undefined ? { initialValue: opts.initial } : {}
|
|
9309
|
+
});
|
|
9310
|
+
return unwrap(result);
|
|
9311
|
+
}
|
|
10689
9312
|
};
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
9313
|
+
var DETECTION_RUNTIMES = [
|
|
9314
|
+
{
|
|
9315
|
+
id: "ollama",
|
|
9316
|
+
enabled: true,
|
|
9317
|
+
baseURL: "http://localhost:11434/v1",
|
|
9318
|
+
defaultModel: { providerID: "ollama", modelID: "probe" }
|
|
9319
|
+
},
|
|
9320
|
+
{
|
|
9321
|
+
id: "lmstudio",
|
|
9322
|
+
enabled: true,
|
|
9323
|
+
baseURL: "http://localhost:1234/v1",
|
|
9324
|
+
defaultModel: { providerID: "lmstudio", modelID: "probe" }
|
|
9325
|
+
},
|
|
9326
|
+
{
|
|
9327
|
+
id: "lemonade",
|
|
9328
|
+
enabled: true,
|
|
9329
|
+
baseURL: LEMONADE_DEFAULT_BASE_URL,
|
|
9330
|
+
defaultModel: { providerID: "lemonade", modelID: "probe" }
|
|
9331
|
+
},
|
|
9332
|
+
{
|
|
9333
|
+
id: "foundry-local",
|
|
9334
|
+
enabled: true,
|
|
9335
|
+
discovery: "cli",
|
|
9336
|
+
defaultModel: { providerID: "foundry-local", modelID: "probe" }
|
|
9337
|
+
}
|
|
9338
|
+
];
|
|
9339
|
+
function createLoadFrontierProfiles() {
|
|
9340
|
+
return async () => {
|
|
9341
|
+
try {
|
|
9342
|
+
const result = await loadCapabilityProfiles({
|
|
9343
|
+
fetch: globalThis.fetch,
|
|
9344
|
+
now: () => Date.now()
|
|
9345
|
+
});
|
|
9346
|
+
return result.profiles.length > 0 ? result.profiles : [...CURATED_FRONTIER_PROFILES];
|
|
9347
|
+
} catch {
|
|
9348
|
+
return [...CURATED_FRONTIER_PROFILES];
|
|
9349
|
+
}
|
|
10700
9350
|
};
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
|
|
10709
|
-
|
|
10710
|
-
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
if (deps.isDone(result)) {
|
|
10717
|
-
return {
|
|
10718
|
-
iterations: results.length,
|
|
10719
|
-
termination: "done",
|
|
10720
|
-
decision: result.decision,
|
|
10721
|
-
results
|
|
9351
|
+
}
|
|
9352
|
+
function createDetect(exec) {
|
|
9353
|
+
return async () => {
|
|
9354
|
+
const registry = new RuntimeRegistry({
|
|
9355
|
+
fetch: globalThis.fetch,
|
|
9356
|
+
clock: () => new Date().toISOString(),
|
|
9357
|
+
foundryDiscovery: foundryDiscoveryFromExec(exec)
|
|
9358
|
+
});
|
|
9359
|
+
const snapshots = await registry.probe(DETECTION_RUNTIMES);
|
|
9360
|
+
return snapshots.map((snapshot) => {
|
|
9361
|
+
const detected = {
|
|
9362
|
+
id: snapshot.id,
|
|
9363
|
+
baseURL: snapshot.baseURL,
|
|
9364
|
+
reachable: snapshot.reachable,
|
|
9365
|
+
models: snapshot.models.map((model) => model.modelID)
|
|
10722
9366
|
};
|
|
9367
|
+
if (snapshot.models.some((model) => model.loaded !== undefined)) {
|
|
9368
|
+
detected.modelInfo = snapshot.models;
|
|
9369
|
+
}
|
|
9370
|
+
if (snapshot.error !== undefined) {
|
|
9371
|
+
detected.error = snapshot.error;
|
|
9372
|
+
}
|
|
9373
|
+
return detected;
|
|
9374
|
+
});
|
|
9375
|
+
};
|
|
9376
|
+
}
|
|
9377
|
+
function createProbeRuntime(exec) {
|
|
9378
|
+
return async (id, baseURL) => {
|
|
9379
|
+
const registry = new RuntimeRegistry({
|
|
9380
|
+
fetch: globalThis.fetch,
|
|
9381
|
+
clock: () => new Date().toISOString(),
|
|
9382
|
+
foundryDiscovery: foundryDiscoveryFromExec(exec)
|
|
9383
|
+
});
|
|
9384
|
+
const runtime = {
|
|
9385
|
+
id,
|
|
9386
|
+
enabled: true,
|
|
9387
|
+
baseURL,
|
|
9388
|
+
defaultModel: { providerID: id, modelID: "probe" }
|
|
9389
|
+
};
|
|
9390
|
+
const [snapshot] = await registry.probe([runtime]);
|
|
9391
|
+
const detected = {
|
|
9392
|
+
id,
|
|
9393
|
+
baseURL: snapshot?.baseURL ?? baseURL,
|
|
9394
|
+
reachable: snapshot?.reachable ?? false,
|
|
9395
|
+
models: snapshot?.models.map((model) => model.modelID) ?? []
|
|
9396
|
+
};
|
|
9397
|
+
if (snapshot?.models.some((model) => model.loaded !== undefined) === true) {
|
|
9398
|
+
detected.modelInfo = snapshot.models;
|
|
10723
9399
|
}
|
|
10724
|
-
|
|
10725
|
-
|
|
10726
|
-
|
|
10727
|
-
|
|
10728
|
-
decision: results.at(-1)?.decision,
|
|
10729
|
-
results
|
|
9400
|
+
if (snapshot?.error !== undefined) {
|
|
9401
|
+
detected.error = snapshot.error;
|
|
9402
|
+
}
|
|
9403
|
+
return detected;
|
|
10730
9404
|
};
|
|
10731
9405
|
}
|
|
10732
9406
|
|
|
10733
|
-
// src/
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
roleID: z17.string().min(1),
|
|
10737
|
-
agentName: z17.string().min(1)
|
|
10738
|
-
}).strict();
|
|
10739
|
-
var RosterSchema = z17.object({
|
|
10740
|
-
universe: z17.string().min(1),
|
|
10741
|
-
entries: z17.array(RosterEntrySchema)
|
|
10742
|
-
}).strict();
|
|
10743
|
-
var ROSTER_JSON_FENCE = /```json\s*([\s\S]*?)```/;
|
|
10744
|
-
|
|
10745
|
-
class RosterParseError extends Error {
|
|
10746
|
-
code;
|
|
10747
|
-
constructor(code, message) {
|
|
10748
|
-
super(message);
|
|
10749
|
-
this.name = "RosterParseError";
|
|
10750
|
-
this.code = code;
|
|
10751
|
-
}
|
|
10752
|
-
}
|
|
10753
|
-
function parseRoster(text2) {
|
|
10754
|
-
const result = parseRosterResult(text2);
|
|
10755
|
-
if (!result.ok) {
|
|
10756
|
-
throw result.error;
|
|
10757
|
-
}
|
|
10758
|
-
return result.roster;
|
|
10759
|
-
}
|
|
10760
|
-
function parseRosterResult(text2) {
|
|
10761
|
-
const match = ROSTER_JSON_FENCE.exec(text2);
|
|
10762
|
-
if (match === null || match[1] === undefined) {
|
|
10763
|
-
return {
|
|
10764
|
-
ok: false,
|
|
10765
|
-
error: new RosterParseError("missing-json-block", "openteam roster: no JSON block found in roster file")
|
|
10766
|
-
};
|
|
10767
|
-
}
|
|
10768
|
-
let data;
|
|
10769
|
-
try {
|
|
10770
|
-
data = JSON.parse(match[1]);
|
|
10771
|
-
} catch (error) {
|
|
9407
|
+
// src/config/graphFeatureGate.ts
|
|
9408
|
+
function checkCertificate(status, name) {
|
|
9409
|
+
if (status.status === "absent") {
|
|
10772
9410
|
return {
|
|
10773
|
-
|
|
10774
|
-
|
|
9411
|
+
code: `${name}-certificate-absent`,
|
|
9412
|
+
detail: `${name} certificate not found — run the ${name} certification suite to produce it`
|
|
10775
9413
|
};
|
|
10776
9414
|
}
|
|
10777
|
-
|
|
10778
|
-
if (!parsed.success) {
|
|
9415
|
+
if (status.status === "invalid") {
|
|
10779
9416
|
return {
|
|
10780
|
-
|
|
10781
|
-
|
|
9417
|
+
code: `${name}-certificate-invalid`,
|
|
9418
|
+
detail: `${name} certificate rejected by recomputing parser: ${status.code} — ${status.detail}`
|
|
10782
9419
|
};
|
|
10783
9420
|
}
|
|
10784
|
-
return
|
|
10785
|
-
}
|
|
10786
|
-
var GUARANTEED_ROLE_IDS = [
|
|
10787
|
-
"orchestrator",
|
|
10788
|
-
"guardian",
|
|
10789
|
-
"scribe",
|
|
10790
|
-
"ralph"
|
|
10791
|
-
];
|
|
10792
|
-
var GUARANTEED_ROLE_DEFAULT_AGENT_NAMES = {
|
|
10793
|
-
orchestrator: "openteam",
|
|
10794
|
-
guardian: "guardian",
|
|
10795
|
-
scribe: "scribe",
|
|
10796
|
-
ralph: "ralph"
|
|
10797
|
-
};
|
|
10798
|
-
function defaultAgentNameForGuaranteedRole(roleID) {
|
|
10799
|
-
return GUARANTEED_ROLE_DEFAULT_AGENT_NAMES[roleID];
|
|
10800
|
-
}
|
|
10801
|
-
function isGuaranteedRoleID(roleID) {
|
|
10802
|
-
return GUARANTEED_ROLE_IDS.includes(roleID);
|
|
10803
|
-
}
|
|
10804
|
-
function missingGuaranteedRoles(roster) {
|
|
10805
|
-
const present = new Set(roster.entries.map((entry) => entry.roleID));
|
|
10806
|
-
return GUARANTEED_ROLE_IDS.filter((roleID) => !present.has(roleID));
|
|
10807
|
-
}
|
|
10808
|
-
function isRoleProfileLike(value) {
|
|
10809
|
-
return typeof value === "object" && value !== null && "roleID" in value && "defaultTier" in value;
|
|
10810
|
-
}
|
|
10811
|
-
function configuredRolesFromAuditInput(input) {
|
|
10812
|
-
if ("configuredRoles" in input && !isRoleProfileLike(input.configuredRoles)) {
|
|
10813
|
-
return input.configuredRoles;
|
|
10814
|
-
}
|
|
10815
|
-
return input;
|
|
9421
|
+
return;
|
|
10816
9422
|
}
|
|
10817
|
-
function
|
|
10818
|
-
const
|
|
10819
|
-
|
|
10820
|
-
|
|
10821
|
-
|
|
10822
|
-
|
|
10823
|
-
|
|
10824
|
-
|
|
10825
|
-
|
|
10826
|
-
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
|
|
9423
|
+
function evaluateGraphGate(input) {
|
|
9424
|
+
const violations = [];
|
|
9425
|
+
const advisories = [];
|
|
9426
|
+
const shadowV = checkCertificate(input.shadow, "shadow");
|
|
9427
|
+
if (shadowV !== undefined)
|
|
9428
|
+
violations.push(shadowV);
|
|
9429
|
+
const releaseV = checkCertificate(input.release, "release");
|
|
9430
|
+
if (releaseV !== undefined)
|
|
9431
|
+
violations.push(releaseV);
|
|
9432
|
+
const soakV = checkCertificate(input.soak, "soak");
|
|
9433
|
+
if (soakV !== undefined)
|
|
9434
|
+
advisories.push(soakV);
|
|
9435
|
+
if (input.migration === "pending") {
|
|
9436
|
+
violations.push({
|
|
9437
|
+
code: "migration-pending",
|
|
9438
|
+
detail: "legacy migration has pending work — run the migration tool to completion before enabling active mode"
|
|
9439
|
+
});
|
|
9440
|
+
} else if (input.migration === "unknown") {
|
|
9441
|
+
violations.push({
|
|
9442
|
+
code: "migration-unknown",
|
|
9443
|
+
detail: "could not determine migration status — check ledger readability and storage access"
|
|
9444
|
+
});
|
|
9445
|
+
}
|
|
9446
|
+
if (!input.operatorApproval) {
|
|
9447
|
+
violations.push({
|
|
9448
|
+
code: "operator-approval-missing",
|
|
9449
|
+
detail: "set graph.operatorApproval to true in .opencode/openteam.json after reviewing all certificates and completing migration — this is a declaration of intent, not a proof"
|
|
9450
|
+
});
|
|
10838
9451
|
}
|
|
10839
|
-
return auditParsedRoster(rosterOrText, configuredRoles);
|
|
10840
|
-
}
|
|
10841
|
-
function auditParsedRoster(roster, configuredRoles) {
|
|
10842
|
-
const missingGuaranteedRoleIDs = missingGuaranteedRoles(roster);
|
|
10843
|
-
const missing = new Set(missingGuaranteedRoleIDs);
|
|
10844
|
-
const findings = missingGuaranteedRoleIDs.map((roleID) => ({
|
|
10845
|
-
kind: "missing-guaranteed-role",
|
|
10846
|
-
roleID
|
|
10847
|
-
}));
|
|
10848
|
-
const unprofiledRoleEntries = [];
|
|
10849
|
-
const misassignedGuaranteedRoleEntries = [];
|
|
10850
|
-
roster.entries.forEach((entry) => {
|
|
10851
|
-
const profile = getRoleProfile(entry.roleID, configuredRoles);
|
|
10852
|
-
if (profile === undefined && !isKnownNonWorkerRole(entry.roleID)) {
|
|
10853
|
-
unprofiledRoleEntries.push({
|
|
10854
|
-
roleID: entry.roleID,
|
|
10855
|
-
agentName: entry.agentName,
|
|
10856
|
-
severity: "info"
|
|
10857
|
-
});
|
|
10858
|
-
findings.push({
|
|
10859
|
-
kind: "unresolved-role-profile",
|
|
10860
|
-
roleID: entry.roleID,
|
|
10861
|
-
agentName: entry.agentName
|
|
10862
|
-
});
|
|
10863
|
-
}
|
|
10864
|
-
if (isGuaranteedRoleID(entry.agentName) && entry.roleID !== entry.agentName && missing.has(entry.agentName)) {
|
|
10865
|
-
misassignedGuaranteedRoleEntries.push({
|
|
10866
|
-
roleID: entry.roleID,
|
|
10867
|
-
agentName: entry.agentName,
|
|
10868
|
-
missingRoleID: entry.agentName,
|
|
10869
|
-
reason: "agentNameMatchesMissingGuaranteedRole"
|
|
10870
|
-
});
|
|
10871
|
-
findings.push({
|
|
10872
|
-
kind: "non-canonical-role-id",
|
|
10873
|
-
roleID: entry.roleID,
|
|
10874
|
-
agentName: entry.agentName,
|
|
10875
|
-
canonicalRoleID: entry.agentName
|
|
10876
|
-
});
|
|
10877
|
-
return;
|
|
10878
|
-
}
|
|
10879
|
-
if (profile !== undefined && isGuaranteedRoleID(profile.roleID) && profile.roleID !== entry.roleID && missing.has(profile.roleID)) {
|
|
10880
|
-
misassignedGuaranteedRoleEntries.push({
|
|
10881
|
-
roleID: entry.roleID,
|
|
10882
|
-
agentName: entry.agentName,
|
|
10883
|
-
missingRoleID: profile.roleID,
|
|
10884
|
-
reason: "roleIDResolvesToMissingGuaranteedRole"
|
|
10885
|
-
});
|
|
10886
|
-
findings.push({
|
|
10887
|
-
kind: "non-canonical-role-id",
|
|
10888
|
-
roleID: entry.roleID,
|
|
10889
|
-
agentName: entry.agentName,
|
|
10890
|
-
canonicalRoleID: profile.roleID
|
|
10891
|
-
});
|
|
10892
|
-
}
|
|
10893
|
-
});
|
|
10894
9452
|
return {
|
|
10895
|
-
|
|
10896
|
-
|
|
10897
|
-
|
|
10898
|
-
unprofiledRoleEntries,
|
|
10899
|
-
misassignedGuaranteedRoleEntries
|
|
9453
|
+
allowed: violations.length === 0,
|
|
9454
|
+
violations,
|
|
9455
|
+
advisories
|
|
10900
9456
|
};
|
|
10901
9457
|
}
|
|
10902
|
-
function
|
|
10903
|
-
|
|
10904
|
-
if (missing.length === 0) {
|
|
10905
|
-
return roster;
|
|
10906
|
-
}
|
|
10907
|
-
const added = missing.map((roleID) => ({
|
|
10908
|
-
roleID,
|
|
10909
|
-
agentName: defaultAgentNameForGuaranteedRole(roleID)
|
|
10910
|
-
}));
|
|
10911
|
-
return { universe: roster.universe, entries: [...roster.entries, ...added] };
|
|
10912
|
-
}
|
|
10913
|
-
async function generateRoster(prompt, source) {
|
|
10914
|
-
const draft = await source(prompt);
|
|
10915
|
-
const validated = RosterSchema.parse(draft);
|
|
10916
|
-
return enforceGuaranteedRoles(validated);
|
|
10917
|
-
}
|
|
10918
|
-
function missingAgentFileMessage(agentName) {
|
|
10919
|
-
return `openteam: guaranteed role agent "${agentName}" has no agent file in this workspace, ` + `so it cannot be dispatched. Run \`openteam setup\` to generate the required agent ` + `files before dispatching (see #153).`;
|
|
9458
|
+
function isGateApplicable(mode) {
|
|
9459
|
+
return mode === "active";
|
|
10920
9460
|
}
|
|
10921
9461
|
|
|
9462
|
+
// src/messages/commands.ts
|
|
9463
|
+
var clearCacheMessages = {
|
|
9464
|
+
header: "openteam clear-cache — frozen plugin cache entries:",
|
|
9465
|
+
columns: {
|
|
9466
|
+
specDir: "spec dir",
|
|
9467
|
+
pinned: "spec-pinned",
|
|
9468
|
+
installed: "installed",
|
|
9469
|
+
mtime: "mtime"
|
|
9470
|
+
},
|
|
9471
|
+
reparseSkipSuffix: " [SKIP — reparse point]",
|
|
9472
|
+
deletedLabel: "deleted.",
|
|
9473
|
+
lockedLabel: (message) => `[LOCKED] ${message}`,
|
|
9474
|
+
pathOutsideWarning: (specDir, absolutePath) => ` ⚠ ${specDir}: path outside cacheRoot (${absolutePath}), skipped.`,
|
|
9475
|
+
processed: (count) => `${count} entry(ies) processed.`,
|
|
9476
|
+
found: (count) => `${count} entry(ies) found. Use --delete to remove them.`
|
|
9477
|
+
};
|
|
9478
|
+
var baselineMessages = {
|
|
9479
|
+
effectiveAuto: "cheapest-capable (auto)",
|
|
9480
|
+
pinnedSuffix: (ref) => `${ref} (pinned)`,
|
|
9481
|
+
summary: (params) => [
|
|
9482
|
+
"openteam baseline:",
|
|
9483
|
+
` mode: ${params.mode}`,
|
|
9484
|
+
` pinned: ${params.pinned}`,
|
|
9485
|
+
` hardDefault: ${params.hardDefault}`,
|
|
9486
|
+
` effective: ${params.effective}`
|
|
9487
|
+
],
|
|
9488
|
+
invalidModel: (input) => `Invalid model "${input}". Use the provider/model format, e.g. anthropic/claude-sonnet-4-5.`,
|
|
9489
|
+
pinnedTo: (ref) => `Baseline pinned to ${ref} (pinned mode).`,
|
|
9490
|
+
autoMode: "Baseline set to auto mode (cheapest-capable)."
|
|
9491
|
+
};
|
|
9492
|
+
var localMessages = {
|
|
9493
|
+
help: {
|
|
9494
|
+
status: " openteam local status Show the routing mode (local-first / local-only / frontier-only)",
|
|
9495
|
+
off: " openteam local off Use frontier models only (no local runtime)",
|
|
9496
|
+
only: " openteam local only Use local models only (never frontier)",
|
|
9497
|
+
on: " openteam local on Re-enable local-first routing"
|
|
9498
|
+
},
|
|
9499
|
+
noRuntimes: "none",
|
|
9500
|
+
modeFrontierOnly: "frontier-only (frontierOnly)",
|
|
9501
|
+
modeLocalOnly: "local-only (localOnly)",
|
|
9502
|
+
modeLocalFirst: "local-first",
|
|
9503
|
+
summary: (params) => [
|
|
9504
|
+
"openteam local:",
|
|
9505
|
+
` mode: ${params.mode}`,
|
|
9506
|
+
` frontierOnly: ${params.frontierOnly}`,
|
|
9507
|
+
` localOnly: ${params.localOnly}`,
|
|
9508
|
+
` privacy: ${params.privacy}`,
|
|
9509
|
+
` runtimes: ${params.runtimes}`
|
|
9510
|
+
],
|
|
9511
|
+
frontierOnlyNoChange: "No change: already in frontier-only mode.",
|
|
9512
|
+
privacyAdjustedNote: " Privacy adjusted to 'consentBeforeFrontier' (forceLocalOnSensitive requires a local runtime).",
|
|
9513
|
+
frontierOnlyEnabled: "Frontier-only mode enabled (frontierOnly=true, localOnly=false).",
|
|
9514
|
+
localOnlyNoRuntimes: "Cannot enable local-only mode: no enabled local runtime is configured. Run 'openteam setup' or enable a local runtime in .opencode/openteam.json first.",
|
|
9515
|
+
localOnlyNoChange: "No change: already in local-only mode.",
|
|
9516
|
+
localOnlyEnabled: "Local-only mode enabled (localOnly=true, frontierOnly=false).",
|
|
9517
|
+
localFirstNoChange: "No change: local-first routing is already active.",
|
|
9518
|
+
localFirstReEnabled: "Local-first routing re-enabled (frontierOnly=false, localOnly=false). Make sure a local runtime is reachable.",
|
|
9519
|
+
unknownSubcommand: (subcommand, help) => `Unknown local subcommand: ${subcommand}
|
|
9520
|
+
|
|
9521
|
+
${help}`
|
|
9522
|
+
};
|
|
9523
|
+
var frontierCatalogMessages = {
|
|
9524
|
+
free: "free",
|
|
9525
|
+
costPerMillion: (inputUSD, outputUSD) => `$${inputUSD}/$${outputUSD} per 1M`
|
|
9526
|
+
};
|
|
9527
|
+
var migrateMessages = {
|
|
9528
|
+
header: "openteam migrate:",
|
|
9529
|
+
nothingToMigrate: " nothing to migrate — the P15b layout is already in place.",
|
|
9530
|
+
counts: (params) => [
|
|
9531
|
+
` moved: ${params.moved} file(s)`,
|
|
9532
|
+
` deduped: ${params.deduped} file(s) (destination already identical)`,
|
|
9533
|
+
` conflicts: ${params.conflicts} file(s)`
|
|
9534
|
+
],
|
|
9535
|
+
relocatedHeader: " relocated:",
|
|
9536
|
+
relocatedEntry: (from, to, deduped) => ` · ${from} → ${to}${deduped ? " (deduped)" : ""}`,
|
|
9537
|
+
conflictsHeader: " ⚠ left in place (destination exists with different content — no data lost):",
|
|
9538
|
+
conflictEntry: (from, to) => ` · ${from} → ${to}`,
|
|
9539
|
+
manualWorktreesHeader: " ⚠ legacy git worktree(s) need a manual move (run `git worktree move`):",
|
|
9540
|
+
manualWorktreeEntry: (label, from, to) => ` · ${label}: ${from} → ${to}`,
|
|
9541
|
+
success: " ✓ machine-local runtime output relocated under .opencode/openteam-local/."
|
|
9542
|
+
};
|
|
9543
|
+
var rolesInitMessages = {
|
|
9544
|
+
header: "openteam roles init:",
|
|
9545
|
+
title: "openteam roles init",
|
|
9546
|
+
noRoster: (path) => ` ✗ no roster at ${path}.`,
|
|
9547
|
+
noRosterRemedy: " Cast the team first: run `openteam setup`, then ask the orchestrator to register the cast.",
|
|
9548
|
+
unparseableRoster: (path, error) => ` ✗ ${path} is present but unparseable: ${error}`,
|
|
9549
|
+
unparseableRosterRemedy: " Run `openteam doctor` for the remedy, then re-run this command.",
|
|
9550
|
+
allProfiled: (count) => ` ✓ ${count} roster role(s) checked; no unprofiled roles — every role resolves a routing profile.`,
|
|
9551
|
+
whyTitle: "Why you are being asked",
|
|
9552
|
+
why: (count) => [
|
|
9553
|
+
`${count} roster role(s) resolve no routing profile.`,
|
|
9554
|
+
"Until a profile exists they are frontier-eligible: their work can escalate",
|
|
9555
|
+
"to a frontier model. A 'local only' note in the roster prose does not",
|
|
9556
|
+
"prevent this — prose outside the JSON fence is never read."
|
|
9557
|
+
].join(`
|
|
9558
|
+
`),
|
|
9559
|
+
policyQuestion: (roleID, agentName) => `Routing policy for "${roleID}" (${agentName})`,
|
|
9560
|
+
localOnlyLabel: (roleID, agentName) => `Local only — ${roleID} (${agentName}) never leaves this machine`,
|
|
9561
|
+
localOnlyHint: "sets requiresLocalRuntime; the role fails closed when no local runtime is reachable",
|
|
9562
|
+
frontierOkLabel: "Local first, frontier allowed",
|
|
9563
|
+
frontierOkHint: "records today's implicit behaviour: cheapest-capable frontier when the task warrants it",
|
|
9564
|
+
skipLabel: "Skip for now",
|
|
9565
|
+
skipHint: "leaves the role unprofiled, so it stays frontier-eligible and doctor keeps reporting it",
|
|
9566
|
+
runtimeQuestion: (roleID) => `Preferred local runtime(s) for "${roleID}" (in failover order)`,
|
|
9567
|
+
runtimeHint: "machine-local — written to the git-ignored overlay",
|
|
9568
|
+
cancelled: (reason) => ` ✗ cancelled (${reason}); nothing was written.`,
|
|
9569
|
+
invalidResult: (reason) => ` ✗ refusing to write: the resulting config is invalid — ${reason}`,
|
|
9570
|
+
nothingWritten: (skipped) => ` · skipped ${skipped}; nothing written.`,
|
|
9571
|
+
nothingToWriteOutro: "Nothing to write.",
|
|
9572
|
+
pinnedLocal: (roleIDs, path) => ` ✓ pinned local-only: ${roleIDs} → ${path} (tracked, travels with the roster)`,
|
|
9573
|
+
frontierAllowed: (roleIDs, path) => ` ✓ frontier allowed: ${roleIDs} → ${path} (model choice stays "auto": cheapest-capable per task)`,
|
|
9574
|
+
runtimeBinding: (path) => ` ✓ runtime binding: ${path} (git-ignored, machine-local)`,
|
|
9575
|
+
skippedSummary: (roleIDs) => ` · skipped: ${roleIDs} — still frontier-eligible; re-run this command to revisit.`,
|
|
9576
|
+
outro: "Role policy updated."
|
|
9577
|
+
};
|
|
9578
|
+
|
|
10922
9579
|
// src/orchestrator/worktreeReconciler.ts
|
|
10923
9580
|
function planOne(wt) {
|
|
10924
9581
|
const base = { runID: wt.runID, path: wt.path };
|
|
@@ -10973,11 +9630,11 @@ function planWorktreeReconciliation(input) {
|
|
|
10973
9630
|
}
|
|
10974
9631
|
|
|
10975
9632
|
// src/telemetry/otelConfig.ts
|
|
10976
|
-
import { z as
|
|
10977
|
-
var OtelBackendConfigSchema =
|
|
10978
|
-
backend:
|
|
10979
|
-
connectionEnv:
|
|
10980
|
-
serviceName:
|
|
9633
|
+
import { z as z17 } from "zod";
|
|
9634
|
+
var OtelBackendConfigSchema = z17.object({
|
|
9635
|
+
backend: z17.literal("opentelemetry"),
|
|
9636
|
+
connectionEnv: z17.string().min(1),
|
|
9637
|
+
serviceName: z17.string().min(1).optional()
|
|
10981
9638
|
}).strict();
|
|
10982
9639
|
function parseConnectionString(raw) {
|
|
10983
9640
|
const pairs = new Map;
|
|
@@ -11921,31 +10578,31 @@ function mergeOpencodeConfigs(base, override) {
|
|
|
11921
10578
|
}
|
|
11922
10579
|
|
|
11923
10580
|
// src/graph/soakLedger.ts
|
|
11924
|
-
import { z as
|
|
11925
|
-
var Sha256Schema2 =
|
|
11926
|
-
var SoakPlatformSchema =
|
|
11927
|
-
var PrivacyPairSchema =
|
|
11928
|
-
surfacesScanned:
|
|
11929
|
-
rawFindings:
|
|
10581
|
+
import { z as z18 } from "zod";
|
|
10582
|
+
var Sha256Schema2 = z18.string().regex(/^[0-9a-f]{64}$/);
|
|
10583
|
+
var SoakPlatformSchema = z18.enum(["linux", "win32", "darwin"]);
|
|
10584
|
+
var PrivacyPairSchema = z18.object({
|
|
10585
|
+
surfacesScanned: z18.number().int().nonnegative(),
|
|
10586
|
+
rawFindings: z18.number().int().nonnegative()
|
|
11930
10587
|
}).strict();
|
|
11931
|
-
var DuplicateEffectPairSchema =
|
|
11932
|
-
effectsExamined:
|
|
11933
|
-
duplicatesFound:
|
|
10588
|
+
var DuplicateEffectPairSchema = z18.object({
|
|
10589
|
+
effectsExamined: z18.number().int().nonnegative(),
|
|
10590
|
+
duplicatesFound: z18.number().int().nonnegative()
|
|
11934
10591
|
}).strict();
|
|
11935
|
-
var ModelVerificationPairSchema =
|
|
11936
|
-
nodesChecked:
|
|
11937
|
-
unverified:
|
|
11938
|
-
mismatched:
|
|
10592
|
+
var ModelVerificationPairSchema = z18.object({
|
|
10593
|
+
nodesChecked: z18.number().int().nonnegative(),
|
|
10594
|
+
unverified: z18.number().int().nonnegative(),
|
|
10595
|
+
mismatched: z18.number().int().nonnegative()
|
|
11939
10596
|
}).strict();
|
|
11940
|
-
var SoakObservationSchema =
|
|
11941
|
-
version:
|
|
11942
|
-
seq:
|
|
11943
|
-
timestamp:
|
|
10597
|
+
var SoakObservationSchema = z18.object({
|
|
10598
|
+
version: z18.literal(1),
|
|
10599
|
+
seq: z18.number().int().nonnegative(),
|
|
10600
|
+
timestamp: z18.string().datetime(),
|
|
11944
10601
|
platform: SoakPlatformSchema,
|
|
11945
|
-
opencodeVersion:
|
|
11946
|
-
provenance:
|
|
10602
|
+
opencodeVersion: z18.string().min(1),
|
|
10603
|
+
provenance: z18.enum(["genuine-usage", "ci-synthetic"]),
|
|
11947
10604
|
traceDigest: Sha256Schema2,
|
|
11948
|
-
criticalDivergences:
|
|
10605
|
+
criticalDivergences: z18.number().int().nonnegative().nullable(),
|
|
11949
10606
|
privacy: PrivacyPairSchema.nullable(),
|
|
11950
10607
|
duplicateEffects: DuplicateEffectPairSchema.nullable(),
|
|
11951
10608
|
modelVerification: ModelVerificationPairSchema.nullable(),
|
|
@@ -12304,7 +10961,7 @@ function buildOpenTeamConfig(answers) {
|
|
|
12304
10961
|
return runtime;
|
|
12305
10962
|
});
|
|
12306
10963
|
const primaryLocal = firstEnabled(answers.runtimes);
|
|
12307
|
-
const
|
|
10964
|
+
const localDefault = primaryLocal !== undefined ? {
|
|
12308
10965
|
providerID: choiceProviderID(primaryLocal),
|
|
12309
10966
|
modelID: primaryLocal.defaultModelID
|
|
12310
10967
|
} : null;
|
|
@@ -12317,7 +10974,7 @@ function buildOpenTeamConfig(answers) {
|
|
|
12317
10974
|
},
|
|
12318
10975
|
router: {
|
|
12319
10976
|
mode: answers.routerMode,
|
|
12320
|
-
localDefault
|
|
10977
|
+
localDefault,
|
|
12321
10978
|
frontierOnly: primaryLocal === undefined,
|
|
12322
10979
|
localOnly
|
|
12323
10980
|
},
|
|
@@ -13647,8 +12304,6 @@ var HELP = [
|
|
|
13647
12304
|
" openteam graph cancel <run> Cancel an active Graph run",
|
|
13648
12305
|
" openteam graph recover Recover a partial tail of the Graph journal",
|
|
13649
12306
|
" openteam graph worktrees Reconcile orphaned Graph worktrees",
|
|
13650
|
-
" openteam loop Unattended Ralph-pattern loop over the backlog (see #152)",
|
|
13651
|
-
" openteam loop --dry-run Preview the loop plan without dispatching anything",
|
|
13652
12307
|
" openteam mcp Read-only MCP server (team memory) over stdio",
|
|
13653
12308
|
" openteam report Cost/savings summary (telemetry)",
|
|
13654
12309
|
" openteam clear-cache List the plugin's frozen cache entries",
|
|
@@ -13690,29 +12345,6 @@ var PURGE_HELP = [
|
|
|
13690
12345
|
"A non-interactive terminal without --yes fails instead of hanging."
|
|
13691
12346
|
].join(`
|
|
13692
12347
|
`);
|
|
13693
|
-
var LOOP_HELP = [
|
|
13694
|
-
"openteam loop — unattended Ralph-pattern loop over the shared backlog (#152)",
|
|
13695
|
-
"",
|
|
13696
|
-
"Usage:",
|
|
13697
|
-
" openteam loop --dry-run Preview the plan (iterations, prompt, stops); dispatch nothing",
|
|
13698
|
-
" openteam loop --yes Run unattended until the backlog is empty or a stop fires",
|
|
13699
|
-
"",
|
|
13700
|
-
"Options:",
|
|
13701
|
-
" --max-iterations <N> Maximum iterations (default 20)",
|
|
13702
|
-
" --budget-usd <N> Spend ceiling",
|
|
13703
|
-
" --sleep <seconds> Pause between iterations (default 2)",
|
|
13704
|
-
" --no-progress-limit <N> Stop after N iterations with no progress (default 2)",
|
|
13705
|
-
" --fresh-context New context per iteration (default on — the pure Ralph pattern)",
|
|
13706
|
-
" --no-fresh-context Keep context across iterations",
|
|
13707
|
-
" --stop-on-red-tests Stop when the gates go red",
|
|
13708
|
-
" --backlog <path> Backlog Markdown file (default .opencode/openteam/backlog.md)",
|
|
13709
|
-
" --dry-run Print the plan and invoke nothing",
|
|
13710
|
-
" --yes Run unattended",
|
|
13711
|
-
"",
|
|
13712
|
-
"Stops are evaluated BEFORE each iteration spends: empty backlog, max-iterations,",
|
|
13713
|
-
"budget, red tests, no-progress."
|
|
13714
|
-
].join(`
|
|
13715
|
-
`);
|
|
13716
12348
|
var GRAPH_OPERATOR_TASK_ID = "graph-operator";
|
|
13717
12349
|
var PURGE_FLAG_TO_CATEGORY = new Map(Object.entries(PURGE_CATEGORY_FLAG).map(([id, flag]) => [flag, id]));
|
|
13718
12350
|
function parseArgs(argv) {
|
|
@@ -14018,13 +12650,13 @@ async function resolveOperatorGraphPolicy(config, operator) {
|
|
|
14018
12650
|
};
|
|
14019
12651
|
}
|
|
14020
12652
|
function checkOperatorGate(policy) {
|
|
14021
|
-
const
|
|
14022
|
-
if (
|
|
12653
|
+
const decision = decideIngress(policy, GRAPH_OPERATOR_TASK_ID);
|
|
12654
|
+
if (decision.route === "graph") {
|
|
14023
12655
|
return;
|
|
14024
12656
|
}
|
|
14025
12657
|
return {
|
|
14026
12658
|
exitCode: 1,
|
|
14027
|
-
stdout: renderLegacyGraphReason(
|
|
12659
|
+
stdout: renderLegacyGraphReason(decision)
|
|
14028
12660
|
};
|
|
14029
12661
|
}
|
|
14030
12662
|
async function runGraphStatus(deps, configPath, configResolution) {
|
|
@@ -14092,327 +12724,62 @@ async function runGraphRecover(deps, configPath, configResolution) {
|
|
|
14092
12724
|
const operator = requireGraphOperator(deps);
|
|
14093
12725
|
const snapshot = await deps.readGraphSnapshot(configPath, configResolution);
|
|
14094
12726
|
const plan2 = planGraphRecover(snapshot);
|
|
14095
|
-
if (plan2.kind === "none") {
|
|
14096
|
-
return {
|
|
14097
|
-
exitCode: 0,
|
|
14098
|
-
stdout: `openteam graph recover: ${plan2.reason}`
|
|
14099
|
-
};
|
|
14100
|
-
}
|
|
14101
|
-
if (plan2.kind === "blocked") {
|
|
14102
|
-
return {
|
|
14103
|
-
exitCode: 1,
|
|
14104
|
-
stdout: `openteam graph recover: ${plan2.reason}`
|
|
14105
|
-
};
|
|
14106
|
-
}
|
|
14107
|
-
const config = await deps.loadConfig(configPath, configResolution);
|
|
14108
|
-
const policy = await resolveOperatorGraphPolicy(config, operator);
|
|
14109
|
-
const blocked = checkOperatorGate(policy);
|
|
14110
|
-
if (blocked !== undefined) {
|
|
14111
|
-
return blocked;
|
|
14112
|
-
}
|
|
14113
|
-
const recovered = await operator.recovery.recover(config);
|
|
14114
|
-
return {
|
|
14115
|
-
exitCode: 0,
|
|
14116
|
-
stdout: `openteam graph recover: ${recovered.reason} (${recovered.tail})`
|
|
14117
|
-
};
|
|
14118
|
-
}
|
|
14119
|
-
async function runGraphWorktrees(deps, configPath, configResolution) {
|
|
14120
|
-
const operator = requireGraphOperator(deps);
|
|
14121
|
-
const config = await deps.loadConfig(configPath, configResolution);
|
|
14122
|
-
const enabled = config.graph?.worktrees?.enabled ?? false;
|
|
14123
|
-
if (!enabled) {
|
|
14124
|
-
return {
|
|
14125
|
-
exitCode: 0,
|
|
14126
|
-
stdout: renderWorktreeReconciliation(planWorktreeReconciliation({ enabled: false, worktrees: [] }))
|
|
14127
|
-
};
|
|
14128
|
-
}
|
|
14129
|
-
const policy = await resolveOperatorGraphPolicy(config, operator);
|
|
14130
|
-
const blocked = checkOperatorGate(policy);
|
|
14131
|
-
if (blocked !== undefined) {
|
|
14132
|
-
return blocked;
|
|
14133
|
-
}
|
|
14134
|
-
const observations = await operator.worktrees.listObservations(config);
|
|
14135
|
-
const report = planWorktreeReconciliation({
|
|
14136
|
-
enabled,
|
|
14137
|
-
worktrees: observations
|
|
14138
|
-
});
|
|
14139
|
-
if (report.plans.length === 0) {
|
|
14140
|
-
return { exitCode: 0, stdout: renderWorktreeReconciliation(report) };
|
|
14141
|
-
}
|
|
14142
|
-
for (const plan2 of report.plans) {
|
|
14143
|
-
if (!plan2.safe || plan2.action === "retain" || plan2.action === "block") {
|
|
14144
|
-
continue;
|
|
14145
|
-
}
|
|
14146
|
-
await operator.worktrees.apply(plan2.action, plan2.runID);
|
|
14147
|
-
}
|
|
14148
|
-
return {
|
|
14149
|
-
exitCode: report.blocked > 0 ? 1 : 0,
|
|
14150
|
-
stdout: renderWorktreeReconciliation(report)
|
|
14151
|
-
};
|
|
14152
|
-
}
|
|
14153
|
-
var DEFAULT_LOOP_MAX_ITERATIONS = 20;
|
|
14154
|
-
var DEFAULT_LOOP_SLEEP_SECONDS = 2;
|
|
14155
|
-
var DEFAULT_LOOP_NO_PROGRESS_LIMIT = 2;
|
|
14156
|
-
var DEFAULT_LOOP_BACKLOG_PATH = ".opencode/openteam/backlog.md";
|
|
14157
|
-
var BACKLOG_LINE = /^(\s*)- \[( |x|X)\]\s+(.*)$/;
|
|
14158
|
-
var BACKLOG_CAST = /^\[@([^\]]+)\]\s*(.*)$/;
|
|
14159
|
-
function parseBacklog2(text2) {
|
|
14160
|
-
const items = [];
|
|
14161
|
-
const lines = text2.split(/\r?\n/);
|
|
14162
|
-
for (let i = 0;i < lines.length; i += 1) {
|
|
14163
|
-
const match = BACKLOG_LINE.exec(lines[i] ?? "");
|
|
14164
|
-
if (match === null) {
|
|
14165
|
-
continue;
|
|
14166
|
-
}
|
|
14167
|
-
const checked = (match[2] ?? " ").toLowerCase() === "x";
|
|
14168
|
-
const rest = match[3] ?? "";
|
|
14169
|
-
const cast = BACKLOG_CAST.exec(rest);
|
|
14170
|
-
const item = cast ? { line: i, text: (cast[2] ?? "").trim(), checked, cast: cast[1] ?? "" } : { line: i, text: rest.trim(), checked };
|
|
14171
|
-
items.push(item);
|
|
14172
|
-
}
|
|
14173
|
-
return items;
|
|
14174
|
-
}
|
|
14175
|
-
function uncheckedBacklogItems(items) {
|
|
14176
|
-
return items.filter((item) => !item.checked);
|
|
14177
|
-
}
|
|
14178
|
-
function markBacklogLineDone(text2, line) {
|
|
14179
|
-
const lines = text2.split(`
|
|
14180
|
-
`);
|
|
14181
|
-
const target = lines[line];
|
|
14182
|
-
if (target === undefined) {
|
|
14183
|
-
return text2;
|
|
14184
|
-
}
|
|
14185
|
-
lines[line] = target.replace(/- \[ \]/, "- [x]");
|
|
14186
|
-
return lines.join(`
|
|
14187
|
-
`);
|
|
14188
|
-
}
|
|
14189
|
-
function evaluateLoopStop(state) {
|
|
14190
|
-
if (state.uncheckedCount <= 0) {
|
|
14191
|
-
return { stop: true, reason: "empty-backlog" };
|
|
14192
|
-
}
|
|
14193
|
-
if (state.iteration >= state.maxIterations) {
|
|
14194
|
-
return { stop: true, reason: "max-iterations" };
|
|
14195
|
-
}
|
|
14196
|
-
if (state.budgetUsd !== undefined && state.spentUsd >= state.budgetUsd) {
|
|
14197
|
-
return { stop: true, reason: "budget-exhausted" };
|
|
14198
|
-
}
|
|
14199
|
-
if (state.stopOnRedTests && state.testsRed) {
|
|
14200
|
-
return { stop: true, reason: "red-tests" };
|
|
14201
|
-
}
|
|
14202
|
-
if (state.noProgressCount >= state.noProgressLimit) {
|
|
14203
|
-
return { stop: true, reason: "no-progress" };
|
|
14204
|
-
}
|
|
14205
|
-
return { stop: false };
|
|
14206
|
-
}
|
|
14207
|
-
function parsePositiveNumber(raw, flag) {
|
|
14208
|
-
if (raw === undefined) {
|
|
14209
|
-
return { error: `Missing value for ${flag}` };
|
|
14210
|
-
}
|
|
14211
|
-
const value = Number(raw);
|
|
14212
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
14213
|
-
return { error: `${flag} expects a positive number, got "${raw}"` };
|
|
14214
|
-
}
|
|
14215
|
-
return { value };
|
|
14216
|
-
}
|
|
14217
|
-
function parseLoopFlags(argv) {
|
|
14218
|
-
const flags = {
|
|
14219
|
-
maxIterations: DEFAULT_LOOP_MAX_ITERATIONS,
|
|
14220
|
-
sleepSeconds: DEFAULT_LOOP_SLEEP_SECONDS,
|
|
14221
|
-
noProgressLimit: DEFAULT_LOOP_NO_PROGRESS_LIMIT,
|
|
14222
|
-
freshContext: true,
|
|
14223
|
-
stopOnRedTests: false,
|
|
14224
|
-
dryRun: false,
|
|
14225
|
-
yes: false,
|
|
14226
|
-
backlogPath: DEFAULT_LOOP_BACKLOG_PATH
|
|
14227
|
-
};
|
|
14228
|
-
for (let i = 0;i < argv.length; i += 1) {
|
|
14229
|
-
const arg = argv[i];
|
|
14230
|
-
if (arg === "--config" || arg === "--telemetry") {
|
|
14231
|
-
i += 1;
|
|
14232
|
-
continue;
|
|
14233
|
-
}
|
|
14234
|
-
if (arg === "--dry-run") {
|
|
14235
|
-
flags.dryRun = true;
|
|
14236
|
-
} else if (arg === "--yes" || arg === "-y") {
|
|
14237
|
-
flags.yes = true;
|
|
14238
|
-
} else if (arg === "--fresh-context") {
|
|
14239
|
-
flags.freshContext = true;
|
|
14240
|
-
} else if (arg === "--no-fresh-context") {
|
|
14241
|
-
flags.freshContext = false;
|
|
14242
|
-
} else if (arg === "--stop-on-red-tests") {
|
|
14243
|
-
flags.stopOnRedTests = true;
|
|
14244
|
-
} else if (arg === "--max-iterations") {
|
|
14245
|
-
const parsed = parsePositiveNumber(argv[i + 1], arg);
|
|
14246
|
-
if ("error" in parsed)
|
|
14247
|
-
return { ok: false, error: parsed.error };
|
|
14248
|
-
flags.maxIterations = Math.floor(parsed.value);
|
|
14249
|
-
i += 1;
|
|
14250
|
-
} else if (arg === "--budget-usd") {
|
|
14251
|
-
const parsed = parsePositiveNumber(argv[i + 1], arg);
|
|
14252
|
-
if ("error" in parsed)
|
|
14253
|
-
return { ok: false, error: parsed.error };
|
|
14254
|
-
flags.budgetUsd = parsed.value;
|
|
14255
|
-
i += 1;
|
|
14256
|
-
} else if (arg === "--sleep") {
|
|
14257
|
-
const parsed = parsePositiveNumber(argv[i + 1], arg);
|
|
14258
|
-
if ("error" in parsed)
|
|
14259
|
-
return { ok: false, error: parsed.error };
|
|
14260
|
-
flags.sleepSeconds = parsed.value;
|
|
14261
|
-
i += 1;
|
|
14262
|
-
} else if (arg === "--no-progress-limit") {
|
|
14263
|
-
const parsed = parsePositiveNumber(argv[i + 1], arg);
|
|
14264
|
-
if ("error" in parsed)
|
|
14265
|
-
return { ok: false, error: parsed.error };
|
|
14266
|
-
flags.noProgressLimit = Math.floor(parsed.value);
|
|
14267
|
-
i += 1;
|
|
14268
|
-
} else if (arg === "--backlog") {
|
|
14269
|
-
const value = argv[i + 1];
|
|
14270
|
-
if (value === undefined) {
|
|
14271
|
-
return { ok: false, error: "Missing value for --backlog" };
|
|
14272
|
-
}
|
|
14273
|
-
flags.backlogPath = value;
|
|
14274
|
-
i += 1;
|
|
14275
|
-
}
|
|
14276
|
-
}
|
|
14277
|
-
return { ok: true, flags };
|
|
14278
|
-
}
|
|
14279
|
-
var LOOP_GUARANTEED_ROLE_IDS = [RALPH_ROLE_ID, "guardian", "scribe"];
|
|
14280
|
-
function guaranteedAgentName(roleID) {
|
|
14281
|
-
return TEAM_ROLES[roleID]?.opencodeAgent ?? roleID;
|
|
14282
|
-
}
|
|
14283
|
-
async function guaranteedAgentStatuses(runtime) {
|
|
14284
|
-
const statuses = [];
|
|
14285
|
-
for (const roleID of LOOP_GUARANTEED_ROLE_IDS) {
|
|
14286
|
-
const agentName = guaranteedAgentName(roleID);
|
|
14287
|
-
statuses.push({
|
|
14288
|
-
roleID,
|
|
14289
|
-
agentName,
|
|
14290
|
-
exists: await runtime.agentFileExists(agentName)
|
|
14291
|
-
});
|
|
14292
|
-
}
|
|
14293
|
-
return statuses;
|
|
14294
|
-
}
|
|
14295
|
-
function rosterPlanSummary(rosterFile) {
|
|
14296
|
-
switch (rosterFile.status) {
|
|
14297
|
-
case "loaded":
|
|
14298
|
-
return `${rosterFile.roster.universe} (${rosterFile.roster.entries.length} roles)`;
|
|
14299
|
-
case "absent":
|
|
14300
|
-
return "none yet — one would be generated on the first real run";
|
|
14301
|
-
case "invalid":
|
|
14302
|
-
return `${rosterFile.path} is present but unparseable — run 'openteam doctor'`;
|
|
14303
|
-
}
|
|
14304
|
-
}
|
|
14305
|
-
function renderLoopPlan(plan2) {
|
|
14306
|
-
const { flags } = plan2;
|
|
14307
|
-
const budget = flags.budgetUsd !== undefined ? `$${flags.budgetUsd}` : "unbounded";
|
|
14308
|
-
const freshContext = flags.freshContext ? "on (pure Ralph pattern)" : "off";
|
|
14309
|
-
const budgetStop = flags.budgetUsd !== undefined ? ", budget" : "";
|
|
14310
|
-
const redTestsStop = flags.stopOnRedTests ? ", red tests" : "";
|
|
14311
|
-
const rosterSummary = rosterPlanSummary(plan2.rosterFile);
|
|
14312
|
-
const lines = [
|
|
14313
|
-
"openteam loop — plan (dry run, nothing dispatched):",
|
|
14314
|
-
` backlog: ${flags.backlogPath} (${plan2.uncheckedCount} unchecked item(s))`,
|
|
14315
|
-
` iterations: up to ${plan2.plannedIterations} (max-iterations ${flags.maxIterations})`,
|
|
14316
|
-
` budget: ${budget}`,
|
|
14317
|
-
` sleep: ${flags.sleepSeconds}s between iterations`,
|
|
14318
|
-
` fresh context: ${freshContext}`,
|
|
14319
|
-
` stop before spending on: empty backlog, max-iterations${budgetStop}${redTestsStop}, no-progress (${flags.noProgressLimit})`,
|
|
14320
|
-
` roster: ${rosterSummary}`
|
|
14321
|
-
];
|
|
14322
|
-
for (const role of plan2.guaranteed) {
|
|
14323
|
-
const mark = role.exists ? "✓" : "✗ missing — run 'openteam setup'";
|
|
14324
|
-
lines.push(` role ${role.roleID} → agent ${role.agentName}: ${mark}`);
|
|
14325
|
-
}
|
|
14326
|
-
return lines.join(`
|
|
14327
|
-
`);
|
|
14328
|
-
}
|
|
14329
|
-
function backlogPrompt(freshContext, items) {
|
|
14330
|
-
const next = uncheckedBacklogItems(items)[0];
|
|
14331
|
-
const header = freshContext ? "You are ralph, the loop driver. Fresh context each iteration." : "You are ralph, the loop driver.";
|
|
14332
|
-
const focus = next !== undefined ? `Work the next backlog item to completion: ${next.text}` : "The backlog is empty.";
|
|
14333
|
-
return `${header}
|
|
14334
|
-
${focus}`;
|
|
14335
|
-
}
|
|
14336
|
-
async function runLoop(argv, deps, configPath, configResolution) {
|
|
14337
|
-
const parsed = parseLoopFlags(argv);
|
|
14338
|
-
if (!parsed.ok) {
|
|
14339
|
-
return { exitCode: 1, stdout: `${parsed.error}
|
|
14340
|
-
|
|
14341
|
-
${LOOP_HELP}` };
|
|
14342
|
-
}
|
|
14343
|
-
const { flags } = parsed;
|
|
14344
|
-
if (deps.loopRuntime === undefined) {
|
|
12727
|
+
if (plan2.kind === "none") {
|
|
14345
12728
|
return {
|
|
14346
|
-
exitCode:
|
|
14347
|
-
stdout:
|
|
12729
|
+
exitCode: 0,
|
|
12730
|
+
stdout: `openteam graph recover: ${plan2.reason}`
|
|
14348
12731
|
};
|
|
14349
12732
|
}
|
|
14350
|
-
|
|
14351
|
-
const config = await deps.loadConfig(configPath, configResolution);
|
|
14352
|
-
const backlogText = await runtime.readBacklog(flags.backlogPath) ?? "";
|
|
14353
|
-
const items = parseBacklog2(backlogText);
|
|
14354
|
-
const unchecked = uncheckedBacklogItems(items);
|
|
14355
|
-
const rosterFile = await runtime.loadRosterFile();
|
|
14356
|
-
const guaranteed = await guaranteedAgentStatuses(runtime);
|
|
14357
|
-
const plannedIterations = Math.max(0, Math.min(flags.maxIterations, unchecked.length));
|
|
14358
|
-
const plan2 = {
|
|
14359
|
-
flags,
|
|
14360
|
-
uncheckedCount: unchecked.length,
|
|
14361
|
-
plannedIterations,
|
|
14362
|
-
rosterFile,
|
|
14363
|
-
guaranteed
|
|
14364
|
-
};
|
|
14365
|
-
if (flags.dryRun) {
|
|
14366
|
-
return { exitCode: 0, stdout: renderLoopPlan(plan2) };
|
|
14367
|
-
}
|
|
14368
|
-
if (!flags.yes) {
|
|
12733
|
+
if (plan2.kind === "blocked") {
|
|
14369
12734
|
return {
|
|
14370
12735
|
exitCode: 1,
|
|
14371
|
-
stdout:
|
|
12736
|
+
stdout: `openteam graph recover: ${plan2.reason}`
|
|
14372
12737
|
};
|
|
14373
12738
|
}
|
|
14374
|
-
const
|
|
14375
|
-
|
|
14376
|
-
|
|
14377
|
-
|
|
14378
|
-
|
|
14379
|
-
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
}
|
|
14385
|
-
|
|
12739
|
+
const config = await deps.loadConfig(configPath, configResolution);
|
|
12740
|
+
const policy = await resolveOperatorGraphPolicy(config, operator);
|
|
12741
|
+
const blocked = checkOperatorGate(policy);
|
|
12742
|
+
if (blocked !== undefined) {
|
|
12743
|
+
return blocked;
|
|
12744
|
+
}
|
|
12745
|
+
const recovered = await operator.recovery.recover(config);
|
|
12746
|
+
return {
|
|
12747
|
+
exitCode: 0,
|
|
12748
|
+
stdout: `openteam graph recover: ${recovered.reason} (${recovered.tail})`
|
|
12749
|
+
};
|
|
12750
|
+
}
|
|
12751
|
+
async function runGraphWorktrees(deps, configPath, configResolution) {
|
|
12752
|
+
const operator = requireGraphOperator(deps);
|
|
12753
|
+
const config = await deps.loadConfig(configPath, configResolution);
|
|
12754
|
+
const enabled = config.graph?.worktrees?.enabled ?? false;
|
|
12755
|
+
if (!enabled) {
|
|
14386
12756
|
return {
|
|
14387
12757
|
exitCode: 0,
|
|
14388
|
-
stdout:
|
|
12758
|
+
stdout: renderWorktreeReconciliation(planWorktreeReconciliation({ enabled: false, worktrees: [] }))
|
|
14389
12759
|
};
|
|
14390
12760
|
}
|
|
14391
|
-
const
|
|
14392
|
-
|
|
14393
|
-
|
|
12761
|
+
const policy = await resolveOperatorGraphPolicy(config, operator);
|
|
12762
|
+
const blocked = checkOperatorGate(policy);
|
|
12763
|
+
if (blocked !== undefined) {
|
|
12764
|
+
return blocked;
|
|
14394
12765
|
}
|
|
14395
|
-
await
|
|
14396
|
-
const
|
|
14397
|
-
|
|
14398
|
-
|
|
14399
|
-
config,
|
|
14400
|
-
freshContext: flags.freshContext
|
|
12766
|
+
const observations = await operator.worktrees.listObservations(config);
|
|
12767
|
+
const report = planWorktreeReconciliation({
|
|
12768
|
+
enabled,
|
|
12769
|
+
worktrees: observations
|
|
14401
12770
|
});
|
|
14402
|
-
|
|
14403
|
-
|
|
14404
|
-
for (let n = 0;n < completed; n += 1) {
|
|
14405
|
-
const item = unchecked[n];
|
|
14406
|
-
if (item !== undefined) {
|
|
14407
|
-
updated = markBacklogLineDone(updated, item.line);
|
|
14408
|
-
}
|
|
12771
|
+
if (report.plans.length === 0) {
|
|
12772
|
+
return { exitCode: 0, stdout: renderWorktreeReconciliation(report) };
|
|
14409
12773
|
}
|
|
14410
|
-
|
|
14411
|
-
|
|
12774
|
+
for (const plan2 of report.plans) {
|
|
12775
|
+
if (!plan2.safe || plan2.action === "retain" || plan2.action === "block") {
|
|
12776
|
+
continue;
|
|
12777
|
+
}
|
|
12778
|
+
await operator.worktrees.apply(plan2.action, plan2.runID);
|
|
14412
12779
|
}
|
|
14413
12780
|
return {
|
|
14414
|
-
exitCode: 0,
|
|
14415
|
-
stdout:
|
|
12781
|
+
exitCode: report.blocked > 0 ? 1 : 0,
|
|
12782
|
+
stdout: renderWorktreeReconciliation(report)
|
|
14416
12783
|
};
|
|
14417
12784
|
}
|
|
14418
12785
|
async function runCli(argv, deps) {
|
|
@@ -14557,12 +12924,6 @@ async function runCli(argv, deps) {
|
|
|
14557
12924
|
if (command === "graph") {
|
|
14558
12925
|
return await runGraph(parsed.positionals, deps, configPath, configResolution);
|
|
14559
12926
|
}
|
|
14560
|
-
if (command === "loop") {
|
|
14561
|
-
if (parsed.positionals.includes("--help") || parsed.positionals.includes("-h")) {
|
|
14562
|
-
return { exitCode: 0, stdout: LOOP_HELP };
|
|
14563
|
-
}
|
|
14564
|
-
return await runLoop(argv, deps, configPath, configResolution);
|
|
14565
|
-
}
|
|
14566
12927
|
if (command === "console") {
|
|
14567
12928
|
const config = await deps.loadConfig(configPath, configResolution);
|
|
14568
12929
|
return { exitCode: 0, stdout: renderConsoleStatus(config.console) };
|
|
@@ -14640,6 +13001,167 @@ ${HELP}`
|
|
|
14640
13001
|
}
|
|
14641
13002
|
}
|
|
14642
13003
|
|
|
13004
|
+
// src/orchestrator/roster.ts
|
|
13005
|
+
import { z as z19 } from "zod";
|
|
13006
|
+
var RosterEntrySchema = z19.object({
|
|
13007
|
+
roleID: z19.string().min(1),
|
|
13008
|
+
agentName: z19.string().min(1)
|
|
13009
|
+
}).strict();
|
|
13010
|
+
var RosterSchema = z19.object({
|
|
13011
|
+
universe: z19.string().min(1),
|
|
13012
|
+
entries: z19.array(RosterEntrySchema)
|
|
13013
|
+
}).strict();
|
|
13014
|
+
var ROSTER_JSON_FENCE = /```json\s*([\s\S]*?)```/;
|
|
13015
|
+
|
|
13016
|
+
class RosterParseError extends Error {
|
|
13017
|
+
code;
|
|
13018
|
+
constructor(code, message) {
|
|
13019
|
+
super(message);
|
|
13020
|
+
this.name = "RosterParseError";
|
|
13021
|
+
this.code = code;
|
|
13022
|
+
}
|
|
13023
|
+
}
|
|
13024
|
+
function parseRoster(text2) {
|
|
13025
|
+
const result = parseRosterResult(text2);
|
|
13026
|
+
if (!result.ok) {
|
|
13027
|
+
throw result.error;
|
|
13028
|
+
}
|
|
13029
|
+
return result.roster;
|
|
13030
|
+
}
|
|
13031
|
+
function parseRosterResult(text2) {
|
|
13032
|
+
const match = ROSTER_JSON_FENCE.exec(text2);
|
|
13033
|
+
if (match === null || match[1] === undefined) {
|
|
13034
|
+
return {
|
|
13035
|
+
ok: false,
|
|
13036
|
+
error: new RosterParseError("missing-json-block", "openteam roster: no JSON block found in roster file")
|
|
13037
|
+
};
|
|
13038
|
+
}
|
|
13039
|
+
let data;
|
|
13040
|
+
try {
|
|
13041
|
+
data = JSON.parse(match[1]);
|
|
13042
|
+
} catch (error) {
|
|
13043
|
+
return {
|
|
13044
|
+
ok: false,
|
|
13045
|
+
error: new RosterParseError("malformed-json", `openteam roster: malformed JSON in roster file: ${String(error)}`)
|
|
13046
|
+
};
|
|
13047
|
+
}
|
|
13048
|
+
const parsed = RosterSchema.safeParse(data);
|
|
13049
|
+
if (!parsed.success) {
|
|
13050
|
+
return {
|
|
13051
|
+
ok: false,
|
|
13052
|
+
error: new RosterParseError("invalid-roster-schema", `openteam roster: invalid roster schema: ${parsed.error.message}`)
|
|
13053
|
+
};
|
|
13054
|
+
}
|
|
13055
|
+
return { ok: true, roster: parsed.data };
|
|
13056
|
+
}
|
|
13057
|
+
var GUARANTEED_ROLE_IDS = [
|
|
13058
|
+
"orchestrator",
|
|
13059
|
+
"guardian",
|
|
13060
|
+
"scribe",
|
|
13061
|
+
"ralph"
|
|
13062
|
+
];
|
|
13063
|
+
function isGuaranteedRoleID(roleID) {
|
|
13064
|
+
return GUARANTEED_ROLE_IDS.includes(roleID);
|
|
13065
|
+
}
|
|
13066
|
+
function missingGuaranteedRoles(roster) {
|
|
13067
|
+
const present = new Set(roster.entries.map((entry) => entry.roleID));
|
|
13068
|
+
return GUARANTEED_ROLE_IDS.filter((roleID) => !present.has(roleID));
|
|
13069
|
+
}
|
|
13070
|
+
function isRoleProfileLike(value) {
|
|
13071
|
+
return typeof value === "object" && value !== null && "roleID" in value && "defaultTier" in value;
|
|
13072
|
+
}
|
|
13073
|
+
function configuredRolesFromAuditInput(input) {
|
|
13074
|
+
if ("configuredRoles" in input && !isRoleProfileLike(input.configuredRoles)) {
|
|
13075
|
+
return input.configuredRoles;
|
|
13076
|
+
}
|
|
13077
|
+
return input;
|
|
13078
|
+
}
|
|
13079
|
+
function auditRoster(rosterOrText, configuredRolesOrOptions = {}) {
|
|
13080
|
+
const configuredRoles = configuredRolesFromAuditInput(configuredRolesOrOptions);
|
|
13081
|
+
if (typeof rosterOrText === "string") {
|
|
13082
|
+
const parsed = parseRosterResult(rosterOrText);
|
|
13083
|
+
if (!parsed.ok) {
|
|
13084
|
+
return {
|
|
13085
|
+
findings: [
|
|
13086
|
+
{
|
|
13087
|
+
kind: "unparseable-roster",
|
|
13088
|
+
roleID: "roster",
|
|
13089
|
+
code: parsed.error.code,
|
|
13090
|
+
message: parsed.error.message
|
|
13091
|
+
}
|
|
13092
|
+
],
|
|
13093
|
+
hasAllGuaranteedRoles: false,
|
|
13094
|
+
missingGuaranteedRoleIDs: [],
|
|
13095
|
+
unprofiledRoleEntries: [],
|
|
13096
|
+
misassignedGuaranteedRoleEntries: []
|
|
13097
|
+
};
|
|
13098
|
+
}
|
|
13099
|
+
return auditParsedRoster(parsed.roster, configuredRoles);
|
|
13100
|
+
}
|
|
13101
|
+
return auditParsedRoster(rosterOrText, configuredRoles);
|
|
13102
|
+
}
|
|
13103
|
+
function auditParsedRoster(roster, configuredRoles) {
|
|
13104
|
+
const missingGuaranteedRoleIDs = missingGuaranteedRoles(roster);
|
|
13105
|
+
const missing = new Set(missingGuaranteedRoleIDs);
|
|
13106
|
+
const findings = missingGuaranteedRoleIDs.map((roleID) => ({
|
|
13107
|
+
kind: "missing-guaranteed-role",
|
|
13108
|
+
roleID
|
|
13109
|
+
}));
|
|
13110
|
+
const unprofiledRoleEntries = [];
|
|
13111
|
+
const misassignedGuaranteedRoleEntries = [];
|
|
13112
|
+
roster.entries.forEach((entry) => {
|
|
13113
|
+
const profile = getRoleProfile(entry.roleID, configuredRoles);
|
|
13114
|
+
if (profile === undefined && !isKnownNonWorkerRole(entry.roleID)) {
|
|
13115
|
+
unprofiledRoleEntries.push({
|
|
13116
|
+
roleID: entry.roleID,
|
|
13117
|
+
agentName: entry.agentName,
|
|
13118
|
+
severity: "info"
|
|
13119
|
+
});
|
|
13120
|
+
findings.push({
|
|
13121
|
+
kind: "unresolved-role-profile",
|
|
13122
|
+
roleID: entry.roleID,
|
|
13123
|
+
agentName: entry.agentName
|
|
13124
|
+
});
|
|
13125
|
+
}
|
|
13126
|
+
if (isGuaranteedRoleID(entry.agentName) && entry.roleID !== entry.agentName && missing.has(entry.agentName)) {
|
|
13127
|
+
misassignedGuaranteedRoleEntries.push({
|
|
13128
|
+
roleID: entry.roleID,
|
|
13129
|
+
agentName: entry.agentName,
|
|
13130
|
+
missingRoleID: entry.agentName,
|
|
13131
|
+
reason: "agentNameMatchesMissingGuaranteedRole"
|
|
13132
|
+
});
|
|
13133
|
+
findings.push({
|
|
13134
|
+
kind: "non-canonical-role-id",
|
|
13135
|
+
roleID: entry.roleID,
|
|
13136
|
+
agentName: entry.agentName,
|
|
13137
|
+
canonicalRoleID: entry.agentName
|
|
13138
|
+
});
|
|
13139
|
+
return;
|
|
13140
|
+
}
|
|
13141
|
+
if (profile !== undefined && isGuaranteedRoleID(profile.roleID) && profile.roleID !== entry.roleID && missing.has(profile.roleID)) {
|
|
13142
|
+
misassignedGuaranteedRoleEntries.push({
|
|
13143
|
+
roleID: entry.roleID,
|
|
13144
|
+
agentName: entry.agentName,
|
|
13145
|
+
missingRoleID: profile.roleID,
|
|
13146
|
+
reason: "roleIDResolvesToMissingGuaranteedRole"
|
|
13147
|
+
});
|
|
13148
|
+
findings.push({
|
|
13149
|
+
kind: "non-canonical-role-id",
|
|
13150
|
+
roleID: entry.roleID,
|
|
13151
|
+
agentName: entry.agentName,
|
|
13152
|
+
canonicalRoleID: profile.roleID
|
|
13153
|
+
});
|
|
13154
|
+
}
|
|
13155
|
+
});
|
|
13156
|
+
return {
|
|
13157
|
+
findings,
|
|
13158
|
+
hasAllGuaranteedRoles: missingGuaranteedRoleIDs.length === 0,
|
|
13159
|
+
missingGuaranteedRoleIDs,
|
|
13160
|
+
unprofiledRoleEntries,
|
|
13161
|
+
misassignedGuaranteedRoleEntries
|
|
13162
|
+
};
|
|
13163
|
+
}
|
|
13164
|
+
|
|
14643
13165
|
// src/commands/rolesInit.ts
|
|
14644
13166
|
var ROLES_INIT_LOCAL_ONLY = "local-only";
|
|
14645
13167
|
var ROLES_INIT_FRONTIER_OK = "frontier-ok";
|
|
@@ -14977,9 +13499,6 @@ function planStartupHygieneMigration(exists) {
|
|
|
14977
13499
|
// src/index.ts
|
|
14978
13500
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
14979
13501
|
import { basename as basename2, dirname as dirname5 } from "node:path";
|
|
14980
|
-
import {
|
|
14981
|
-
tool as tool2
|
|
14982
|
-
} from "@opencode-ai/plugin";
|
|
14983
13502
|
|
|
14984
13503
|
// src/graph/certificate.ts
|
|
14985
13504
|
import { z as z20 } from "zod";
|
|
@@ -15699,58 +14218,6 @@ var realCacheAdapter = {
|
|
|
15699
14218
|
isReparsePath
|
|
15700
14219
|
};
|
|
15701
14220
|
|
|
15702
|
-
// src/orchestrator/rosterPersistence.ts
|
|
15703
|
-
function rosterFence(roster) {
|
|
15704
|
-
const data = { universe: roster.universe, entries: roster.entries };
|
|
15705
|
-
return `\`\`\`json
|
|
15706
|
-
${JSON.stringify(data, null, 2)}
|
|
15707
|
-
\`\`\``;
|
|
15708
|
-
}
|
|
15709
|
-
function serializeRoster(roster) {
|
|
15710
|
-
return `# openteam roster
|
|
15711
|
-
|
|
15712
|
-
Universe: ${roster.universe}
|
|
15713
|
-
|
|
15714
|
-
${rosterFence(roster)}
|
|
15715
|
-
`;
|
|
15716
|
-
}
|
|
15717
|
-
async function persistRoster(storage, roster, path4 = OPENTEAM_ROSTER_PATH) {
|
|
15718
|
-
await storage.write(path4, serializeRoster(roster));
|
|
15719
|
-
return path4;
|
|
15720
|
-
}
|
|
15721
|
-
async function loadRosterFile(storage, path4 = OPENTEAM_ROSTER_PATH) {
|
|
15722
|
-
const content = await storage.read(path4);
|
|
15723
|
-
if (content === undefined) {
|
|
15724
|
-
return { status: "absent", path: path4 };
|
|
15725
|
-
}
|
|
15726
|
-
const parsed = parseRosterResult(content);
|
|
15727
|
-
if (!parsed.ok) {
|
|
15728
|
-
return { status: "invalid", path: path4, error: parsed.error };
|
|
15729
|
-
}
|
|
15730
|
-
return {
|
|
15731
|
-
status: "loaded",
|
|
15732
|
-
path: path4,
|
|
15733
|
-
roster: enforceGuaranteedRoles(parsed.roster)
|
|
15734
|
-
};
|
|
15735
|
-
}
|
|
15736
|
-
async function loadOrGenerateRoster(storage, generate, path4 = OPENTEAM_ROSTER_PATH) {
|
|
15737
|
-
const existing = await loadRosterFile(storage, path4);
|
|
15738
|
-
if (existing.status === "loaded") {
|
|
15739
|
-
return { roster: existing.roster, regenerated: false };
|
|
15740
|
-
}
|
|
15741
|
-
const roster = enforceGuaranteedRoles(await generate());
|
|
15742
|
-
if (existing.status === "absent") {
|
|
15743
|
-
await persistRoster(storage, roster, path4);
|
|
15744
|
-
return { roster, regenerated: true };
|
|
15745
|
-
}
|
|
15746
|
-
return {
|
|
15747
|
-
roster,
|
|
15748
|
-
regenerated: true,
|
|
15749
|
-
recoveredFromInvalidPersisted: true,
|
|
15750
|
-
invalidPersistedRosterError: existing.error.message
|
|
15751
|
-
};
|
|
15752
|
-
}
|
|
15753
|
-
|
|
15754
14221
|
// src/memory/types.ts
|
|
15755
14222
|
import { z as z21 } from "zod";
|
|
15756
14223
|
var SHARED_OWNER_KEY = "*";
|
|
@@ -16362,51 +14829,6 @@ async function recallMemory(deps, queryText, query) {
|
|
|
16362
14829
|
return recall(recordRead.records, queryEmbedding, query, recallDeps);
|
|
16363
14830
|
}
|
|
16364
14831
|
|
|
16365
|
-
// src/plugin/orchestrateTool.ts
|
|
16366
|
-
import { tool } from "@opencode-ai/plugin";
|
|
16367
|
-
import { z as z22 } from "zod";
|
|
16368
|
-
var _pluginWarnedRoles = new Set;
|
|
16369
|
-
var EMPTY_ROLE_ID_SET = new Set;
|
|
16370
|
-
var RoleAssignmentSchema = z22.object({
|
|
16371
|
-
roleID: z22.string().min(1).describe("Role identifier from the roster"),
|
|
16372
|
-
prompt: z22.string().min(1).describe("Work prompt for this role"),
|
|
16373
|
-
title: z22.string().min(1).optional().describe("Human-readable title"),
|
|
16374
|
-
dependsOn: z22.array(z22.string().min(1)).optional().describe("Role IDs this assignment depends on (DAG edges)")
|
|
16375
|
-
}).strict();
|
|
16376
|
-
var OrchestratePayloadSchema = z22.object({
|
|
16377
|
-
assignments: z22.array(RoleAssignmentSchema).min(1).describe("Role assignments to distribute"),
|
|
16378
|
-
parentSessionID: z22.string().min(1).optional().describe("Parent session for subsessions"),
|
|
16379
|
-
directory: z22.string().min(1).optional().describe("Working directory override")
|
|
16380
|
-
}).strict();
|
|
16381
|
-
function adaptSdkClient(sdk) {
|
|
16382
|
-
return {
|
|
16383
|
-
session: {
|
|
16384
|
-
create: async (args) => {
|
|
16385
|
-
const result = await sdk.session.create(args);
|
|
16386
|
-
const out = {};
|
|
16387
|
-
if (result.data !== undefined) {
|
|
16388
|
-
out.data = result.data;
|
|
16389
|
-
}
|
|
16390
|
-
if (result.error !== undefined) {
|
|
16391
|
-
out.error = result.error;
|
|
16392
|
-
}
|
|
16393
|
-
return out;
|
|
16394
|
-
},
|
|
16395
|
-
prompt: async (args) => {
|
|
16396
|
-
const result = await sdk.session.prompt(args);
|
|
16397
|
-
const out = {};
|
|
16398
|
-
if (result.data !== undefined) {
|
|
16399
|
-
out.data = result.data;
|
|
16400
|
-
}
|
|
16401
|
-
if (result.error !== undefined) {
|
|
16402
|
-
out.error = result.error;
|
|
16403
|
-
}
|
|
16404
|
-
return out;
|
|
16405
|
-
}
|
|
16406
|
-
}
|
|
16407
|
-
};
|
|
16408
|
-
}
|
|
16409
|
-
|
|
16410
14832
|
// src/local/embeddings.ts
|
|
16411
14833
|
function parseEmbeddingsResponse(payload) {
|
|
16412
14834
|
if (typeof payload !== "object" || payload === null || !Array.isArray(payload.data)) {
|
|
@@ -16570,7 +14992,7 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
|
|
|
16570
14992
|
// package.json
|
|
16571
14993
|
var package_default = {
|
|
16572
14994
|
name: "@jmanuelcorral/openteam",
|
|
16573
|
-
version: "0.
|
|
14995
|
+
version: "0.18.1",
|
|
16574
14996
|
packageManager: "bun@1.3.14",
|
|
16575
14997
|
description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
|
|
16576
14998
|
license: "MIT",
|
|
@@ -16918,11 +15340,11 @@ function applyLimit(list, limit) {
|
|
|
16918
15340
|
return list.slice(0, limit);
|
|
16919
15341
|
}
|
|
16920
15342
|
function queryDecisions(memory, query = {}) {
|
|
16921
|
-
const filtered = memory.decisions.filter((
|
|
16922
|
-
if (query.agent !== undefined &&
|
|
15343
|
+
const filtered = memory.decisions.filter((decision) => {
|
|
15344
|
+
if (query.agent !== undefined && decision.agent !== query.agent) {
|
|
16923
15345
|
return false;
|
|
16924
15346
|
}
|
|
16925
|
-
if (query.tag !== undefined && !(
|
|
15347
|
+
if (query.tag !== undefined && !(decision.tags ?? []).includes(query.tag)) {
|
|
16926
15348
|
return false;
|
|
16927
15349
|
}
|
|
16928
15350
|
return true;
|
|
@@ -17588,7 +16010,6 @@ var deps = {
|
|
|
17588
16010
|
agentDir: AGENT_DIR2,
|
|
17589
16011
|
cachePort: realCacheAdapter,
|
|
17590
16012
|
purge: createCliPurgeRuntime(),
|
|
17591
|
-
loopRuntime: createCliLoopRuntime(),
|
|
17592
16013
|
loadRosterForDoctor: readStoredRosterForDoctor,
|
|
17593
16014
|
auditRoster: auditStoredRosterForDoctor,
|
|
17594
16015
|
readGitignore: () => storage.read(OPENCODE_GITIGNORE_PATH),
|
|
@@ -17606,71 +16027,6 @@ function createCliPurgeRuntime() {
|
|
|
17606
16027
|
sessionsDir: DEFAULT_SESSIONS_DIR
|
|
17607
16028
|
};
|
|
17608
16029
|
}
|
|
17609
|
-
var fallbackRosterSource = () => ({
|
|
17610
|
-
universe: "openteam",
|
|
17611
|
-
entries: Object.entries(TEAM_ROLES).filter(([, profile]) => profile.opencodeAgent !== undefined).map(([roleID, profile]) => ({
|
|
17612
|
-
roleID,
|
|
17613
|
-
agentName: profile.opencodeAgent
|
|
17614
|
-
}))
|
|
17615
|
-
});
|
|
17616
|
-
function ralphResultIsDone(result) {
|
|
17617
|
-
const response2 = result.subsession.response;
|
|
17618
|
-
if (typeof response2 === "string") {
|
|
17619
|
-
return /\bDONE\b/i.test(response2);
|
|
17620
|
-
}
|
|
17621
|
-
if (response2 !== null && typeof response2 === "object") {
|
|
17622
|
-
const done = response2.done;
|
|
17623
|
-
if (typeof done === "boolean") {
|
|
17624
|
-
return done;
|
|
17625
|
-
}
|
|
17626
|
-
}
|
|
17627
|
-
return false;
|
|
17628
|
-
}
|
|
17629
|
-
async function runCliRalphLoop(input) {
|
|
17630
|
-
const client = adaptSdkClient(createOpencodeClient());
|
|
17631
|
-
const sink = createNullEventSink();
|
|
17632
|
-
const registry = new RuntimeRegistry({
|
|
17633
|
-
fetch: globalThis.fetch,
|
|
17634
|
-
clock: () => new Date().toISOString(),
|
|
17635
|
-
foundryDiscovery: foundryDiscoveryFromExec(nodeExec2)
|
|
17636
|
-
});
|
|
17637
|
-
const localRuntimeReachable = async () => {
|
|
17638
|
-
const snapshots = await registry.probe(input.config.local.runtimes.filter((runtime) => runtime.enabled));
|
|
17639
|
-
return anyRuntimeReachable(snapshots);
|
|
17640
|
-
};
|
|
17641
|
-
const task = {
|
|
17642
|
-
promptChars: input.prompt.length,
|
|
17643
|
-
hasCode: false
|
|
17644
|
-
};
|
|
17645
|
-
const result = await runRalphLoop({
|
|
17646
|
-
task,
|
|
17647
|
-
prompt: input.prompt,
|
|
17648
|
-
config: input.config,
|
|
17649
|
-
maxIterations: input.maxIterations
|
|
17650
|
-
}, {
|
|
17651
|
-
client,
|
|
17652
|
-
sink,
|
|
17653
|
-
now: () => Date.now(),
|
|
17654
|
-
newDecisionID: () => crypto.randomUUID(),
|
|
17655
|
-
localRuntimeReachable,
|
|
17656
|
-
isDone: ralphResultIsDone
|
|
17657
|
-
});
|
|
17658
|
-
return { iterations: result.iterations, termination: result.termination };
|
|
17659
|
-
}
|
|
17660
|
-
function createCliLoopRuntime() {
|
|
17661
|
-
const agentFileExists = (agentName) => storage.exists(`${AGENT_DIR2}/${agentName}.md`);
|
|
17662
|
-
const generate = () => generateRoster("openteam initial roster", fallbackRosterSource);
|
|
17663
|
-
return {
|
|
17664
|
-
loadRosterFile: () => loadRosterFile(storage),
|
|
17665
|
-
loadOrGenerateRoster: async () => (await loadOrGenerateRoster(storage, generate)).roster,
|
|
17666
|
-
agentFileExists,
|
|
17667
|
-
readBacklog: (path4) => storage.read(path4),
|
|
17668
|
-
writeBacklog: async (path4, contents) => {
|
|
17669
|
-
await storage.write(path4, contents);
|
|
17670
|
-
},
|
|
17671
|
-
run: (loopInput) => runCliRalphLoop(loopInput)
|
|
17672
|
-
};
|
|
17673
|
-
}
|
|
17674
16030
|
function createSetupDeps() {
|
|
17675
16031
|
return {
|
|
17676
16032
|
detect: createDetect(nodeExec2),
|