@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.
Files changed (36) hide show
  1. package/README.es.md +4 -4
  2. package/README.md +4 -4
  3. package/dist/certificates/graph-release-certificate.json +2 -2
  4. package/dist/certificates/graph-shadow-certificate.json +2 -2
  5. package/dist/cli.js +893 -2537
  6. package/dist/commands/dispatch.d.ts +2 -135
  7. package/dist/commands/dispatch.d.ts.map +1 -1
  8. package/dist/config/schema.d.ts +2 -0
  9. package/dist/config/schema.d.ts.map +1 -1
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +6151 -6398
  13. package/dist/opencodeArtifacts/orchestratorAgent.d.ts.map +1 -1
  14. package/dist/orchestrator/coordinator.d.ts +39 -1
  15. package/dist/orchestrator/coordinator.d.ts.map +1 -1
  16. package/dist/orchestrator/nodeIteration.d.ts +36 -0
  17. package/dist/orchestrator/nodeIteration.d.ts.map +1 -0
  18. package/dist/orchestrator/outputContract.d.ts +117 -0
  19. package/dist/orchestrator/outputContract.d.ts.map +1 -0
  20. package/dist/orchestrator/reportArtifactProbe.d.ts +40 -0
  21. package/dist/orchestrator/reportArtifactProbe.d.ts.map +1 -0
  22. package/dist/orchestrator/reportArtifactReader.d.ts +36 -0
  23. package/dist/orchestrator/reportArtifactReader.d.ts.map +1 -0
  24. package/dist/orchestrator/reportDurability.d.ts +70 -0
  25. package/dist/orchestrator/reportDurability.d.ts.map +1 -0
  26. package/dist/orchestrator/subsessions.d.ts +1 -1
  27. package/dist/orchestrator/subsessions.d.ts.map +1 -1
  28. package/dist/plugin/orchestrateTool.d.ts +59 -1
  29. package/dist/plugin/orchestrateTool.d.ts.map +1 -1
  30. package/dist/telemetry/diagnostics.d.ts +1 -1
  31. package/dist/telemetry/diagnostics.d.ts.map +1 -1
  32. package/dist/telemetry/events.d.ts +14 -0
  33. package/dist/telemetry/events.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/dist/orchestrator/ralphLoop.d.ts +0 -57
  36. 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/permissions.ts
1604
- var ROLE_PERMISSIONS = {
1605
- architect: [
1606
- "read",
1607
- "edit",
1608
- "multiFileEdit",
1609
- "destructive",
1610
- "network",
1611
- "shell"
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
- integration: ["read", "edit", "multiFileEdit", "network", "shell"],
1614
- "local-runtime": ["read", "edit", "network", "shell"],
1615
- "routing-cost": ["read", "edit"],
1616
- scribe: ["read", "edit"],
1617
- ralph: ["read", "edit", "shell"],
1618
- guardian: ["read"],
1619
- tester: ["read", "edit", "shell"],
1620
- reviewer: ["read"]
1621
- };
1622
- var elevatedTier = {
1623
- trivial: "simple",
1624
- simple: "moderate",
1625
- moderate: "hard",
1626
- hard: "hard"
1627
- };
1628
- function elevateTierForPermissions(baseTier, perms) {
1629
- const needsElevation = perms.includes("destructive") || perms.includes("multiFileEdit");
1630
- return needsElevation ? elevatedTier[baseTier] : baseTier;
1631
- }
1632
-
1633
- // src/orchestrator/runtimeSelection.ts
1634
- function configuredRuntime(runtimeId, config) {
1635
- return config.local.runtimes.find((runtime) => runtime.id === runtimeId);
1636
- }
1637
- function runtimeIdOfModel(model, config) {
1638
- return config.local.runtimes.find((runtime) => sameModel(runtime.defaultModel, model))?.id;
1639
- }
1640
- function roleLocalModel(role, runtimeId, fallback, runtime) {
1641
- const pinned = role.localModels?.[runtimeId];
1642
- if (pinned !== undefined) {
1643
- return { ...fallback, modelID: pinned };
1644
- }
1645
- const classModel = runtimeClassModel(role.localModelClass, runtime);
1646
- return classModel === undefined ? fallback : { ...fallback, modelID: classModel };
1647
- }
1648
- function runtimeClassModel(modelClass, runtime) {
1649
- if (modelClass === undefined || runtime === undefined) {
1650
- return;
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 modelClass === "thinking" ? runtime.thinkingModel : runtime.coderModel;
1197
+ return;
1653
1198
  }
1654
- function selectDispatchRuntime(role, decision2, reachable, config) {
1655
- if (decision2.routeKind !== "local") {
1656
- return { runtimeId: undefined, decision: decision2 };
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
- const listed = role.localRuntimes;
1659
- if (listed !== undefined && listed.length > 0 && reachable !== undefined) {
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
- const resolvedRuntimeId = runtimeIdOfModel(decision2.selected, config);
1676
- if (resolvedRuntimeId !== undefined) {
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
- return {
1686
- runtimeId: resolvedRuntimeId,
1687
- decision: decision2
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
- function safeToString(value) {
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 safeGet(value, key) {
1715
- try {
1716
- return Reflect.get(value, key);
1717
- } catch {
1718
- return;
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
- function stringProperty(value, key) {
1722
- const candidate = safeGet(value, key);
1723
- return typeof candidate === "string" ? candidate : undefined;
1229
+
1230
+ // src/console/protocol.ts
1231
+ function redactFrameText(text) {
1232
+ return `[redacted · ${text.length} chars · ${hashPrompt(text)}]`;
1724
1233
  }
1725
- function nestedMessage(value, key) {
1726
- const container = safeGet(value, key);
1727
- if (!isObject(container)) {
1728
- return;
1729
- }
1730
- const message = stringProperty(container, "message");
1731
- return message !== undefined && message.trim().length > 0 ? message : undefined;
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 functionName(value) {
1734
- return value.name.length > 0 ? `:${value.name}` : "";
1245
+ function str(value) {
1246
+ return typeof value === "string" && value.length > 0 ? value : undefined;
1735
1247
  }
1736
- function jsonCompatible(value, seen, depth) {
1737
- if (value === null) {
1738
- return null;
1248
+ function eventSessionID(event) {
1249
+ const props = asRecord(event.properties);
1250
+ if (props === undefined) {
1251
+ return;
1739
1252
  }
1740
- switch (typeof value) {
1741
- case "string":
1742
- case "number":
1743
- case "boolean":
1744
- return value;
1745
- case "bigint":
1746
- return `${value.toString()}n`;
1747
- case "symbol":
1748
- return safeToString(value);
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 (seen.has(value)) {
1757
- return "[Circular]";
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
- if (depth >= MAX_SERIALIZED_DEPTH) {
1760
- return "[MaxDepth]";
1268
+ return;
1269
+ }
1270
+ function messagesToFrames(raw, sessionID, options = {}) {
1271
+ if (!Array.isArray(raw)) {
1272
+ return [];
1761
1273
  }
1762
- seen.add(value);
1763
- try {
1764
- if (Array.isArray(value)) {
1765
- const items = value.slice(0, MAX_SERIALIZED_ARRAY_ITEMS).map((item) => jsonCompatible(item, seen, depth + 1));
1766
- if (value.length > MAX_SERIALIZED_ARRAY_ITEMS) {
1767
- items.push(`...${value.length - MAX_SERIALIZED_ARRAY_ITEMS} more items`);
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
- return items;
1770
- }
1771
- const descriptors = Object.getOwnPropertyDescriptors(value);
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 `${reason.slice(0, UNKNOWN_ERROR_REASON_MAX_CHARS - TRUNCATED_REASON_SUFFIX.length)}${TRUNCATED_REASON_SUFFIX}`;
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 errorReason(error) {
1805
- if (isError(error)) {
1806
- const message = stringProperty(error, "message");
1807
- if (message !== undefined) {
1808
- return message;
1809
- }
1302
+ function eventToSessionFrame(event, sessionID, options = {}) {
1303
+ if (eventSessionID(event) !== sessionID) {
1304
+ return;
1810
1305
  }
1811
- if (isObject(error)) {
1812
- const message = stringProperty(error, "message");
1813
- if (message !== undefined) {
1814
- return message;
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
- const dataMessage = nestedMessage(error, "data");
1817
- if (dataMessage !== undefined) {
1818
- return dataMessage;
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
- const nestedErrorMessage = nestedMessage(error, "error");
1821
- if (nestedErrorMessage !== undefined) {
1822
- return nestedErrorMessage;
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
- return objectReason(error);
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
- async function withTimeout(promise, timeoutMs) {
1859
- if (timeoutMs === undefined) {
1860
- return promise;
1861
- }
1862
- let timeoutID;
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
- async function runSubsession(client, req, deps = {}) {
1875
- let sessionID;
1876
- try {
1877
- const created = await client.session.create(createArgs(req));
1878
- if (created.error !== undefined) {
1879
- return {
1880
- success: false,
1881
- roleID: req.roleID,
1882
- model: req.model,
1883
- failureStage: "create",
1884
- failureReason: errorReason(created.error)
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
- sessionID = created.data?.id;
1888
- if (sessionID === undefined || sessionID.length === 0) {
1889
- return {
1890
- success: false,
1891
- roleID: req.roleID,
1892
- model: req.model,
1893
- failureStage: "create",
1894
- failureReason: "create response did not include a session id"
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
- } catch (error) {
1898
- return {
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
- return {
1919
- success: true,
1920
- sessionID,
1921
- roleID: req.roleID,
1922
- model: req.model,
1923
- response: prompted.data
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
- // src/orchestrator/coordinator.ts
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
- (warn ?? console.warn)(`[openteam] unknown roleID "${roleID}" — no matching profile in config orchestrator.roles or TEAM_ROLES. ` + `A neutral frontier-eligible profile will be used and the request will be ` + `dispatched to agent "${roleID}". If this is a custom agent in your workspace, ` + `this message is informational. To silence it, add "${roleID}" to orchestrator.roles in .opencode/openteam.json.`);
1947
- }
1948
- function roleFor(input) {
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
- function satisfiesRoleRequirement(requirement, profile) {
2017
- return profile.kind === "frontier" && profile.availability !== "unavailable" && profile.contextWindow >= requirement.minContextWindow && profile.reasoningTier >= requirement.minReasoningTier && profile.codeQualityTier >= requirement.minCodeQualityTier && (!requirement.needsTools || profile.supportsToolCalling) && (!requirement.needsVision || profile.supportsVision);
2018
- }
2019
- function roleCapableProfiles(profiles, requirement) {
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
- return profiles.filter((profile) => satisfiesRoleRequirement(requirement, profile));
2024
- }
2025
- function routingInput(input, role) {
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 recordSessionID(input, subsession) {
2052
- return subsession.sessionID ?? input.parentSessionID;
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(decision2) {
4326
- const who = decision2.agent === undefined ? "" : `<span class="who">${escapeHtml(decision2.agent)}</span> `;
4327
- const tags = decision2.tags === undefined || decision2.tags.length === 0 ? "" : ` ${decision2.tags.map((tag) => `<span class="tag">#${escapeHtml(tag)}</span>`).join(" ")}`;
4328
- return `<li class="decision"><span class="kind">decision</span> ${who}${escapeHtml(decision2.summary)}${tags}</li>`;
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(decision2) {
4065
+ function decisionToActivity(decision) {
5202
4066
  const entry = {
5203
- ts: decision2.ts,
4067
+ ts: decision.ts,
5204
4068
  kind: "decision",
5205
- summary: decision2.summary
4069
+ summary: decision.summary
5206
4070
  };
5207
- if (decision2.agent !== undefined) {
5208
- entry.agent = decision2.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((decision2) => decision2.sessionID === id).slice(0, Math.max(0, limits.decisionsLimit));
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((decision2) => decision2.sessionID === id), aggregate.meetings.filter((meeting) => meeting.sessionID === id), limits.activityLimit);
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 decision2 = decideIngress(policy, taskID);
8303
- return decision2.route === "legacy" ? {
7166
+ const decision = decideIngress(policy, taskID);
7167
+ return decision.route === "legacy" ? {
8304
7168
  kind: "legacy",
8305
- reason: decision2.reason,
8306
- ...decision2.gateViolations !== undefined ? { gateViolations: decision2.gateViolations } : {}
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, and created session. If one role fails, the rest keep running.",
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 (!isObject2(payload) || !Array.isArray(payload.data)) {
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 (!isObject2(value) || typeof value.id !== "string" || value.id.length === 0) {
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 (!isObject2(value)) {
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 (isObject2(model.limit)) {
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 isObject2(value) {
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 (!isObject3(payload) || !Array.isArray(payload.data)) {
9033
+ if (!isObject2(payload) || !Array.isArray(payload.data)) {
10143
9034
  return ids;
10144
9035
  }
10145
9036
  for (const entry of payload.data) {
10146
- if (!isObject3(entry) || typeof entry.id !== "string") {
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 (!isObject3(payload) || !Array.isArray(payload.all_models_loaded)) {
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 (!isObject3(entry) || typeof entry.model_name !== "string") {
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 (!isObject3(options) || typeof options.llamacpp_args !== "string") {
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 isObject3(value) {
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 (snapshot?.models.some((model) => model.loaded !== undefined) === true) {
10510
- detected.modelInfo = snapshot.models;
9229
+ if (runtime.baseURL !== undefined) {
9230
+ options.baseURL = runtime.baseURL;
10511
9231
  }
10512
- if (snapshot?.error !== undefined) {
10513
- detected.error = snapshot.error;
9232
+ if (localRuntimeKind(runtime) === "foundry-local" && runtime.discovery !== "manual" && this.foundryDiscovery !== undefined) {
9233
+ options.discovery = this.foundryDiscovery;
10514
9234
  }
10515
- return detected;
10516
- };
9235
+ return options;
9236
+ }
10517
9237
  }
10518
9238
 
10519
- // src/config/graphFeatureGate.ts
10520
- function checkCertificate(status, name) {
10521
- if (status.status === "absent") {
10522
- return {
10523
- code: `${name}-certificate-absent`,
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
- if (status.status === "invalid") {
10528
- return {
10529
- code: `${name}-certificate-invalid`,
10530
- detail: `${name} certificate rejected by recomputing parser: ${status.code} — ${status.detail}`
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 evaluateGraphGate(input) {
10536
- const violations = [];
10537
- const advisories = [];
10538
- const shadowV = checkCertificate(input.shadow, "shadow");
10539
- if (shadowV !== undefined)
10540
- violations.push(shadowV);
10541
- const releaseV = checkCertificate(input.release, "release");
10542
- if (releaseV !== undefined)
10543
- violations.push(releaseV);
10544
- const soakV = checkCertificate(input.soak, "soak");
10545
- if (soakV !== undefined)
10546
- advisories.push(soakV);
10547
- if (input.migration === "pending") {
10548
- violations.push({
10549
- code: "migration-pending",
10550
- detail: "legacy migration has pending work — run the migration tool to completion before enabling active mode"
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
- } else if (input.migration === "unknown") {
10553
- violations.push({
10554
- code: "migration-unknown",
10555
- detail: "could not determine migration status — check ledger readability and storage access"
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
- if (!input.operatorApproval) {
10559
- violations.push({
10560
- code: "operator-approval-missing",
10561
- 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"
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
- reparseSkipSuffix: " [SKIP — reparse point]",
10584
- deletedLabel: "deleted.",
10585
- lockedLabel: (message) => `[LOCKED] ${message}`,
10586
- pathOutsideWarning: (specDir, absolutePath) => ` ⚠ ${specDir}: path outside cacheRoot (${absolutePath}), skipped.`,
10587
- processed: (count) => `${count} entry(ies) processed.`,
10588
- found: (count) => `${count} entry(ies) found. Use --delete to remove them.`
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
- noRuntimes: "none",
10612
- modeFrontierOnly: "frontier-only (frontierOnly)",
10613
- modeLocalOnly: "local-only (localOnly)",
10614
- modeLocalFirst: "local-first",
10615
- summary: (params) => [
10616
- "openteam local:",
10617
- ` mode: ${params.mode}`,
10618
- ` frontierOnly: ${params.frontierOnly}`,
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
- // src/orchestrator/ralphLoop.ts
10692
- var RALPH_ROLE_ID = "ralph";
10693
- var DEFAULT_RALPH_MAX_ITERATIONS = 10;
10694
- function ralphTaskInput(input) {
10695
- const task = {
10696
- roleID: RALPH_ROLE_ID,
10697
- task: input.task,
10698
- prompt: input.prompt,
10699
- config: input.config
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
- if (input.parentSessionID !== undefined) {
10702
- task.parentSessionID = input.parentSessionID;
10703
- }
10704
- return task;
10705
- }
10706
- async function runRalphLoop(input, deps) {
10707
- const cap = input.maxIterations ?? DEFAULT_RALPH_MAX_ITERATIONS;
10708
- if (cap < 1) {
10709
- throw new Error(`openteam ralph loop: maxIterations must be >= 1, got ${cap}`);
10710
- }
10711
- const results = [];
10712
- const taskInput = ralphTaskInput(input);
10713
- while (results.length < cap) {
10714
- const result = await runRoleTask(taskInput, deps);
10715
- results.push(result);
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
- return {
10726
- iterations: results.length,
10727
- termination: "cap",
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/orchestrator/roster.ts
10734
- import { z as z17 } from "zod";
10735
- var RosterEntrySchema = z17.object({
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
- ok: false,
10774
- error: new RosterParseError("malformed-json", `openteam roster: malformed JSON in roster file: ${String(error)}`)
9411
+ code: `${name}-certificate-absent`,
9412
+ detail: `${name} certificate not found — run the ${name} certification suite to produce it`
10775
9413
  };
10776
9414
  }
10777
- const parsed = RosterSchema.safeParse(data);
10778
- if (!parsed.success) {
9415
+ if (status.status === "invalid") {
10779
9416
  return {
10780
- ok: false,
10781
- error: new RosterParseError("invalid-roster-schema", `openteam roster: invalid roster schema: ${parsed.error.message}`)
9417
+ code: `${name}-certificate-invalid`,
9418
+ detail: `${name} certificate rejected by recomputing parser: ${status.code} — ${status.detail}`
10782
9419
  };
10783
9420
  }
10784
- return { ok: true, roster: parsed.data };
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 auditRoster(rosterOrText, configuredRolesOrOptions = {}) {
10818
- const configuredRoles = configuredRolesFromAuditInput(configuredRolesOrOptions);
10819
- if (typeof rosterOrText === "string") {
10820
- const parsed = parseRosterResult(rosterOrText);
10821
- if (!parsed.ok) {
10822
- return {
10823
- findings: [
10824
- {
10825
- kind: "unparseable-roster",
10826
- roleID: "roster",
10827
- code: parsed.error.code,
10828
- message: parsed.error.message
10829
- }
10830
- ],
10831
- hasAllGuaranteedRoles: false,
10832
- missingGuaranteedRoleIDs: [],
10833
- unprofiledRoleEntries: [],
10834
- misassignedGuaranteedRoleEntries: []
10835
- };
10836
- }
10837
- return auditParsedRoster(parsed.roster, configuredRoles);
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
- findings,
10896
- hasAllGuaranteedRoles: missingGuaranteedRoleIDs.length === 0,
10897
- missingGuaranteedRoleIDs,
10898
- unprofiledRoleEntries,
10899
- misassignedGuaranteedRoleEntries
9453
+ allowed: violations.length === 0,
9454
+ violations,
9455
+ advisories
10900
9456
  };
10901
9457
  }
10902
- function enforceGuaranteedRoles(roster) {
10903
- const missing = missingGuaranteedRoles(roster);
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 z18 } from "zod";
10977
- var OtelBackendConfigSchema = z18.object({
10978
- backend: z18.literal("opentelemetry"),
10979
- connectionEnv: z18.string().min(1),
10980
- serviceName: z18.string().min(1).optional()
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 z19 } from "zod";
11925
- var Sha256Schema2 = z19.string().regex(/^[0-9a-f]{64}$/);
11926
- var SoakPlatformSchema = z19.enum(["linux", "win32", "darwin"]);
11927
- var PrivacyPairSchema = z19.object({
11928
- surfacesScanned: z19.number().int().nonnegative(),
11929
- rawFindings: z19.number().int().nonnegative()
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 = z19.object({
11932
- effectsExamined: z19.number().int().nonnegative(),
11933
- duplicatesFound: z19.number().int().nonnegative()
10588
+ var DuplicateEffectPairSchema = z18.object({
10589
+ effectsExamined: z18.number().int().nonnegative(),
10590
+ duplicatesFound: z18.number().int().nonnegative()
11934
10591
  }).strict();
11935
- var ModelVerificationPairSchema = z19.object({
11936
- nodesChecked: z19.number().int().nonnegative(),
11937
- unverified: z19.number().int().nonnegative(),
11938
- mismatched: z19.number().int().nonnegative()
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 = z19.object({
11941
- version: z19.literal(1),
11942
- seq: z19.number().int().nonnegative(),
11943
- timestamp: z19.string().datetime(),
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: z19.string().min(1),
11946
- provenance: z19.enum(["genuine-usage", "ci-synthetic"]),
10602
+ opencodeVersion: z18.string().min(1),
10603
+ provenance: z18.enum(["genuine-usage", "ci-synthetic"]),
11947
10604
  traceDigest: Sha256Schema2,
11948
- criticalDivergences: z19.number().int().nonnegative().nullable(),
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 localDefault2 = primaryLocal !== undefined ? {
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: localDefault2,
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 decision2 = decideIngress(policy, GRAPH_OPERATOR_TASK_ID);
14022
- if (decision2.route === "graph") {
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(decision2)
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: 1,
14347
- stdout: "The loop runtime is not configured in this environment."
12729
+ exitCode: 0,
12730
+ stdout: `openteam graph recover: ${plan2.reason}`
14348
12731
  };
14349
12732
  }
14350
- const runtime = deps.loopRuntime;
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: "Refusing to run unattended without confirmation. Re-run with --dry-run to preview the plan, or --yes to run."
12736
+ stdout: `openteam graph recover: ${plan2.reason}`
14372
12737
  };
14373
12738
  }
14374
- const preStop = evaluateLoopStop({
14375
- uncheckedCount: unchecked.length,
14376
- iteration: 0,
14377
- maxIterations: flags.maxIterations,
14378
- ...flags.budgetUsd !== undefined ? { budgetUsd: flags.budgetUsd } : {},
14379
- spentUsd: 0,
14380
- stopOnRedTests: flags.stopOnRedTests,
14381
- testsRed: false,
14382
- noProgressCount: 0,
14383
- noProgressLimit: flags.noProgressLimit
14384
- });
14385
- if (preStop.stop) {
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: `openteam loop: nothing to do (${preStop.reason}).`
12758
+ stdout: renderWorktreeReconciliation(planWorktreeReconciliation({ enabled: false, worktrees: [] }))
14389
12759
  };
14390
12760
  }
14391
- const ralphAgent = guaranteedAgentName(RALPH_ROLE_ID);
14392
- if (!await runtime.agentFileExists(ralphAgent)) {
14393
- return { exitCode: 1, stdout: missingAgentFileMessage(ralphAgent) };
12761
+ const policy = await resolveOperatorGraphPolicy(config, operator);
12762
+ const blocked = checkOperatorGate(policy);
12763
+ if (blocked !== undefined) {
12764
+ return blocked;
14394
12765
  }
14395
- await runtime.loadOrGenerateRoster();
14396
- const result = await runtime.run({
14397
- prompt: backlogPrompt(flags.freshContext, items),
14398
- maxIterations: Math.max(1, plannedIterations),
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
- let updated = backlogText;
14403
- const completed = Math.min(result.iterations, unchecked.length);
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
- if (updated !== backlogText) {
14411
- await runtime.writeBacklog(flags.backlogPath, updated);
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: `openteam loop: ${result.iterations} iteration(s), terminated on ${result.termination}; marked ${completed} backlog item(s) done.`
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.17.6",
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((decision2) => {
16922
- if (query.agent !== undefined && decision2.agent !== query.agent) {
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 && !(decision2.tags ?? []).includes(query.tag)) {
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),