@jmanuelcorral/openteam 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.es.md +1 -1
- package/README.md +1 -1
- package/dist/cli.js +522 -67
- package/dist/commands/dispatch.d.ts +23 -3
- package/dist/commands/dispatch.d.ts.map +1 -1
- package/dist/commands/doctor.d.ts +47 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/index.d.ts +11 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +665 -103
- package/dist/opencodeArtifacts/orchestratorAgent.d.ts +1 -1
- package/dist/opencodeArtifacts/orchestratorAgent.d.ts.map +1 -1
- package/dist/orchestrator/coordinator.d.ts +8 -5
- package/dist/orchestrator/coordinator.d.ts.map +1 -1
- package/dist/orchestrator/permissions.d.ts.map +1 -1
- package/dist/orchestrator/roles.d.ts +23 -4
- package/dist/orchestrator/roles.d.ts.map +1 -1
- package/dist/orchestrator/roster.d.ts +85 -2
- package/dist/orchestrator/roster.d.ts.map +1 -1
- package/dist/orchestrator/rosterPersistence.d.ts +32 -5
- package/dist/orchestrator/rosterPersistence.d.ts.map +1 -1
- package/dist/plugin/diagnostics.d.ts +30 -0
- package/dist/plugin/diagnostics.d.ts.map +1 -0
- package/dist/plugin/orchestrateTool.d.ts +3 -1
- package/dist/plugin/orchestrateTool.d.ts.map +1 -1
- package/dist/storage/index/memoryIndex.d.ts +2 -2
- package/dist/storage/index/memoryIndex.d.ts.map +1 -1
- package/dist/telemetry/diagnostics.d.ts +10 -0
- package/dist/telemetry/diagnostics.d.ts.map +1 -0
- package/dist/telemetry/eventLog.d.ts +3 -1
- package/dist/telemetry/eventLog.d.ts.map +1 -1
- package/dist/telemetry/events.d.ts +77 -0
- package/dist/telemetry/events.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -98,8 +98,8 @@ var AgentRoleProfileSchema = z.object({
|
|
|
98
98
|
localRuntimes: z.array(z.enum(["ollama", "lmstudio", "foundry-local", "lemonade"])).optional()
|
|
99
99
|
}).strict();
|
|
100
100
|
var TEAM_ROLES = {
|
|
101
|
-
|
|
102
|
-
roleID: "
|
|
101
|
+
architect: {
|
|
102
|
+
roleID: "architect",
|
|
103
103
|
opencodeAgent: "architect",
|
|
104
104
|
defaultTier: "hard",
|
|
105
105
|
minReasoningTier: 5,
|
|
@@ -108,8 +108,8 @@ var TEAM_ROLES = {
|
|
|
108
108
|
preferredFrontierModels: ["auto"],
|
|
109
109
|
localFirst: false
|
|
110
110
|
},
|
|
111
|
-
|
|
112
|
-
roleID: "
|
|
111
|
+
integration: {
|
|
112
|
+
roleID: "integration",
|
|
113
113
|
opencodeAgent: "integration",
|
|
114
114
|
defaultTier: "moderate",
|
|
115
115
|
minReasoningTier: 4,
|
|
@@ -118,8 +118,8 @@ var TEAM_ROLES = {
|
|
|
118
118
|
preferredFrontierModels: ["auto"],
|
|
119
119
|
localFirst: false
|
|
120
120
|
},
|
|
121
|
-
|
|
122
|
-
roleID: "
|
|
121
|
+
"local-runtime": {
|
|
122
|
+
roleID: "local-runtime",
|
|
123
123
|
opencodeAgent: "local-runtime",
|
|
124
124
|
defaultTier: "moderate",
|
|
125
125
|
minReasoningTier: 3,
|
|
@@ -128,8 +128,8 @@ var TEAM_ROLES = {
|
|
|
128
128
|
preferredFrontierModels: ["auto"],
|
|
129
129
|
localFirst: true
|
|
130
130
|
},
|
|
131
|
-
|
|
132
|
-
roleID: "
|
|
131
|
+
"routing-cost": {
|
|
132
|
+
roleID: "routing-cost",
|
|
133
133
|
opencodeAgent: "routing-cost",
|
|
134
134
|
defaultTier: "moderate",
|
|
135
135
|
minReasoningTier: 3,
|
|
@@ -171,8 +171,8 @@ var TEAM_ROLES = {
|
|
|
171
171
|
localFirst: true,
|
|
172
172
|
requiresLocalRuntime: true
|
|
173
173
|
},
|
|
174
|
-
|
|
175
|
-
roleID: "
|
|
174
|
+
tester: {
|
|
175
|
+
roleID: "tester",
|
|
176
176
|
opencodeAgent: "tester",
|
|
177
177
|
defaultTier: "simple",
|
|
178
178
|
minReasoningTier: 2,
|
|
@@ -192,8 +192,35 @@ var TEAM_ROLES = {
|
|
|
192
192
|
localFirst: true
|
|
193
193
|
}
|
|
194
194
|
};
|
|
195
|
+
var LEGACY_TEAM_ROLE_ALIASES = {
|
|
196
|
+
rusty: "architect",
|
|
197
|
+
livingston: "integration",
|
|
198
|
+
yen: "local-runtime",
|
|
199
|
+
basher: "routing-cost",
|
|
200
|
+
linus: "tester"
|
|
201
|
+
};
|
|
202
|
+
var KNOWN_NON_WORKER_ROLE_IDS = ["orchestrator", "openteam"];
|
|
203
|
+
function isKnownNonWorkerRole(roleID) {
|
|
204
|
+
return KNOWN_NON_WORKER_ROLE_IDS.includes(roleID);
|
|
205
|
+
}
|
|
206
|
+
function getConfiguredRoleProfileByAlias(roleID, roles) {
|
|
207
|
+
if (roles === undefined) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
for (const key of Object.keys(roles).sort()) {
|
|
211
|
+
const profile = roles[key];
|
|
212
|
+
if (profile?.opencodeAgent === roleID) {
|
|
213
|
+
return profile;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
function getLegacyTeamRoleProfile(roleID) {
|
|
219
|
+
const canonicalRoleID = LEGACY_TEAM_ROLE_ALIASES[roleID];
|
|
220
|
+
return canonicalRoleID === undefined ? undefined : TEAM_ROLES[canonicalRoleID];
|
|
221
|
+
}
|
|
195
222
|
function getRoleProfile(roleID, configuredRoles) {
|
|
196
|
-
return configuredRoles?.[roleID] ?? TEAM_ROLES[roleID];
|
|
223
|
+
return configuredRoles?.[roleID] ?? TEAM_ROLES[roleID] ?? getConfiguredRoleProfileByAlias(roleID, configuredRoles) ?? getLegacyTeamRoleProfile(roleID);
|
|
197
224
|
}
|
|
198
225
|
function synthesiseFallbackProfile(roleID) {
|
|
199
226
|
return {
|
|
@@ -2878,6 +2905,50 @@ var ModelCapabilityProfileSchema = z10.object({
|
|
|
2878
2905
|
availability: z10.enum(["available", "degraded", "unavailable"])
|
|
2879
2906
|
}).strict();
|
|
2880
2907
|
|
|
2908
|
+
// src/telemetry/diagnostics.ts
|
|
2909
|
+
var DIAGNOSTIC_CODES = [
|
|
2910
|
+
"availability-refresh-failed",
|
|
2911
|
+
"config-missing",
|
|
2912
|
+
"graph-gate-denied",
|
|
2913
|
+
"memory-extraction-warning",
|
|
2914
|
+
"memory-runtime-warning",
|
|
2915
|
+
"opencode-server-url",
|
|
2916
|
+
"opencode-version-read-failed",
|
|
2917
|
+
"privacy-inert",
|
|
2918
|
+
"soak-evidence-rejected",
|
|
2919
|
+
"telemetry-warning",
|
|
2920
|
+
"telemetry-write-failed",
|
|
2921
|
+
"unknown-role"
|
|
2922
|
+
];
|
|
2923
|
+
function diagnosticDescription(code) {
|
|
2924
|
+
switch (code) {
|
|
2925
|
+
case "availability-refresh-failed":
|
|
2926
|
+
return "Local runtime availability refresh failed; routing will continue with the previous or empty availability snapshot.";
|
|
2927
|
+
case "config-missing":
|
|
2928
|
+
return "openteam did not find its runtime config file; defaults or plugin options were used.";
|
|
2929
|
+
case "graph-gate-denied":
|
|
2930
|
+
return "The graph cutover gate denied active mode and openteam degraded graph mode to off.";
|
|
2931
|
+
case "memory-extraction-warning":
|
|
2932
|
+
return "Semantic memory extraction reported a non-fatal local-runtime warning; no raw conversation content was recorded.";
|
|
2933
|
+
case "memory-runtime-warning":
|
|
2934
|
+
return "Semantic memory runtime setup reported a non-fatal warning; memory recall may be incomplete.";
|
|
2935
|
+
case "opencode-server-url":
|
|
2936
|
+
return "openteam observed the opencode server URL during plugin initialization.";
|
|
2937
|
+
case "opencode-version-read-failed":
|
|
2938
|
+
return "openteam could not read the live opencode version; graph gates fail closed when this happens.";
|
|
2939
|
+
case "privacy-inert":
|
|
2940
|
+
return "forceLocalOnSensitive is configured but no local runtime is configured, so the setting cannot keep sensitive prompts local.";
|
|
2941
|
+
case "soak-evidence-rejected":
|
|
2942
|
+
return "Graph soak evidence contained rejected observation lines; cutover gate evidence may be incomplete.";
|
|
2943
|
+
case "telemetry-warning":
|
|
2944
|
+
return "An optional telemetry backend warning occurred; local JSONL telemetry remains the fallback when enabled.";
|
|
2945
|
+
case "telemetry-write-failed":
|
|
2946
|
+
return "Writing an openteam telemetry event failed; plugin execution continued.";
|
|
2947
|
+
case "unknown-role":
|
|
2948
|
+
return "An unknown role ID was encountered; openteam used a neutral frontier-eligible fallback. If intentional, define the role in orchestrator.roles.";
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2881
2952
|
// src/telemetry/events.ts
|
|
2882
2953
|
var EVENT_SCHEMA_VERSION = 1;
|
|
2883
2954
|
var EventBaseSchema = z11.object({
|
|
@@ -2971,6 +3042,13 @@ var ShadowDiagnosticEventSchema = EventBaseSchema.extend({
|
|
|
2971
3042
|
"unknown"
|
|
2972
3043
|
])
|
|
2973
3044
|
});
|
|
3045
|
+
var DiagnosticLevelSchema = z11.enum(["debug", "info", "warn", "error"]);
|
|
3046
|
+
var DiagnosticCodeSchema = z11.enum(DIAGNOSTIC_CODES);
|
|
3047
|
+
var DiagnosticEventSchema = EventBaseSchema.extend({
|
|
3048
|
+
type: z11.literal("diagnostic"),
|
|
3049
|
+
level: DiagnosticLevelSchema,
|
|
3050
|
+
code: DiagnosticCodeSchema
|
|
3051
|
+
});
|
|
2974
3052
|
var OpenTeamEventSchema = z11.discriminatedUnion("type", [
|
|
2975
3053
|
RouteEventSchema,
|
|
2976
3054
|
MessageEventSchema,
|
|
@@ -2979,7 +3057,8 @@ var OpenTeamEventSchema = z11.discriminatedUnion("type", [
|
|
|
2979
3057
|
DecisionEventSchema,
|
|
2980
3058
|
ActivityEventSchema,
|
|
2981
3059
|
SessionEndpointEventSchema,
|
|
2982
|
-
ShadowDiagnosticEventSchema
|
|
3060
|
+
ShadowDiagnosticEventSchema,
|
|
3061
|
+
DiagnosticEventSchema
|
|
2983
3062
|
]);
|
|
2984
3063
|
|
|
2985
3064
|
// src/telemetry/types.ts
|
|
@@ -3162,6 +3241,9 @@ async function readRouteCostRecords(dir, deps) {
|
|
|
3162
3241
|
}
|
|
3163
3242
|
return records;
|
|
3164
3243
|
}
|
|
3244
|
+
async function readDiagnosticEvents(dir, deps) {
|
|
3245
|
+
return (await readSessionEvents(dir, deps)).filter((event) => event.type === "diagnostic");
|
|
3246
|
+
}
|
|
3165
3247
|
async function readSessionEvents(dir, deps) {
|
|
3166
3248
|
const files = (await deps.storage.list(dir)).filter((file) => file.endsWith(".jsonl"));
|
|
3167
3249
|
const perFile = await Promise.all(files.map(async (file) => {
|
|
@@ -3579,22 +3661,70 @@ function buildOrchestratorAgent(frontier, options = {}) {
|
|
|
3579
3661
|
"1. When you create the team for the first time, **choose a thematic universe**",
|
|
3580
3662
|
" (e.g. a film, series, comic or mythology). If the user has a preference,",
|
|
3581
3663
|
" ask them for it; if not, propose one and move forward without blocking work.",
|
|
3582
|
-
"2. Give each subagent the **name of a character** from that universe;
|
|
3583
|
-
" file name (`.opencode/agent/<
|
|
3664
|
+
"2. Give each subagent an `agentName`: the **name of a character** from that universe;",
|
|
3665
|
+
" the file name (`.opencode/agent/<agentName>.md`) is its alias for",
|
|
3584
3666
|
" `@mention`, and its role is clear from the `description`.",
|
|
3585
|
-
|
|
3586
|
-
"
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
"
|
|
3667
|
+
"3. Give each entry a `roleID`: the stable functional routing key. `roleID` is",
|
|
3668
|
+
" **not** the themed name. Worked example: C-3PO as the scribe is",
|
|
3669
|
+
' `{ "roleID": "scribe", "agentName": "c3po" }`, never',
|
|
3670
|
+
' `{ "roleID": "c3po", "agentName": "c3po" }`.',
|
|
3671
|
+
"4. Prefer existing curated `roleID` keys when they fit:",
|
|
3672
|
+
" `architect`, `integration`, `tester`, `reviewer`, `local-runtime`,",
|
|
3673
|
+
" `routing-cost` (plus the guaranteed `orchestrator`, `guardian`,",
|
|
3674
|
+
" `scribe`, `ralph`). The roleID space is open: use free-form project",
|
|
3675
|
+
" roleIDs only when no curated role fits.",
|
|
3676
|
+
`5. **Register the cast** in \`${OPENTEAM_ROSTER_PATH}\` so the names persist`,
|
|
3677
|
+
" across sessions. This file goes **outside** `.opencode/agent/` (otherwise",
|
|
3678
|
+
" opencode would load it as a phantom agent). Always reuse the same cast;",
|
|
3679
|
+
" do not re-cast without reason.",
|
|
3680
|
+
"",
|
|
3681
|
+
"## Roster file format",
|
|
3682
|
+
"",
|
|
3683
|
+
"Write the roster as Markdown with exactly one fenced `json` block matching",
|
|
3684
|
+
"`{ universe, entries: [{ roleID, agentName }] }`. Do not write a table.",
|
|
3685
|
+
"Minimal parseable example:",
|
|
3686
|
+
"",
|
|
3687
|
+
"# openteam roster",
|
|
3688
|
+
"",
|
|
3689
|
+
"Universe: star-wars",
|
|
3690
|
+
"",
|
|
3691
|
+
"```json",
|
|
3692
|
+
"{",
|
|
3693
|
+
' "universe": "star-wars",',
|
|
3694
|
+
' "entries": [',
|
|
3695
|
+
" {",
|
|
3696
|
+
' "roleID": "orchestrator",',
|
|
3697
|
+
' "agentName": "openteam"',
|
|
3698
|
+
" },",
|
|
3699
|
+
" {",
|
|
3700
|
+
' "roleID": "guardian",',
|
|
3701
|
+
' "agentName": "leia"',
|
|
3702
|
+
" },",
|
|
3703
|
+
" {",
|
|
3704
|
+
' "roleID": "scribe",',
|
|
3705
|
+
' "agentName": "c3po"',
|
|
3706
|
+
" },",
|
|
3707
|
+
" {",
|
|
3708
|
+
' "roleID": "ralph",',
|
|
3709
|
+
' "agentName": "r2d2"',
|
|
3710
|
+
" },",
|
|
3711
|
+
" {",
|
|
3712
|
+
' "roleID": "tester",',
|
|
3713
|
+
' "agentName": "rex"',
|
|
3714
|
+
" }",
|
|
3715
|
+
" ]",
|
|
3716
|
+
"}",
|
|
3717
|
+
"```",
|
|
3590
3718
|
"",
|
|
3591
3719
|
"## How to create the team when it does not exist",
|
|
3592
3720
|
"",
|
|
3593
3721
|
"1. Analyze the goal and explore the repository (`read`/`glob`/`grep`).",
|
|
3594
3722
|
"2. Break the request into tasks and design the **minimal team** needed:",
|
|
3595
|
-
" define only the roles the work requires
|
|
3596
|
-
"
|
|
3597
|
-
"
|
|
3723
|
+
" define only the roles the work requires. Reuse curated roleIDs such as",
|
|
3724
|
+
" `architect`, `integration`, `reviewer`, `tester`, `local-runtime` and",
|
|
3725
|
+
" `routing-cost`; use custom roleIDs for uncovered project specialties.",
|
|
3726
|
+
" Do not invent roles you will not use.",
|
|
3727
|
+
"3. Create each subagent **on demand** by writing `.opencode/agent/<agentName>.md`:",
|
|
3598
3728
|
" - Frontmatter: `description` (required, include role and universe),",
|
|
3599
3729
|
" `mode: subagent`, `temperature`, `permission` with the minimum needed,",
|
|
3600
3730
|
" and `model` **optional**.",
|
|
@@ -3615,8 +3745,10 @@ function buildOrchestratorAgent(frontier, options = {}) {
|
|
|
3615
3745
|
"",
|
|
3616
3746
|
"## Standard team roles",
|
|
3617
3747
|
"",
|
|
3618
|
-
"
|
|
3619
|
-
"
|
|
3748
|
+
"Every roster MUST include these guaranteed canonical roleIDs exactly:",
|
|
3749
|
+
"`orchestrator`, `guardian`, `scribe`, and `ralph`. Map `orchestrator` to",
|
|
3750
|
+
"agentName `openteam` (the primary agent). Create the `guardian`, `scribe`,",
|
|
3751
|
+
"and `ralph` subagent files on demand with their themed `agentName` values:",
|
|
3620
3752
|
"",
|
|
3621
3753
|
"- **scribe** — the team's silent memory. Records decisions and",
|
|
3622
3754
|
" learnings in a shared log (`.opencode/openteam/decisions.md`) without",
|
|
@@ -5728,14 +5860,21 @@ function hashPrompt(prompt) {
|
|
|
5728
5860
|
|
|
5729
5861
|
// src/orchestrator/permissions.ts
|
|
5730
5862
|
var ROLE_PERMISSIONS = {
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5863
|
+
architect: [
|
|
5864
|
+
"read",
|
|
5865
|
+
"edit",
|
|
5866
|
+
"multiFileEdit",
|
|
5867
|
+
"destructive",
|
|
5868
|
+
"network",
|
|
5869
|
+
"shell"
|
|
5870
|
+
],
|
|
5871
|
+
integration: ["read", "edit", "multiFileEdit", "network", "shell"],
|
|
5872
|
+
"local-runtime": ["read", "edit", "network", "shell"],
|
|
5873
|
+
"routing-cost": ["read", "edit"],
|
|
5735
5874
|
scribe: ["read", "edit"],
|
|
5736
5875
|
ralph: ["read", "edit", "shell"],
|
|
5737
5876
|
guardian: ["read"],
|
|
5738
|
-
|
|
5877
|
+
tester: ["read", "edit", "shell"],
|
|
5739
5878
|
reviewer: ["read"]
|
|
5740
5879
|
};
|
|
5741
5880
|
var elevatedTier = {
|
|
@@ -6031,6 +6170,8 @@ async function runSubsession(client, req, deps = {}) {
|
|
|
6031
6170
|
|
|
6032
6171
|
// src/orchestrator/coordinator.ts
|
|
6033
6172
|
function warnUnknownRole(roleID, warn, warnedRoles) {
|
|
6173
|
+
if (isKnownNonWorkerRole(roleID))
|
|
6174
|
+
return;
|
|
6034
6175
|
if (warnedRoles !== undefined) {
|
|
6035
6176
|
if (warnedRoles.has(roleID))
|
|
6036
6177
|
return;
|
|
@@ -6109,7 +6250,7 @@ function routingInput(input, role) {
|
|
|
6109
6250
|
...input.task,
|
|
6110
6251
|
prompt: input.task.prompt ?? input.prompt
|
|
6111
6252
|
}).tier;
|
|
6112
|
-
const permissionTier = elevateTierForPermissions(classifiedTier, permissionsFor(
|
|
6253
|
+
const permissionTier = elevateTierForPermissions(classifiedTier, permissionsFor(role.roleID));
|
|
6113
6254
|
const tier = permissionTier;
|
|
6114
6255
|
const requirement = roleToRequirement(role, input.task, tier);
|
|
6115
6256
|
const task = roleAdjustedTask(role, input, requirement);
|
|
@@ -6517,12 +6658,67 @@ var RosterSchema = z18.object({
|
|
|
6517
6658
|
universe: z18.string().min(1),
|
|
6518
6659
|
entries: z18.array(RosterEntrySchema)
|
|
6519
6660
|
}).strict();
|
|
6661
|
+
var ROSTER_JSON_FENCE = /```json\s*([\s\S]*?)```/;
|
|
6662
|
+
|
|
6663
|
+
class RosterParseError extends Error {
|
|
6664
|
+
code;
|
|
6665
|
+
constructor(code, message) {
|
|
6666
|
+
super(message);
|
|
6667
|
+
this.name = "RosterParseError";
|
|
6668
|
+
this.code = code;
|
|
6669
|
+
}
|
|
6670
|
+
}
|
|
6671
|
+
function parseRoster(text) {
|
|
6672
|
+
const result = parseRosterResult(text);
|
|
6673
|
+
if (!result.ok) {
|
|
6674
|
+
throw result.error;
|
|
6675
|
+
}
|
|
6676
|
+
return result.roster;
|
|
6677
|
+
}
|
|
6678
|
+
function parseRosterResult(text) {
|
|
6679
|
+
const match = ROSTER_JSON_FENCE.exec(text);
|
|
6680
|
+
if (match === null || match[1] === undefined) {
|
|
6681
|
+
return {
|
|
6682
|
+
ok: false,
|
|
6683
|
+
error: new RosterParseError("missing-json-block", "openteam roster: no JSON block found in roster file")
|
|
6684
|
+
};
|
|
6685
|
+
}
|
|
6686
|
+
let data;
|
|
6687
|
+
try {
|
|
6688
|
+
data = JSON.parse(match[1]);
|
|
6689
|
+
} catch (error) {
|
|
6690
|
+
return {
|
|
6691
|
+
ok: false,
|
|
6692
|
+
error: new RosterParseError("malformed-json", `openteam roster: malformed JSON in roster file: ${String(error)}`)
|
|
6693
|
+
};
|
|
6694
|
+
}
|
|
6695
|
+
const parsed = RosterSchema.safeParse(data);
|
|
6696
|
+
if (!parsed.success) {
|
|
6697
|
+
return {
|
|
6698
|
+
ok: false,
|
|
6699
|
+
error: new RosterParseError("invalid-roster-schema", `openteam roster: invalid roster schema: ${parsed.error.message}`)
|
|
6700
|
+
};
|
|
6701
|
+
}
|
|
6702
|
+
return { ok: true, roster: parsed.data };
|
|
6703
|
+
}
|
|
6520
6704
|
var GUARANTEED_ROLE_IDS = [
|
|
6521
6705
|
"orchestrator",
|
|
6522
6706
|
"guardian",
|
|
6523
6707
|
"scribe",
|
|
6524
6708
|
"ralph"
|
|
6525
6709
|
];
|
|
6710
|
+
var GUARANTEED_ROLE_DEFAULT_AGENT_NAMES = {
|
|
6711
|
+
orchestrator: "openteam",
|
|
6712
|
+
guardian: "guardian",
|
|
6713
|
+
scribe: "scribe",
|
|
6714
|
+
ralph: "ralph"
|
|
6715
|
+
};
|
|
6716
|
+
function defaultAgentNameForGuaranteedRole(roleID) {
|
|
6717
|
+
return GUARANTEED_ROLE_DEFAULT_AGENT_NAMES[roleID];
|
|
6718
|
+
}
|
|
6719
|
+
function isGuaranteedRoleID(roleID) {
|
|
6720
|
+
return GUARANTEED_ROLE_IDS.includes(roleID);
|
|
6721
|
+
}
|
|
6526
6722
|
function missingGuaranteedRoles(roster) {
|
|
6527
6723
|
const present = new Set(roster.entries.map((entry) => entry.roleID));
|
|
6528
6724
|
return GUARANTEED_ROLE_IDS.filter((roleID) => !present.has(roleID));
|
|
@@ -6530,6 +6726,100 @@ function missingGuaranteedRoles(roster) {
|
|
|
6530
6726
|
function hasAllGuaranteedRoles(roster) {
|
|
6531
6727
|
return missingGuaranteedRoles(roster).length === 0;
|
|
6532
6728
|
}
|
|
6729
|
+
function isRoleProfileLike(value) {
|
|
6730
|
+
return typeof value === "object" && value !== null && "roleID" in value && "defaultTier" in value;
|
|
6731
|
+
}
|
|
6732
|
+
function configuredRolesFromAuditInput(input) {
|
|
6733
|
+
if ("configuredRoles" in input && !isRoleProfileLike(input.configuredRoles)) {
|
|
6734
|
+
return input.configuredRoles;
|
|
6735
|
+
}
|
|
6736
|
+
return input;
|
|
6737
|
+
}
|
|
6738
|
+
function auditRoster(rosterOrText, configuredRolesOrOptions = {}) {
|
|
6739
|
+
const configuredRoles = configuredRolesFromAuditInput(configuredRolesOrOptions);
|
|
6740
|
+
if (typeof rosterOrText === "string") {
|
|
6741
|
+
const parsed = parseRosterResult(rosterOrText);
|
|
6742
|
+
if (!parsed.ok) {
|
|
6743
|
+
return {
|
|
6744
|
+
findings: [
|
|
6745
|
+
{
|
|
6746
|
+
kind: "unparseable-roster",
|
|
6747
|
+
roleID: "roster",
|
|
6748
|
+
code: parsed.error.code,
|
|
6749
|
+
message: parsed.error.message
|
|
6750
|
+
}
|
|
6751
|
+
],
|
|
6752
|
+
hasAllGuaranteedRoles: false,
|
|
6753
|
+
missingGuaranteedRoleIDs: [],
|
|
6754
|
+
unprofiledRoleEntries: [],
|
|
6755
|
+
misassignedGuaranteedRoleEntries: []
|
|
6756
|
+
};
|
|
6757
|
+
}
|
|
6758
|
+
return auditParsedRoster(parsed.roster, configuredRoles);
|
|
6759
|
+
}
|
|
6760
|
+
return auditParsedRoster(rosterOrText, configuredRoles);
|
|
6761
|
+
}
|
|
6762
|
+
function auditParsedRoster(roster, configuredRoles) {
|
|
6763
|
+
const missingGuaranteedRoleIDs = missingGuaranteedRoles(roster);
|
|
6764
|
+
const missing = new Set(missingGuaranteedRoleIDs);
|
|
6765
|
+
const findings = missingGuaranteedRoleIDs.map((roleID) => ({
|
|
6766
|
+
kind: "missing-guaranteed-role",
|
|
6767
|
+
roleID
|
|
6768
|
+
}));
|
|
6769
|
+
const unprofiledRoleEntries = [];
|
|
6770
|
+
const misassignedGuaranteedRoleEntries = [];
|
|
6771
|
+
roster.entries.forEach((entry) => {
|
|
6772
|
+
const profile = getRoleProfile(entry.roleID, configuredRoles);
|
|
6773
|
+
if (profile === undefined && !isKnownNonWorkerRole(entry.roleID)) {
|
|
6774
|
+
unprofiledRoleEntries.push({
|
|
6775
|
+
roleID: entry.roleID,
|
|
6776
|
+
agentName: entry.agentName,
|
|
6777
|
+
severity: "info"
|
|
6778
|
+
});
|
|
6779
|
+
findings.push({
|
|
6780
|
+
kind: "unresolved-role-profile",
|
|
6781
|
+
roleID: entry.roleID,
|
|
6782
|
+
agentName: entry.agentName
|
|
6783
|
+
});
|
|
6784
|
+
}
|
|
6785
|
+
if (isGuaranteedRoleID(entry.agentName) && entry.roleID !== entry.agentName && missing.has(entry.agentName)) {
|
|
6786
|
+
misassignedGuaranteedRoleEntries.push({
|
|
6787
|
+
roleID: entry.roleID,
|
|
6788
|
+
agentName: entry.agentName,
|
|
6789
|
+
missingRoleID: entry.agentName,
|
|
6790
|
+
reason: "agentNameMatchesMissingGuaranteedRole"
|
|
6791
|
+
});
|
|
6792
|
+
findings.push({
|
|
6793
|
+
kind: "non-canonical-role-id",
|
|
6794
|
+
roleID: entry.roleID,
|
|
6795
|
+
agentName: entry.agentName,
|
|
6796
|
+
canonicalRoleID: entry.agentName
|
|
6797
|
+
});
|
|
6798
|
+
return;
|
|
6799
|
+
}
|
|
6800
|
+
if (profile !== undefined && isGuaranteedRoleID(profile.roleID) && profile.roleID !== entry.roleID && missing.has(profile.roleID)) {
|
|
6801
|
+
misassignedGuaranteedRoleEntries.push({
|
|
6802
|
+
roleID: entry.roleID,
|
|
6803
|
+
agentName: entry.agentName,
|
|
6804
|
+
missingRoleID: profile.roleID,
|
|
6805
|
+
reason: "roleIDResolvesToMissingGuaranteedRole"
|
|
6806
|
+
});
|
|
6807
|
+
findings.push({
|
|
6808
|
+
kind: "non-canonical-role-id",
|
|
6809
|
+
roleID: entry.roleID,
|
|
6810
|
+
agentName: entry.agentName,
|
|
6811
|
+
canonicalRoleID: profile.roleID
|
|
6812
|
+
});
|
|
6813
|
+
}
|
|
6814
|
+
});
|
|
6815
|
+
return {
|
|
6816
|
+
findings,
|
|
6817
|
+
hasAllGuaranteedRoles: missingGuaranteedRoleIDs.length === 0,
|
|
6818
|
+
missingGuaranteedRoleIDs,
|
|
6819
|
+
unprofiledRoleEntries,
|
|
6820
|
+
misassignedGuaranteedRoleEntries
|
|
6821
|
+
};
|
|
6822
|
+
}
|
|
6533
6823
|
function enforceGuaranteedRoles(roster) {
|
|
6534
6824
|
const missing = missingGuaranteedRoles(roster);
|
|
6535
6825
|
if (missing.length === 0) {
|
|
@@ -6537,7 +6827,7 @@ function enforceGuaranteedRoles(roster) {
|
|
|
6537
6827
|
}
|
|
6538
6828
|
const added = missing.map((roleID) => ({
|
|
6539
6829
|
roleID,
|
|
6540
|
-
agentName: roleID
|
|
6830
|
+
agentName: defaultAgentNameForGuaranteedRole(roleID)
|
|
6541
6831
|
}));
|
|
6542
6832
|
return { universe: roster.universe, entries: [...roster.entries, ...added] };
|
|
6543
6833
|
}
|
|
@@ -6569,7 +6859,6 @@ async function dispatchGuaranteedRole(agentName, agentFileExists, dispatch) {
|
|
|
6569
6859
|
}
|
|
6570
6860
|
|
|
6571
6861
|
// src/orchestrator/rosterPersistence.ts
|
|
6572
|
-
var ROSTER_JSON_FENCE = /```json\s*([\s\S]*?)```/;
|
|
6573
6862
|
function serializeRoster(roster) {
|
|
6574
6863
|
const data = { universe: roster.universe, entries: roster.entries };
|
|
6575
6864
|
const json = JSON.stringify(data, null, 2);
|
|
@@ -6582,38 +6871,41 @@ ${json}
|
|
|
6582
6871
|
\`\`\`
|
|
6583
6872
|
`;
|
|
6584
6873
|
}
|
|
6585
|
-
function parseRoster(text) {
|
|
6586
|
-
const match = ROSTER_JSON_FENCE.exec(text);
|
|
6587
|
-
if (match === null || match[1] === undefined) {
|
|
6588
|
-
throw new Error("openteam roster: no JSON block found in roster file");
|
|
6589
|
-
}
|
|
6590
|
-
let data;
|
|
6591
|
-
try {
|
|
6592
|
-
data = JSON.parse(match[1]);
|
|
6593
|
-
} catch (error) {
|
|
6594
|
-
throw new Error(`openteam roster: malformed JSON in roster file: ${String(error)}`);
|
|
6595
|
-
}
|
|
6596
|
-
return RosterSchema.parse(data);
|
|
6597
|
-
}
|
|
6598
6874
|
async function persistRoster(storage, roster, path2 = OPENTEAM_ROSTER_PATH) {
|
|
6599
6875
|
await storage.write(path2, serializeRoster(roster));
|
|
6600
6876
|
return path2;
|
|
6601
6877
|
}
|
|
6602
|
-
async function
|
|
6878
|
+
async function loadRosterFile(storage, path2 = OPENTEAM_ROSTER_PATH) {
|
|
6603
6879
|
const content = await storage.read(path2);
|
|
6604
6880
|
if (content === undefined) {
|
|
6605
|
-
return;
|
|
6881
|
+
return { status: "absent", path: path2 };
|
|
6606
6882
|
}
|
|
6607
|
-
|
|
6883
|
+
const parsed = parseRosterResult(content);
|
|
6884
|
+
if (!parsed.ok) {
|
|
6885
|
+
return { status: "invalid", path: path2, error: parsed.error };
|
|
6886
|
+
}
|
|
6887
|
+
return {
|
|
6888
|
+
status: "loaded",
|
|
6889
|
+
path: path2,
|
|
6890
|
+
roster: enforceGuaranteedRoles(parsed.roster)
|
|
6891
|
+
};
|
|
6608
6892
|
}
|
|
6609
6893
|
async function loadOrGenerateRoster(storage, generate, path2 = OPENTEAM_ROSTER_PATH) {
|
|
6610
|
-
const existing = await
|
|
6611
|
-
if (existing
|
|
6612
|
-
return { roster: existing, regenerated: false };
|
|
6894
|
+
const existing = await loadRosterFile(storage, path2);
|
|
6895
|
+
if (existing.status === "loaded") {
|
|
6896
|
+
return { roster: existing.roster, regenerated: false };
|
|
6613
6897
|
}
|
|
6614
|
-
const roster = await generate();
|
|
6615
|
-
|
|
6616
|
-
|
|
6898
|
+
const roster = enforceGuaranteedRoles(await generate());
|
|
6899
|
+
if (existing.status === "absent") {
|
|
6900
|
+
await persistRoster(storage, roster, path2);
|
|
6901
|
+
return { roster, regenerated: true };
|
|
6902
|
+
}
|
|
6903
|
+
return {
|
|
6904
|
+
roster,
|
|
6905
|
+
regenerated: true,
|
|
6906
|
+
recoveredFromInvalidPersisted: true,
|
|
6907
|
+
invalidPersistedRosterError: existing.error.message
|
|
6908
|
+
};
|
|
6617
6909
|
}
|
|
6618
6910
|
|
|
6619
6911
|
// src/plugin/availability.ts
|
|
@@ -7641,6 +7933,105 @@ function agentModelLine(diagnostic, nameWidth, searchedPaths) {
|
|
|
7641
7933
|
}
|
|
7642
7934
|
return ` ${mark} ${name} [${diagnostic.mode}] ${modelText2} ${provenance} ${parts.join(" · ")}`;
|
|
7643
7935
|
}
|
|
7936
|
+
function diagnosticMark(level) {
|
|
7937
|
+
switch (level) {
|
|
7938
|
+
case "error":
|
|
7939
|
+
return "✗";
|
|
7940
|
+
case "warn":
|
|
7941
|
+
return "⚠";
|
|
7942
|
+
default:
|
|
7943
|
+
return "·";
|
|
7944
|
+
}
|
|
7945
|
+
}
|
|
7946
|
+
function diagnosticsSection(diagnostics) {
|
|
7947
|
+
const notable = diagnostics.filter((diagnostic) => diagnostic.level === "warn" || diagnostic.level === "error");
|
|
7948
|
+
if (notable.length === 0) {
|
|
7949
|
+
return [];
|
|
7950
|
+
}
|
|
7951
|
+
const recent = notable.slice(-5);
|
|
7952
|
+
const lines = [
|
|
7953
|
+
` plugin diagnostics: ${notable.length} warning/error event(s)`
|
|
7954
|
+
];
|
|
7955
|
+
for (const diagnostic of recent) {
|
|
7956
|
+
lines.push(` ${diagnosticMark(diagnostic.level)} [${diagnostic.code}] ${diagnosticDescription(diagnostic.code)}`);
|
|
7957
|
+
}
|
|
7958
|
+
if (notable.length > recent.length) {
|
|
7959
|
+
lines.push(` … ${notable.length - recent.length} older diagnostic event(s) omitted`);
|
|
7960
|
+
}
|
|
7961
|
+
lines.push(" note: plugin diagnostics are bounded classes recorded instead of writing to the opencode TUI console.");
|
|
7962
|
+
return lines;
|
|
7963
|
+
}
|
|
7964
|
+
function rosterEntryText(entry) {
|
|
7965
|
+
return entry.agentName === undefined ? `"${entry.roleID}"` : `"${entry.roleID}" (agentName "${entry.agentName}")`;
|
|
7966
|
+
}
|
|
7967
|
+
function canonicalRoleID(entry) {
|
|
7968
|
+
return entry.canonicalRoleID ?? entry.missingRoleID;
|
|
7969
|
+
}
|
|
7970
|
+
function legacyRosterFindings(audit) {
|
|
7971
|
+
const findings = [];
|
|
7972
|
+
for (const roleID of audit.missingGuaranteedRoleIDs ?? []) {
|
|
7973
|
+
findings.push({ kind: "missing-guaranteed-role", roleID });
|
|
7974
|
+
}
|
|
7975
|
+
for (const entry of audit.misassignedGuaranteedRoleEntries ?? []) {
|
|
7976
|
+
const canonical2 = canonicalRoleID(entry);
|
|
7977
|
+
findings.push({
|
|
7978
|
+
kind: "non-canonical-role-id",
|
|
7979
|
+
roleID: entry.roleID,
|
|
7980
|
+
...entry.agentName !== undefined ? { agentName: entry.agentName } : {},
|
|
7981
|
+
...canonical2 !== undefined ? { canonicalRoleID: canonical2 } : {}
|
|
7982
|
+
});
|
|
7983
|
+
}
|
|
7984
|
+
for (const entry of audit.unprofiledRoleEntries ?? []) {
|
|
7985
|
+
findings.push({
|
|
7986
|
+
kind: "unresolved-role-profile",
|
|
7987
|
+
roleID: entry.roleID,
|
|
7988
|
+
...entry.agentName !== undefined ? { agentName: entry.agentName } : {}
|
|
7989
|
+
});
|
|
7990
|
+
}
|
|
7991
|
+
return findings;
|
|
7992
|
+
}
|
|
7993
|
+
function rosterHealthSection(audit, loadError, rosterRoleCount) {
|
|
7994
|
+
const auditFindings = audit?.findings ?? legacyRosterFindings(audit ?? {});
|
|
7995
|
+
const unparseable = auditFindings.find((finding) => finding.kind === "unparseable-roster");
|
|
7996
|
+
const unparseableMessage = loadError ?? unparseable?.message;
|
|
7997
|
+
if (unparseableMessage !== undefined) {
|
|
7998
|
+
return [
|
|
7999
|
+
" roster health:",
|
|
8000
|
+
` ✗ unparseable roster: ${OPENTEAM_ROSTER_PATH} — ${unparseableMessage}`,
|
|
8001
|
+
" problem: the roster is present but is not machine-readable by openteam.",
|
|
8002
|
+
" remedy: keep any human notes outside the JSON fence, add one fenced `json` block containing `{ universe, entries: [{ roleID, agentName }] }`, then re-run `openteam doctor`."
|
|
8003
|
+
];
|
|
8004
|
+
}
|
|
8005
|
+
if (audit === undefined) {
|
|
8006
|
+
return [];
|
|
8007
|
+
}
|
|
8008
|
+
const missing = auditFindings.filter((finding) => finding.kind === "missing-guaranteed-role").map((finding) => finding.roleID);
|
|
8009
|
+
const misassigned = auditFindings.filter((finding) => finding.kind === "non-canonical-role-id");
|
|
8010
|
+
const unprofiled = auditFindings.filter((finding) => finding.kind === "unresolved-role-profile");
|
|
8011
|
+
if (missing.length === 0 && misassigned.length === 0 && unprofiled.length === 0) {
|
|
8012
|
+
return rosterRoleCount === undefined ? [] : [
|
|
8013
|
+
" roster health:",
|
|
8014
|
+
` ✓ ${OPENTEAM_ROSTER_PATH} read (${rosterRoleCount} roles); guaranteed roles present.`
|
|
8015
|
+
];
|
|
8016
|
+
}
|
|
8017
|
+
const lines = [" roster health:"];
|
|
8018
|
+
if (missing.length > 0) {
|
|
8019
|
+
lines.push(` ✗ missing guaranteed roleID(s): ${missing.join(", ")}`);
|
|
8020
|
+
lines.push(` remedy: edit ${OPENTEAM_ROSTER_PATH}'s JSON block to add these canonical roleIDs with their themed agentName values; keep roleID canonical, e.g. { "roleID": "scribe", "agentName": "c3po" }.`);
|
|
8021
|
+
}
|
|
8022
|
+
for (const entry of misassigned) {
|
|
8023
|
+
const canonical2 = canonicalRoleID(entry);
|
|
8024
|
+
const target = canonical2 === undefined ? "the canonical functional roleID" : `"${canonical2}"`;
|
|
8025
|
+
lines.push(` ⚠ non-canonical roleID ${rosterEntryText(entry)}`);
|
|
8026
|
+
lines.push(` remedy: change roleID to ${target} and keep the themed name in agentName.`);
|
|
8027
|
+
}
|
|
8028
|
+
if (unprofiled.length > 0) {
|
|
8029
|
+
const roleIDs = unprofiled.map((entry) => entry.roleID).join(", ");
|
|
8030
|
+
lines.push(` · unprofiled roleID(s): ${roleIDs}`);
|
|
8031
|
+
lines.push(" remedy: if intentional project roles, no action is required; for curated routing, change roleID to a TEAM_ROLES key or add a profile under orchestrator.roles in .opencode/openteam.json.");
|
|
8032
|
+
}
|
|
8033
|
+
return lines;
|
|
8034
|
+
}
|
|
7644
8035
|
function agentModelsSection(diagnostics, searchedPaths) {
|
|
7645
8036
|
const lines = [" agent models:"];
|
|
7646
8037
|
if (diagnostics.length === 0) {
|
|
@@ -7676,6 +8067,10 @@ function renderDoctor(input) {
|
|
|
7676
8067
|
}
|
|
7677
8068
|
}
|
|
7678
8069
|
lines.push(` telemetry: ${input.telemetryPath} — ${input.telemetryRecords} record(s)`);
|
|
8070
|
+
if (input.diagnostics !== undefined) {
|
|
8071
|
+
lines.push(...diagnosticsSection(input.diagnostics));
|
|
8072
|
+
}
|
|
8073
|
+
lines.push(...rosterHealthSection(input.rosterAudit, input.rosterLoadError, input.rosterRoleCount));
|
|
7679
8074
|
if (input.otelBackend !== undefined) {
|
|
7680
8075
|
const otel = input.otelBackend;
|
|
7681
8076
|
lines.push(" opentelemetry:");
|
|
@@ -9320,13 +9715,23 @@ async function guaranteedAgentStatuses(runtime) {
|
|
|
9320
9715
|
}
|
|
9321
9716
|
return statuses;
|
|
9322
9717
|
}
|
|
9718
|
+
function rosterPlanSummary(rosterFile) {
|
|
9719
|
+
switch (rosterFile.status) {
|
|
9720
|
+
case "loaded":
|
|
9721
|
+
return `${rosterFile.roster.universe} (${rosterFile.roster.entries.length} roles)`;
|
|
9722
|
+
case "absent":
|
|
9723
|
+
return "none yet — one would be generated on the first real run";
|
|
9724
|
+
case "invalid":
|
|
9725
|
+
return `${rosterFile.path} is present but unparseable — run 'openteam doctor'`;
|
|
9726
|
+
}
|
|
9727
|
+
}
|
|
9323
9728
|
function renderLoopPlan(plan2) {
|
|
9324
9729
|
const { flags } = plan2;
|
|
9325
9730
|
const budget = flags.budgetUsd !== undefined ? `$${flags.budgetUsd}` : "unbounded";
|
|
9326
9731
|
const freshContext = flags.freshContext ? "on (pure Ralph pattern)" : "off";
|
|
9327
9732
|
const budgetStop = flags.budgetUsd !== undefined ? ", budget" : "";
|
|
9328
9733
|
const redTestsStop = flags.stopOnRedTests ? ", red tests" : "";
|
|
9329
|
-
const rosterSummary =
|
|
9734
|
+
const rosterSummary = rosterPlanSummary(plan2.rosterFile);
|
|
9330
9735
|
const lines = [
|
|
9331
9736
|
"openteam loop — plan (dry run, nothing dispatched):",
|
|
9332
9737
|
` backlog: ${flags.backlogPath} (${plan2.uncheckedCount} unchecked item(s))`,
|
|
@@ -9370,14 +9775,14 @@ ${LOOP_HELP}` };
|
|
|
9370
9775
|
const backlogText = await runtime.readBacklog(flags.backlogPath) ?? "";
|
|
9371
9776
|
const items = parseBacklog(backlogText);
|
|
9372
9777
|
const unchecked = uncheckedBacklogItems(items);
|
|
9373
|
-
const
|
|
9778
|
+
const rosterFile = await runtime.loadRosterFile();
|
|
9374
9779
|
const guaranteed = await guaranteedAgentStatuses(runtime);
|
|
9375
9780
|
const plannedIterations = Math.max(0, Math.min(flags.maxIterations, unchecked.length));
|
|
9376
9781
|
const plan2 = {
|
|
9377
9782
|
flags,
|
|
9378
9783
|
uncheckedCount: unchecked.length,
|
|
9379
9784
|
plannedIterations,
|
|
9380
|
-
|
|
9785
|
+
rosterFile,
|
|
9381
9786
|
guaranteed
|
|
9382
9787
|
};
|
|
9383
9788
|
if (flags.dryRun) {
|
|
@@ -9455,9 +9860,19 @@ async function runCli(argv, deps) {
|
|
|
9455
9860
|
if (command === "doctor") {
|
|
9456
9861
|
const config = await deps.loadConfig(configPath, configResolution);
|
|
9457
9862
|
let opencodeConfigError;
|
|
9458
|
-
|
|
9863
|
+
let rosterLoadError;
|
|
9864
|
+
const [
|
|
9865
|
+
snapshots,
|
|
9866
|
+
records,
|
|
9867
|
+
diagnostics,
|
|
9868
|
+
cacheEntries,
|
|
9869
|
+
agentFiles,
|
|
9870
|
+
opencodeConfig,
|
|
9871
|
+
roster
|
|
9872
|
+
] = await Promise.all([
|
|
9459
9873
|
deps.probe(config),
|
|
9460
9874
|
deps.readTelemetry(telemetryPath),
|
|
9875
|
+
deps.readDiagnostics !== undefined ? deps.readDiagnostics() : Promise.resolve(undefined),
|
|
9461
9876
|
deps.cachePort !== undefined ? deps.cachePort.listEntries(deps.cachePort.resolveCacheRoot(parsed.cacheRoot)).catch(() => {
|
|
9462
9877
|
return;
|
|
9463
9878
|
}) : Promise.resolve(undefined),
|
|
@@ -9465,7 +9880,11 @@ async function runCli(argv, deps) {
|
|
|
9465
9880
|
deps.readOpencodeConfig(opencodeConfigPaths).catch((err) => {
|
|
9466
9881
|
opencodeConfigError = err instanceof Error ? err.message : String(err);
|
|
9467
9882
|
return;
|
|
9468
|
-
})
|
|
9883
|
+
}),
|
|
9884
|
+
deps.loadRosterForDoctor !== undefined ? deps.loadRosterForDoctor().catch((err) => {
|
|
9885
|
+
rosterLoadError = err instanceof Error ? err.message : String(err);
|
|
9886
|
+
return;
|
|
9887
|
+
}) : Promise.resolve(undefined)
|
|
9469
9888
|
]);
|
|
9470
9889
|
const defaultModel = defaultModelFromOpencode(opencodeConfig);
|
|
9471
9890
|
const agentModels = diagnoseAgentModels(agentFiles, {
|
|
@@ -9480,6 +9899,18 @@ async function runCli(argv, deps) {
|
|
|
9480
9899
|
backend: "opentelemetry",
|
|
9481
9900
|
connectionEnv: "APPLICATIONINSIGHTS_CONNECTION_STRING"
|
|
9482
9901
|
}, (name) => process.env[name]);
|
|
9902
|
+
let rosterAudit;
|
|
9903
|
+
let rosterRoleCount;
|
|
9904
|
+
if (roster !== undefined && deps.auditRoster !== undefined) {
|
|
9905
|
+
if (typeof roster !== "string") {
|
|
9906
|
+
rosterRoleCount = roster.entries.length;
|
|
9907
|
+
}
|
|
9908
|
+
try {
|
|
9909
|
+
rosterAudit = deps.auditRoster(roster, config.orchestrator?.roles);
|
|
9910
|
+
} catch (err) {
|
|
9911
|
+
rosterLoadError = err instanceof Error ? err.message : String(err);
|
|
9912
|
+
}
|
|
9913
|
+
}
|
|
9483
9914
|
return {
|
|
9484
9915
|
exitCode: 0,
|
|
9485
9916
|
stdout: renderDoctor({
|
|
@@ -9489,6 +9920,10 @@ async function runCli(argv, deps) {
|
|
|
9489
9920
|
telemetryRecords: records.length,
|
|
9490
9921
|
agentModels,
|
|
9491
9922
|
opencodeConfigPaths,
|
|
9923
|
+
...rosterAudit !== undefined ? { rosterAudit } : {},
|
|
9924
|
+
...rosterRoleCount !== undefined ? { rosterRoleCount } : {},
|
|
9925
|
+
...rosterLoadError !== undefined ? { rosterLoadError } : {},
|
|
9926
|
+
...diagnostics !== undefined && diagnostics.length > 0 ? { diagnostics } : {},
|
|
9492
9927
|
...opencodeConfigError !== undefined ? { opencodeConfigError } : {},
|
|
9493
9928
|
...cacheEntries !== undefined ? { cacheEntries, cliVersion: deps.version } : {},
|
|
9494
9929
|
...legacyLayout !== undefined && legacyLayout.length > 0 ? { legacyLayout } : {},
|
|
@@ -9650,6 +10085,96 @@ function createCommandTool(deps) {
|
|
|
9650
10085
|
});
|
|
9651
10086
|
}
|
|
9652
10087
|
|
|
10088
|
+
// src/plugin/diagnostics.ts
|
|
10089
|
+
var PLUGIN_DIAGNOSTIC_SESSION_ID = "plugin";
|
|
10090
|
+
function asDiagnosticClient(client) {
|
|
10091
|
+
if (typeof client !== "object" || client === null) {
|
|
10092
|
+
return {};
|
|
10093
|
+
}
|
|
10094
|
+
return client;
|
|
10095
|
+
}
|
|
10096
|
+
function appLogOptions(diagnostic, directory) {
|
|
10097
|
+
return {
|
|
10098
|
+
body: {
|
|
10099
|
+
service: "openteam",
|
|
10100
|
+
level: diagnostic.level,
|
|
10101
|
+
message: diagnosticDescription(diagnostic.code),
|
|
10102
|
+
extra: {
|
|
10103
|
+
code: diagnostic.code,
|
|
10104
|
+
sessionID: diagnostic.sessionID,
|
|
10105
|
+
source: "plugin"
|
|
10106
|
+
}
|
|
10107
|
+
},
|
|
10108
|
+
...directory !== undefined && directory.length > 0 ? { query: { directory } } : {}
|
|
10109
|
+
};
|
|
10110
|
+
}
|
|
10111
|
+
function toastOptions(diagnostic, directory) {
|
|
10112
|
+
return {
|
|
10113
|
+
body: {
|
|
10114
|
+
title: `openteam ${diagnostic.level}`,
|
|
10115
|
+
message: `${diagnosticDescription(diagnostic.code)} Run \`openteam doctor\` for details.`,
|
|
10116
|
+
variant: diagnostic.level === "error" ? "error" : "warning",
|
|
10117
|
+
duration: 8000
|
|
10118
|
+
},
|
|
10119
|
+
...directory !== undefined && directory.length > 0 ? { query: { directory } } : {}
|
|
10120
|
+
};
|
|
10121
|
+
}
|
|
10122
|
+
function fireAndForget(run) {
|
|
10123
|
+
try {
|
|
10124
|
+
Promise.resolve(run()).catch(() => {});
|
|
10125
|
+
} catch {}
|
|
10126
|
+
}
|
|
10127
|
+
function shouldToast(diagnostic, options) {
|
|
10128
|
+
if (options?.toast !== undefined) {
|
|
10129
|
+
return options.toast;
|
|
10130
|
+
}
|
|
10131
|
+
return diagnostic.level === "warn" || diagnostic.level === "error";
|
|
10132
|
+
}
|
|
10133
|
+
function createPluginDiagnosticChannel(deps) {
|
|
10134
|
+
const client = asDiagnosticClient(deps.client);
|
|
10135
|
+
const toastKeys = new Set;
|
|
10136
|
+
let eventSink = deps.sink ?? createNullEventSink();
|
|
10137
|
+
const emit = (input, options = {}) => {
|
|
10138
|
+
const diagnostic = {
|
|
10139
|
+
v: EVENT_SCHEMA_VERSION,
|
|
10140
|
+
type: "diagnostic",
|
|
10141
|
+
ts: deps.now(),
|
|
10142
|
+
sessionID: input.sessionID ?? PLUGIN_DIAGNOSTIC_SESSION_ID,
|
|
10143
|
+
level: input.level,
|
|
10144
|
+
code: input.code
|
|
10145
|
+
};
|
|
10146
|
+
if (options.persist !== false) {
|
|
10147
|
+
fireAndForget(() => eventSink.emit(diagnostic));
|
|
10148
|
+
}
|
|
10149
|
+
const log = client.app?.log;
|
|
10150
|
+
if (typeof log === "function") {
|
|
10151
|
+
fireAndForget(() => log.call(client.app, appLogOptions(diagnostic, deps.directory)));
|
|
10152
|
+
}
|
|
10153
|
+
if (shouldToast(diagnostic, options) && !toastKeys.has(input.code)) {
|
|
10154
|
+
const showToast = client.tui?.showToast;
|
|
10155
|
+
toastKeys.add(input.code);
|
|
10156
|
+
if (typeof showToast === "function") {
|
|
10157
|
+
fireAndForget(() => showToast.call(client.tui, toastOptions(diagnostic, deps.directory)));
|
|
10158
|
+
}
|
|
10159
|
+
}
|
|
10160
|
+
};
|
|
10161
|
+
return {
|
|
10162
|
+
setEventSink: (sink) => {
|
|
10163
|
+
eventSink = sink;
|
|
10164
|
+
},
|
|
10165
|
+
emit,
|
|
10166
|
+
sink: (level, code, options = {}) => (message) => {
|
|
10167
|
+
emit({ level, code }, options);
|
|
10168
|
+
},
|
|
10169
|
+
warn: (code, options = {}) => (message) => {
|
|
10170
|
+
emit({ level: "warn", code }, options);
|
|
10171
|
+
},
|
|
10172
|
+
info: (code, options = {}) => (message) => {
|
|
10173
|
+
emit({ level: "info", code }, options);
|
|
10174
|
+
}
|
|
10175
|
+
};
|
|
10176
|
+
}
|
|
10177
|
+
|
|
9653
10178
|
// src/plugin/graphTool.ts
|
|
9654
10179
|
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
9655
10180
|
|
|
@@ -11111,16 +11636,16 @@ function insertRecords(db, records) {
|
|
|
11111
11636
|
throw error;
|
|
11112
11637
|
}
|
|
11113
11638
|
}
|
|
11114
|
-
function warnMemoryIndexRecordRejections(result) {
|
|
11639
|
+
function warnMemoryIndexRecordRejections(result, warn) {
|
|
11115
11640
|
if (result.rejectedRows === 0) {
|
|
11116
11641
|
return;
|
|
11117
11642
|
}
|
|
11118
|
-
console.warn(`[openteam] memory index reader rejected ${result.rejectedRows} row(s); semantic memory may be incomplete.`);
|
|
11643
|
+
(warn ?? console.warn)(`[openteam] memory index reader rejected ${result.rejectedRows} row(s); semantic memory may be incomplete.`);
|
|
11119
11644
|
for (const rejection of result.rejections) {
|
|
11120
|
-
console.warn(`[openteam] • memory_records:${rejection.recordId}: ${rejection.detail}`);
|
|
11645
|
+
(warn ?? console.warn)(`[openteam] • memory_records:${rejection.recordId}: ${rejection.detail}`);
|
|
11121
11646
|
}
|
|
11122
11647
|
}
|
|
11123
|
-
function createMemoryIndex(db) {
|
|
11648
|
+
function createMemoryIndex(db, warn) {
|
|
11124
11649
|
db.exec(SCHEMA);
|
|
11125
11650
|
const allRecordsFn = () => {
|
|
11126
11651
|
const rows = db.query("SELECT id, payload FROM memory_records ORDER BY created_at ASC, id ASC").all();
|
|
@@ -11148,7 +11673,7 @@ function createMemoryIndex(db) {
|
|
|
11148
11673
|
};
|
|
11149
11674
|
const allRecordsWithWarning = () => {
|
|
11150
11675
|
const result = allRecordsFn();
|
|
11151
|
-
warnMemoryIndexRecordRejections(result);
|
|
11676
|
+
warnMemoryIndexRecordRejections(result, warn);
|
|
11152
11677
|
return result.records;
|
|
11153
11678
|
};
|
|
11154
11679
|
const state = () => {
|
|
@@ -11987,6 +12512,7 @@ function createOrchestrateTool(deps) {
|
|
|
11987
12512
|
sink: deps.sink,
|
|
11988
12513
|
now: deps.now,
|
|
11989
12514
|
newDecisionID: deps.newDecisionID,
|
|
12515
|
+
warn: deps.warn,
|
|
11990
12516
|
warnedRoles: _pluginWarnedRoles,
|
|
11991
12517
|
localRuntimeReachable: deps.localRuntimeReachable,
|
|
11992
12518
|
reachableRuntimeIds: deps.reachableRuntimeIds,
|
|
@@ -12217,7 +12743,7 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
|
|
|
12217
12743
|
const warn = deps.warn ?? console.warn;
|
|
12218
12744
|
try {
|
|
12219
12745
|
const db = await openDatabase(semantic.indexPath);
|
|
12220
|
-
const index = createMemoryIndex(db);
|
|
12746
|
+
const index = createMemoryIndex(db, warn);
|
|
12221
12747
|
const rebuildResult = await rebuildMemoryIndexFromStorage(index, semantic.logPath, { storage: deps.storage });
|
|
12222
12748
|
if (rebuildResult.rejectedLines > 0) {
|
|
12223
12749
|
warn(`[openteam] memory log rejected ${rebuildResult.rejectedLines} line(s); semantic memory may be incomplete.`);
|
|
@@ -12282,7 +12808,7 @@ var createOtlpSpanExporter = (connection, config) => {
|
|
|
12282
12808
|
// package.json
|
|
12283
12809
|
var package_default = {
|
|
12284
12810
|
name: "@jmanuelcorral/openteam",
|
|
12285
|
-
version: "0.9.
|
|
12811
|
+
version: "0.9.2",
|
|
12286
12812
|
packageManager: "bun@1.3.14",
|
|
12287
12813
|
description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
|
|
12288
12814
|
license: "MIT",
|
|
@@ -12395,24 +12921,24 @@ function createShellExec($) {
|
|
|
12395
12921
|
};
|
|
12396
12922
|
};
|
|
12397
12923
|
}
|
|
12398
|
-
function logAvailabilityRefreshError(error) {
|
|
12924
|
+
function logAvailabilityRefreshError(error, warn) {
|
|
12399
12925
|
const message = error instanceof Error ? error.message : String(error);
|
|
12400
|
-
console.warn(`[openteam] availability refresh failed: ${message}`);
|
|
12926
|
+
(warn ?? console.warn)(`[openteam] availability refresh failed: ${message}`);
|
|
12401
12927
|
}
|
|
12402
|
-
function logTelemetryError(error) {
|
|
12928
|
+
function logTelemetryError(error, warn) {
|
|
12403
12929
|
const message = error instanceof Error ? error.message : String(error);
|
|
12404
|
-
console.warn(`[openteam] telemetry write failed: ${message}`);
|
|
12930
|
+
(warn ?? console.warn)(`[openteam] telemetry write failed: ${message}`);
|
|
12405
12931
|
}
|
|
12406
|
-
function logTelemetryWarning(message) {
|
|
12407
|
-
console.warn(message);
|
|
12932
|
+
function logTelemetryWarning(message, warn) {
|
|
12933
|
+
(warn ?? console.warn)(message);
|
|
12408
12934
|
}
|
|
12409
12935
|
var opencodeServerUrlLogged = false;
|
|
12410
|
-
function logOpencodeServerUrl(serverUrl) {
|
|
12936
|
+
function logOpencodeServerUrl(serverUrl, warn) {
|
|
12411
12937
|
if (opencodeServerUrlLogged) {
|
|
12412
12938
|
return;
|
|
12413
12939
|
}
|
|
12414
12940
|
opencodeServerUrlLogged = true;
|
|
12415
|
-
console.warn(`[openteam] plugin initialisation ctx.serverUrl: ${serverUrl.toString()}`);
|
|
12941
|
+
(warn ?? console.warn)(`[openteam] plugin initialisation ctx.serverUrl: ${serverUrl.toString()}`);
|
|
12416
12942
|
}
|
|
12417
12943
|
var MAX_LOGGED_OPENCODE_VERSION_FAILURE_CAUSES = 64;
|
|
12418
12944
|
function createOpencodeVersionReadFailureLogger(maxCauses = MAX_LOGGED_OPENCODE_VERSION_FAILURE_CAUSES, warn) {
|
|
@@ -12434,26 +12960,25 @@ function createOpencodeVersionReadFailureLogger(maxCauses = MAX_LOGGED_OPENCODE_
|
|
|
12434
12960
|
(warn ?? console.warn)(`[openteam] opencode version read failed: ${message}`);
|
|
12435
12961
|
};
|
|
12436
12962
|
}
|
|
12437
|
-
|
|
12438
|
-
function warnNoRuntimeConfigFound(paths, optionsOnly, localModel) {
|
|
12963
|
+
function warnNoRuntimeConfigFound(paths, optionsOnly, localModel, warn) {
|
|
12439
12964
|
const searched = paths.join(", ");
|
|
12440
12965
|
const modelRef = localModel !== null ? `\`${localModel.providerID}/${localModel.modelID}\`` : "none (no local runtime configured)";
|
|
12441
12966
|
if (optionsOnly) {
|
|
12442
|
-
console.warn(`[openteam] config file not found (searched: ${searched}); ` + `routing is configured by plugin options only — local runtimes declared ` + `in ${paths[0] ?? searched} are not loaded. ` + `Local model in use: ${modelRef}. ` + `Create ${DEFAULT_CONFIG_PATH} to persist your local runtime configuration.`);
|
|
12967
|
+
(warn ?? console.warn)(`[openteam] config file not found (searched: ${searched}); ` + `routing is configured by plugin options only — local runtimes declared ` + `in ${paths[0] ?? searched} are not loaded. ` + `Local model in use: ${modelRef}. ` + `Create ${DEFAULT_CONFIG_PATH} to persist your local runtime configuration.`);
|
|
12443
12968
|
} else {
|
|
12444
|
-
console.warn(`[openteam] no config file found at ${searched}. ` + `Routing uses schema defaults — local model: ${modelRef}. ` + `Privacy-sensitive tasks under forceLocalOnSensitive will be routed to frontier ` + `(no local runtime configured). Run \`openteam setup\` or create ${DEFAULT_CONFIG_PATH}.`);
|
|
12969
|
+
(warn ?? console.warn)(`[openteam] no config file found at ${searched}. ` + `Routing uses schema defaults — local model: ${modelRef}. ` + `Privacy-sensitive tasks under forceLocalOnSensitive will be routed to frontier ` + `(no local runtime configured). Run \`openteam setup\` or create ${DEFAULT_CONFIG_PATH}.`);
|
|
12445
12970
|
}
|
|
12446
12971
|
}
|
|
12447
12972
|
function warnInertPrivacySetting(paths, warn) {
|
|
12448
12973
|
(warn ?? console.warn)(`[openteam] privacyMode is "forceLocalOnSensitive" but no local runtime is configured ` + `(searched: ${paths.join(", ")}). ` + `Privacy-sensitive tasks will be routed to frontier and labelled "frontier" in telemetry ` + `(rationale: "privacy-force-local-none-configured"). ` + `To keep sensitive prompts local, configure a local runtime in ` + `${paths[0] ?? DEFAULT_CONFIG_PATH} or use the alwaysLocal override per request.`);
|
|
12449
12974
|
}
|
|
12450
|
-
function warnSoakEvidenceRejections(result) {
|
|
12975
|
+
function warnSoakEvidenceRejections(result, warn) {
|
|
12451
12976
|
if (result.rejectedLines === 0) {
|
|
12452
12977
|
return;
|
|
12453
12978
|
}
|
|
12454
|
-
console.warn(`[openteam] soak evidence reader rejected ${result.rejectedLines} observation line(s); cutover gate evidence may be incomplete.`);
|
|
12979
|
+
(warn ?? console.warn)(`[openteam] soak evidence reader rejected ${result.rejectedLines} observation line(s); cutover gate evidence may be incomplete.`);
|
|
12455
12980
|
for (const rejection of result.rejections) {
|
|
12456
|
-
console.warn(`[openteam] • ${rejection.file}:${rejection.lineNumber}: ${rejection.detail}`);
|
|
12981
|
+
(warn ?? console.warn)(`[openteam] • ${rejection.file}:${rejection.lineNumber}: ${rejection.detail}`);
|
|
12457
12982
|
}
|
|
12458
12983
|
}
|
|
12459
12984
|
var SHADOW_CERT_PATH = "artifacts/graph-shadow-certificate.json";
|
|
@@ -12630,7 +13155,7 @@ function createLazyOpencodeVersionReader(serverUrl, options = {}) {
|
|
|
12630
13155
|
}
|
|
12631
13156
|
};
|
|
12632
13157
|
}
|
|
12633
|
-
async function buildGateInput(config, storage, opencodeVersion) {
|
|
13158
|
+
async function buildGateInput(config, storage, opencodeVersion, warn) {
|
|
12634
13159
|
const shadow = await readAndParseCertificate(storage, SHADOW_CERT_PATH, (raw) => parseShadowCertificate(raw, opencodeVersion), (cert) => shadowCertificateBindingDigest(cert.evidence));
|
|
12635
13160
|
const shadowDigest = shadow.status === "valid" ? shadow.digest : "0".repeat(64);
|
|
12636
13161
|
const release = await readAndParseCertificate(storage, RELEASE_CERT_PATH, (raw) => parseReleaseCertificate(raw, opencodeVersion, shadowDigest), (cert) => cert.digest);
|
|
@@ -12638,7 +13163,7 @@ async function buildGateInput(config, storage, opencodeVersion) {
|
|
|
12638
13163
|
try {
|
|
12639
13164
|
const recorderPorts = recorderPortsFromStorage(storage);
|
|
12640
13165
|
const chainRead = await readAllChains(recorderPorts, SOAK_EVIDENCE_DIR);
|
|
12641
|
-
warnSoakEvidenceRejections(chainRead);
|
|
13166
|
+
warnSoakEvidenceRejections(chainRead, warn);
|
|
12642
13167
|
if (chainRead.chains.length > 0) {
|
|
12643
13168
|
soakLedgerDigest = canonicalLedger(chainRead.chains);
|
|
12644
13169
|
}
|
|
@@ -12667,8 +13192,8 @@ function recorderPortsFromStorage(storage) {
|
|
|
12667
13192
|
id: { randomUUID: () => crypto.randomUUID() }
|
|
12668
13193
|
};
|
|
12669
13194
|
}
|
|
12670
|
-
async function buildMemoryInjectorFromConfig(config, storage) {
|
|
12671
|
-
const runtime = await buildMemoryRuntimeFromConfig(config, { storage }, { requireInjectionEnabled: true });
|
|
13195
|
+
async function buildMemoryInjectorFromConfig(config, storage, warn) {
|
|
13196
|
+
const runtime = await buildMemoryRuntimeFromConfig(config, { storage, ...warn !== undefined ? { warn } : {} }, { requireInjectionEnabled: true });
|
|
12672
13197
|
if (runtime === undefined) {
|
|
12673
13198
|
return;
|
|
12674
13199
|
}
|
|
@@ -12685,8 +13210,10 @@ async function buildMemoryInjectorFromConfig(config, storage) {
|
|
|
12685
13210
|
}
|
|
12686
13211
|
});
|
|
12687
13212
|
}
|
|
12688
|
-
function createEventSink(rawOptions, storage = createFsStorageProvider(process.cwd())) {
|
|
13213
|
+
function createEventSink(rawOptions, storage = createFsStorageProvider(process.cwd()), loggers = {}) {
|
|
12689
13214
|
const options = telemetryOptions(rawOptions);
|
|
13215
|
+
const onError = loggers.onError !== undefined ? loggers.onError : (error) => logTelemetryError(error);
|
|
13216
|
+
const onWarn = loggers.onWarn !== undefined ? loggers.onWarn : (message) => logTelemetryWarning(message);
|
|
12690
13217
|
const { sink } = createTelemetryEventSink({
|
|
12691
13218
|
enabled: options.enabled,
|
|
12692
13219
|
otel: OtelBackendConfigSchema.parse({
|
|
@@ -12697,13 +13224,13 @@ function createEventSink(rawOptions, storage = createFsStorageProvider(process.c
|
|
|
12697
13224
|
eventLog: {
|
|
12698
13225
|
dir: options.sessionsDir,
|
|
12699
13226
|
storage,
|
|
12700
|
-
onError
|
|
13227
|
+
onError
|
|
12701
13228
|
},
|
|
12702
13229
|
otel: {
|
|
12703
13230
|
env: (name) => process.env[name],
|
|
12704
13231
|
buildExporter: createOtlpSpanExporter,
|
|
12705
|
-
onError
|
|
12706
|
-
onWarn
|
|
13232
|
+
onError,
|
|
13233
|
+
onWarn
|
|
12707
13234
|
}
|
|
12708
13235
|
});
|
|
12709
13236
|
return sink;
|
|
@@ -12721,6 +13248,18 @@ async function readCliConfigFromStorage(storage, path4, resolution) {
|
|
|
12721
13248
|
}
|
|
12722
13249
|
return content;
|
|
12723
13250
|
}
|
|
13251
|
+
async function readStoredRosterForDoctor(storage) {
|
|
13252
|
+
const content = await storage.read(OPENTEAM_ROSTER_PATH);
|
|
13253
|
+
if (content === undefined) {
|
|
13254
|
+
return;
|
|
13255
|
+
}
|
|
13256
|
+
try {
|
|
13257
|
+
return parseRoster(content);
|
|
13258
|
+
} catch {
|
|
13259
|
+
return content;
|
|
13260
|
+
}
|
|
13261
|
+
}
|
|
13262
|
+
var auditStoredRosterForDoctor = (roster, configuredRoles) => typeof roster === "string" ? auditRoster(roster, configuredRoles) : auditRoster(roster, configuredRoles);
|
|
12724
13263
|
function createCliDeps(config, registry, telemetryPath, sessionsDir = DEFAULT_SESSIONS_DIR, storage = createFsStorageProvider(process.cwd()), fallbackSource = "options", rawOptions) {
|
|
12725
13264
|
return {
|
|
12726
13265
|
loadConfig: async (path4, resolution = { explicit: false }) => {
|
|
@@ -12783,6 +13322,9 @@ function createCliDeps(config, registry, telemetryPath, sessionsDir = DEFAULT_SE
|
|
|
12783
13322
|
storage,
|
|
12784
13323
|
legacyTelemetryPath: path4
|
|
12785
13324
|
}),
|
|
13325
|
+
readDiagnostics: () => readDiagnosticEvents(sessionsDir, {
|
|
13326
|
+
storage
|
|
13327
|
+
}),
|
|
12786
13328
|
readGraphSnapshot: createGraphStatusSnapshotReader({
|
|
12787
13329
|
storage,
|
|
12788
13330
|
fallbackConfig: config,
|
|
@@ -12801,6 +13343,8 @@ function createCliDeps(config, registry, telemetryPath, sessionsDir = DEFAULT_SE
|
|
|
12801
13343
|
agentDir: dirname4(ORCHESTRATOR_AGENT_PATH),
|
|
12802
13344
|
cachePort: realCacheAdapter,
|
|
12803
13345
|
purge: createPluginPurgeRuntime(sessionsDir),
|
|
13346
|
+
loadRosterForDoctor: () => readStoredRosterForDoctor(storage),
|
|
13347
|
+
auditRoster: auditStoredRosterForDoctor,
|
|
12804
13348
|
version: PACKAGE_VERSION
|
|
12805
13349
|
};
|
|
12806
13350
|
}
|
|
@@ -12876,7 +13420,13 @@ function createLoopTool(deps) {
|
|
|
12876
13420
|
});
|
|
12877
13421
|
}
|
|
12878
13422
|
var server = async (ctx, rawOptions) => {
|
|
12879
|
-
|
|
13423
|
+
const now = () => Date.now();
|
|
13424
|
+
const diagnostics = createPluginDiagnosticChannel({
|
|
13425
|
+
client: ctx.client,
|
|
13426
|
+
directory: ctx.directory,
|
|
13427
|
+
now
|
|
13428
|
+
});
|
|
13429
|
+
logOpencodeServerUrl(ctx.serverUrl, diagnostics.info("opencode-server-url", { persist: false, toast: false }));
|
|
12880
13430
|
const storage = createFsStorageProvider(ctx.directory || ctx.worktree || process.cwd());
|
|
12881
13431
|
const runtimeConfig = await loadRuntimeOpenTeamConfig(storage, rawOptions);
|
|
12882
13432
|
const config = runtimeConfig.config;
|
|
@@ -12888,13 +13438,23 @@ var server = async (ctx, rawOptions) => {
|
|
|
12888
13438
|
const cache = createAvailabilityCache({
|
|
12889
13439
|
config,
|
|
12890
13440
|
registry,
|
|
12891
|
-
onRefreshError: logAvailabilityRefreshError
|
|
13441
|
+
onRefreshError: (error) => logAvailabilityRefreshError(error, diagnostics.warn("availability-refresh-failed", { toast: false }))
|
|
12892
13442
|
});
|
|
12893
13443
|
cache.refresh();
|
|
12894
13444
|
const telemetryPath = telemetryOptions(rawOptions).path;
|
|
12895
13445
|
const sessionsDir = telemetryOptions(rawOptions).sessionsDir;
|
|
12896
|
-
const sink = createEventSink(rawOptions, storage
|
|
12897
|
-
|
|
13446
|
+
const sink = createEventSink(rawOptions, storage, {
|
|
13447
|
+
onError: (error) => logTelemetryError(error, diagnostics.warn("telemetry-write-failed", {
|
|
13448
|
+
persist: false,
|
|
13449
|
+
toast: false
|
|
13450
|
+
})),
|
|
13451
|
+
onWarn: (message) => logTelemetryWarning(message, diagnostics.warn("telemetry-warning", {
|
|
13452
|
+
persist: false,
|
|
13453
|
+
toast: false
|
|
13454
|
+
}))
|
|
13455
|
+
});
|
|
13456
|
+
diagnostics.setEventSink(sink);
|
|
13457
|
+
const injectMemory = await buildMemoryInjectorFromConfig(config, storage, diagnostics.warn("memory-runtime-warning", { toast: false }));
|
|
12898
13458
|
const sessionTracker = createSessionTracker();
|
|
12899
13459
|
const hooks = createHooks(config, cache.get, injectMemory === undefined ? {
|
|
12900
13460
|
sink,
|
|
@@ -12906,7 +13466,6 @@ var server = async (ctx, rawOptions) => {
|
|
|
12906
13466
|
});
|
|
12907
13467
|
const cliDeps = createCliDeps(config, registry, telemetryPath, sessionsDir, storage, runtimeConfig.source, rawOptions);
|
|
12908
13468
|
const toolcalls = createToolcallTracker({ sink });
|
|
12909
|
-
const now = () => Date.now();
|
|
12910
13469
|
const announcedSessions = new Set;
|
|
12911
13470
|
const announceEndpoint = (sessionID) => {
|
|
12912
13471
|
if (sessionID === undefined || announcedSessions.has(sessionID)) {
|
|
@@ -12923,24 +13482,25 @@ var server = async (ctx, rawOptions) => {
|
|
|
12923
13482
|
};
|
|
12924
13483
|
let graphConfig = resolveGraphConfig(config, runtimeConfig.source);
|
|
12925
13484
|
if (!runtimeConfig.fileFound) {
|
|
12926
|
-
warnNoRuntimeConfigFound(runtimeConfig.searchedPaths, runtimeConfig.optionsFound, localDefault(config));
|
|
13485
|
+
warnNoRuntimeConfigFound(runtimeConfig.searchedPaths, runtimeConfig.optionsFound, localDefault(config), diagnostics.warn("config-missing"));
|
|
12927
13486
|
}
|
|
12928
13487
|
if (config.privacyMode === "forceLocalOnSensitive" && localDefault(config) === null) {
|
|
12929
|
-
warnInertPrivacySetting(runtimeConfig.searchedPaths);
|
|
13488
|
+
warnInertPrivacySetting(runtimeConfig.searchedPaths, diagnostics.warn("privacy-inert"));
|
|
12930
13489
|
}
|
|
12931
13490
|
const getOpencodeVersion = createLazyOpencodeVersionReader(ctx.serverUrl, {
|
|
12932
13491
|
client: ctx.client,
|
|
12933
|
-
onFailure:
|
|
13492
|
+
onFailure: createOpencodeVersionReadFailureLogger(MAX_LOGGED_OPENCODE_VERSION_FAILURE_CAUSES, diagnostics.warn("opencode-version-read-failed", { toast: false }))
|
|
12934
13493
|
});
|
|
12935
13494
|
if (isGateApplicable(graphConfig.effectiveMode)) {
|
|
12936
13495
|
const opencodeVersion = await getOpencodeVersion();
|
|
12937
|
-
const gateInput = await buildGateInput(config, storage, opencodeVersion);
|
|
13496
|
+
const gateInput = await buildGateInput(config, storage, opencodeVersion, diagnostics.warn("soak-evidence-rejected", { toast: false }));
|
|
12938
13497
|
const gateResult = evaluateGraphGate(gateInput);
|
|
12939
13498
|
graphConfig = applyGraphGate(graphConfig, gateResult);
|
|
12940
13499
|
if (graphConfig.gateDenied === true && graphConfig.gateViolations !== undefined) {
|
|
12941
|
-
|
|
13500
|
+
const graphGateWarn = diagnostics.warn("graph-gate-denied");
|
|
13501
|
+
graphGateWarn("[openteam] ⛔ cutover gate denied active mode — degrading to off");
|
|
12942
13502
|
for (const v of graphConfig.gateViolations) {
|
|
12943
|
-
|
|
13503
|
+
graphGateWarn(`[openteam] • ${v.code}: ${v.detail}`);
|
|
12944
13504
|
}
|
|
12945
13505
|
}
|
|
12946
13506
|
}
|
|
@@ -13002,14 +13562,16 @@ var server = async (ctx, rawOptions) => {
|
|
|
13002
13562
|
observeLegacyExecution: graphSurface.observeLegacyExecution,
|
|
13003
13563
|
localRuntimeReachable,
|
|
13004
13564
|
reachableRuntimeIds: probeReachableRuntimeIds,
|
|
13005
|
-
runtimeLimiter
|
|
13565
|
+
runtimeLimiter,
|
|
13566
|
+
warn: diagnostics.warn("unknown-role")
|
|
13006
13567
|
});
|
|
13007
13568
|
const memoryTool = createMemoryTool({
|
|
13008
13569
|
config,
|
|
13009
13570
|
storage,
|
|
13010
13571
|
now,
|
|
13011
13572
|
localRuntimeReachable,
|
|
13012
|
-
fetch: globalThis.fetch
|
|
13573
|
+
fetch: globalThis.fetch,
|
|
13574
|
+
warn: diagnostics.warn("memory-extraction-warning", { toast: false })
|
|
13013
13575
|
});
|
|
13014
13576
|
const loopTool = createLoopTool({
|
|
13015
13577
|
storage,
|
|
@@ -13019,6 +13581,7 @@ var server = async (ctx, rawOptions) => {
|
|
|
13019
13581
|
sink,
|
|
13020
13582
|
now,
|
|
13021
13583
|
newDecisionID: () => crypto.randomUUID(),
|
|
13584
|
+
warn: diagnostics.warn("unknown-role"),
|
|
13022
13585
|
localRuntimeReachable,
|
|
13023
13586
|
reachableRuntimeIds: probeReachableRuntimeIds,
|
|
13024
13587
|
runtimeLimiter
|
|
@@ -13075,7 +13638,6 @@ export {
|
|
|
13075
13638
|
readOpencodeVersion,
|
|
13076
13639
|
logTelemetryWarning,
|
|
13077
13640
|
logTelemetryError,
|
|
13078
|
-
logOpencodeVersionReadFailure,
|
|
13079
13641
|
logOpencodeServerUrl,
|
|
13080
13642
|
logAvailabilityRefreshError,
|
|
13081
13643
|
isMissingFile,
|