@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/cli.js
CHANGED
|
@@ -104,8 +104,8 @@ var AgentRoleProfileSchema = z.object({
|
|
|
104
104
|
localRuntimes: z.array(z.enum(["ollama", "lmstudio", "foundry-local", "lemonade"])).optional()
|
|
105
105
|
}).strict();
|
|
106
106
|
var TEAM_ROLES = {
|
|
107
|
-
|
|
108
|
-
roleID: "
|
|
107
|
+
architect: {
|
|
108
|
+
roleID: "architect",
|
|
109
109
|
opencodeAgent: "architect",
|
|
110
110
|
defaultTier: "hard",
|
|
111
111
|
minReasoningTier: 5,
|
|
@@ -114,8 +114,8 @@ var TEAM_ROLES = {
|
|
|
114
114
|
preferredFrontierModels: ["auto"],
|
|
115
115
|
localFirst: false
|
|
116
116
|
},
|
|
117
|
-
|
|
118
|
-
roleID: "
|
|
117
|
+
integration: {
|
|
118
|
+
roleID: "integration",
|
|
119
119
|
opencodeAgent: "integration",
|
|
120
120
|
defaultTier: "moderate",
|
|
121
121
|
minReasoningTier: 4,
|
|
@@ -124,8 +124,8 @@ var TEAM_ROLES = {
|
|
|
124
124
|
preferredFrontierModels: ["auto"],
|
|
125
125
|
localFirst: false
|
|
126
126
|
},
|
|
127
|
-
|
|
128
|
-
roleID: "
|
|
127
|
+
"local-runtime": {
|
|
128
|
+
roleID: "local-runtime",
|
|
129
129
|
opencodeAgent: "local-runtime",
|
|
130
130
|
defaultTier: "moderate",
|
|
131
131
|
minReasoningTier: 3,
|
|
@@ -134,8 +134,8 @@ var TEAM_ROLES = {
|
|
|
134
134
|
preferredFrontierModels: ["auto"],
|
|
135
135
|
localFirst: true
|
|
136
136
|
},
|
|
137
|
-
|
|
138
|
-
roleID: "
|
|
137
|
+
"routing-cost": {
|
|
138
|
+
roleID: "routing-cost",
|
|
139
139
|
opencodeAgent: "routing-cost",
|
|
140
140
|
defaultTier: "moderate",
|
|
141
141
|
minReasoningTier: 3,
|
|
@@ -177,8 +177,8 @@ var TEAM_ROLES = {
|
|
|
177
177
|
localFirst: true,
|
|
178
178
|
requiresLocalRuntime: true
|
|
179
179
|
},
|
|
180
|
-
|
|
181
|
-
roleID: "
|
|
180
|
+
tester: {
|
|
181
|
+
roleID: "tester",
|
|
182
182
|
opencodeAgent: "tester",
|
|
183
183
|
defaultTier: "simple",
|
|
184
184
|
minReasoningTier: 2,
|
|
@@ -198,8 +198,35 @@ var TEAM_ROLES = {
|
|
|
198
198
|
localFirst: true
|
|
199
199
|
}
|
|
200
200
|
};
|
|
201
|
+
var LEGACY_TEAM_ROLE_ALIASES = {
|
|
202
|
+
rusty: "architect",
|
|
203
|
+
livingston: "integration",
|
|
204
|
+
yen: "local-runtime",
|
|
205
|
+
basher: "routing-cost",
|
|
206
|
+
linus: "tester"
|
|
207
|
+
};
|
|
208
|
+
var KNOWN_NON_WORKER_ROLE_IDS = ["orchestrator", "openteam"];
|
|
209
|
+
function isKnownNonWorkerRole(roleID) {
|
|
210
|
+
return KNOWN_NON_WORKER_ROLE_IDS.includes(roleID);
|
|
211
|
+
}
|
|
212
|
+
function getConfiguredRoleProfileByAlias(roleID, roles) {
|
|
213
|
+
if (roles === undefined) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
for (const key of Object.keys(roles).sort()) {
|
|
217
|
+
const profile = roles[key];
|
|
218
|
+
if (profile?.opencodeAgent === roleID) {
|
|
219
|
+
return profile;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
function getLegacyTeamRoleProfile(roleID) {
|
|
225
|
+
const canonicalRoleID = LEGACY_TEAM_ROLE_ALIASES[roleID];
|
|
226
|
+
return canonicalRoleID === undefined ? undefined : TEAM_ROLES[canonicalRoleID];
|
|
227
|
+
}
|
|
201
228
|
function getRoleProfile(roleID, configuredRoles) {
|
|
202
|
-
return configuredRoles?.[roleID] ?? TEAM_ROLES[roleID];
|
|
229
|
+
return configuredRoles?.[roleID] ?? TEAM_ROLES[roleID] ?? getConfiguredRoleProfileByAlias(roleID, configuredRoles) ?? getLegacyTeamRoleProfile(roleID);
|
|
203
230
|
}
|
|
204
231
|
function synthesiseFallbackProfile(roleID) {
|
|
205
232
|
return {
|
|
@@ -967,6 +994,50 @@ var ModelCapabilityProfileSchema = z3.object({
|
|
|
967
994
|
availability: z3.enum(["available", "degraded", "unavailable"])
|
|
968
995
|
}).strict();
|
|
969
996
|
|
|
997
|
+
// src/telemetry/diagnostics.ts
|
|
998
|
+
var DIAGNOSTIC_CODES = [
|
|
999
|
+
"availability-refresh-failed",
|
|
1000
|
+
"config-missing",
|
|
1001
|
+
"graph-gate-denied",
|
|
1002
|
+
"memory-extraction-warning",
|
|
1003
|
+
"memory-runtime-warning",
|
|
1004
|
+
"opencode-server-url",
|
|
1005
|
+
"opencode-version-read-failed",
|
|
1006
|
+
"privacy-inert",
|
|
1007
|
+
"soak-evidence-rejected",
|
|
1008
|
+
"telemetry-warning",
|
|
1009
|
+
"telemetry-write-failed",
|
|
1010
|
+
"unknown-role"
|
|
1011
|
+
];
|
|
1012
|
+
function diagnosticDescription(code) {
|
|
1013
|
+
switch (code) {
|
|
1014
|
+
case "availability-refresh-failed":
|
|
1015
|
+
return "Local runtime availability refresh failed; routing will continue with the previous or empty availability snapshot.";
|
|
1016
|
+
case "config-missing":
|
|
1017
|
+
return "openteam did not find its runtime config file; defaults or plugin options were used.";
|
|
1018
|
+
case "graph-gate-denied":
|
|
1019
|
+
return "The graph cutover gate denied active mode and openteam degraded graph mode to off.";
|
|
1020
|
+
case "memory-extraction-warning":
|
|
1021
|
+
return "Semantic memory extraction reported a non-fatal local-runtime warning; no raw conversation content was recorded.";
|
|
1022
|
+
case "memory-runtime-warning":
|
|
1023
|
+
return "Semantic memory runtime setup reported a non-fatal warning; memory recall may be incomplete.";
|
|
1024
|
+
case "opencode-server-url":
|
|
1025
|
+
return "openteam observed the opencode server URL during plugin initialization.";
|
|
1026
|
+
case "opencode-version-read-failed":
|
|
1027
|
+
return "openteam could not read the live opencode version; graph gates fail closed when this happens.";
|
|
1028
|
+
case "privacy-inert":
|
|
1029
|
+
return "forceLocalOnSensitive is configured but no local runtime is configured, so the setting cannot keep sensitive prompts local.";
|
|
1030
|
+
case "soak-evidence-rejected":
|
|
1031
|
+
return "Graph soak evidence contained rejected observation lines; cutover gate evidence may be incomplete.";
|
|
1032
|
+
case "telemetry-warning":
|
|
1033
|
+
return "An optional telemetry backend warning occurred; local JSONL telemetry remains the fallback when enabled.";
|
|
1034
|
+
case "telemetry-write-failed":
|
|
1035
|
+
return "Writing an openteam telemetry event failed; plugin execution continued.";
|
|
1036
|
+
case "unknown-role":
|
|
1037
|
+
return "An unknown role ID was encountered; openteam used a neutral frontier-eligible fallback. If intentional, define the role in orchestrator.roles.";
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
970
1041
|
// src/telemetry/events.ts
|
|
971
1042
|
var EVENT_SCHEMA_VERSION = 1;
|
|
972
1043
|
var EventBaseSchema = z4.object({
|
|
@@ -1060,6 +1131,13 @@ var ShadowDiagnosticEventSchema = EventBaseSchema.extend({
|
|
|
1060
1131
|
"unknown"
|
|
1061
1132
|
])
|
|
1062
1133
|
});
|
|
1134
|
+
var DiagnosticLevelSchema = z4.enum(["debug", "info", "warn", "error"]);
|
|
1135
|
+
var DiagnosticCodeSchema = z4.enum(DIAGNOSTIC_CODES);
|
|
1136
|
+
var DiagnosticEventSchema = EventBaseSchema.extend({
|
|
1137
|
+
type: z4.literal("diagnostic"),
|
|
1138
|
+
level: DiagnosticLevelSchema,
|
|
1139
|
+
code: DiagnosticCodeSchema
|
|
1140
|
+
});
|
|
1063
1141
|
var OpenTeamEventSchema = z4.discriminatedUnion("type", [
|
|
1064
1142
|
RouteEventSchema,
|
|
1065
1143
|
MessageEventSchema,
|
|
@@ -1068,7 +1146,8 @@ var OpenTeamEventSchema = z4.discriminatedUnion("type", [
|
|
|
1068
1146
|
DecisionEventSchema,
|
|
1069
1147
|
ActivityEventSchema,
|
|
1070
1148
|
SessionEndpointEventSchema,
|
|
1071
|
-
ShadowDiagnosticEventSchema
|
|
1149
|
+
ShadowDiagnosticEventSchema,
|
|
1150
|
+
DiagnosticEventSchema
|
|
1072
1151
|
]);
|
|
1073
1152
|
|
|
1074
1153
|
// src/storage/fsStorageProvider.ts
|
|
@@ -1328,6 +1407,9 @@ async function readRouteCostRecords(dir, deps) {
|
|
|
1328
1407
|
}
|
|
1329
1408
|
return records;
|
|
1330
1409
|
}
|
|
1410
|
+
async function readDiagnosticEvents(dir, deps) {
|
|
1411
|
+
return (await readSessionEvents(dir, deps)).filter((event) => event.type === "diagnostic");
|
|
1412
|
+
}
|
|
1331
1413
|
async function readSessionEvents(dir, deps) {
|
|
1332
1414
|
const files = (await deps.storage.list(dir)).filter((file) => file.endsWith(".jsonl"));
|
|
1333
1415
|
const perFile = await Promise.all(files.map(async (file) => {
|
|
@@ -1368,14 +1450,21 @@ function hashPrompt(prompt) {
|
|
|
1368
1450
|
|
|
1369
1451
|
// src/orchestrator/permissions.ts
|
|
1370
1452
|
var ROLE_PERMISSIONS = {
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1453
|
+
architect: [
|
|
1454
|
+
"read",
|
|
1455
|
+
"edit",
|
|
1456
|
+
"multiFileEdit",
|
|
1457
|
+
"destructive",
|
|
1458
|
+
"network",
|
|
1459
|
+
"shell"
|
|
1460
|
+
],
|
|
1461
|
+
integration: ["read", "edit", "multiFileEdit", "network", "shell"],
|
|
1462
|
+
"local-runtime": ["read", "edit", "network", "shell"],
|
|
1463
|
+
"routing-cost": ["read", "edit"],
|
|
1375
1464
|
scribe: ["read", "edit"],
|
|
1376
1465
|
ralph: ["read", "edit", "shell"],
|
|
1377
1466
|
guardian: ["read"],
|
|
1378
|
-
|
|
1467
|
+
tester: ["read", "edit", "shell"],
|
|
1379
1468
|
reviewer: ["read"]
|
|
1380
1469
|
};
|
|
1381
1470
|
var elevatedTier = {
|
|
@@ -1671,6 +1760,8 @@ async function runSubsession(client, req, deps = {}) {
|
|
|
1671
1760
|
|
|
1672
1761
|
// src/orchestrator/coordinator.ts
|
|
1673
1762
|
function warnUnknownRole(roleID, warn, warnedRoles) {
|
|
1763
|
+
if (isKnownNonWorkerRole(roleID))
|
|
1764
|
+
return;
|
|
1674
1765
|
if (warnedRoles !== undefined) {
|
|
1675
1766
|
if (warnedRoles.has(roleID))
|
|
1676
1767
|
return;
|
|
@@ -1749,7 +1840,7 @@ function routingInput(input, role) {
|
|
|
1749
1840
|
...input.task,
|
|
1750
1841
|
prompt: input.task.prompt ?? input.prompt
|
|
1751
1842
|
}).tier;
|
|
1752
|
-
const permissionTier = elevateTierForPermissions(classifiedTier, permissionsFor(
|
|
1843
|
+
const permissionTier = elevateTierForPermissions(classifiedTier, permissionsFor(role.roleID));
|
|
1753
1844
|
const tier = permissionTier;
|
|
1754
1845
|
const requirement = roleToRequirement(role, input.task, tier);
|
|
1755
1846
|
const task = roleAdjustedTask(role, input, requirement);
|
|
@@ -8557,22 +8648,70 @@ function buildOrchestratorAgent(frontier, options = {}) {
|
|
|
8557
8648
|
"1. When you create the team for the first time, **choose a thematic universe**",
|
|
8558
8649
|
" (e.g. a film, series, comic or mythology). If the user has a preference,",
|
|
8559
8650
|
" ask them for it; if not, propose one and move forward without blocking work.",
|
|
8560
|
-
"2. Give each subagent the **name of a character** from that universe;
|
|
8561
|
-
" file name (`.opencode/agent/<
|
|
8651
|
+
"2. Give each subagent an `agentName`: the **name of a character** from that universe;",
|
|
8652
|
+
" the file name (`.opencode/agent/<agentName>.md`) is its alias for",
|
|
8562
8653
|
" `@mention`, and its role is clear from the `description`.",
|
|
8563
|
-
|
|
8564
|
-
"
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
"
|
|
8654
|
+
"3. Give each entry a `roleID`: the stable functional routing key. `roleID` is",
|
|
8655
|
+
" **not** the themed name. Worked example: C-3PO as the scribe is",
|
|
8656
|
+
' `{ "roleID": "scribe", "agentName": "c3po" }`, never',
|
|
8657
|
+
' `{ "roleID": "c3po", "agentName": "c3po" }`.',
|
|
8658
|
+
"4. Prefer existing curated `roleID` keys when they fit:",
|
|
8659
|
+
" `architect`, `integration`, `tester`, `reviewer`, `local-runtime`,",
|
|
8660
|
+
" `routing-cost` (plus the guaranteed `orchestrator`, `guardian`,",
|
|
8661
|
+
" `scribe`, `ralph`). The roleID space is open: use free-form project",
|
|
8662
|
+
" roleIDs only when no curated role fits.",
|
|
8663
|
+
`5. **Register the cast** in \`${OPENTEAM_ROSTER_PATH}\` so the names persist`,
|
|
8664
|
+
" across sessions. This file goes **outside** `.opencode/agent/` (otherwise",
|
|
8665
|
+
" opencode would load it as a phantom agent). Always reuse the same cast;",
|
|
8666
|
+
" do not re-cast without reason.",
|
|
8667
|
+
"",
|
|
8668
|
+
"## Roster file format",
|
|
8669
|
+
"",
|
|
8670
|
+
"Write the roster as Markdown with exactly one fenced `json` block matching",
|
|
8671
|
+
"`{ universe, entries: [{ roleID, agentName }] }`. Do not write a table.",
|
|
8672
|
+
"Minimal parseable example:",
|
|
8673
|
+
"",
|
|
8674
|
+
"# openteam roster",
|
|
8675
|
+
"",
|
|
8676
|
+
"Universe: star-wars",
|
|
8677
|
+
"",
|
|
8678
|
+
"```json",
|
|
8679
|
+
"{",
|
|
8680
|
+
' "universe": "star-wars",',
|
|
8681
|
+
' "entries": [',
|
|
8682
|
+
" {",
|
|
8683
|
+
' "roleID": "orchestrator",',
|
|
8684
|
+
' "agentName": "openteam"',
|
|
8685
|
+
" },",
|
|
8686
|
+
" {",
|
|
8687
|
+
' "roleID": "guardian",',
|
|
8688
|
+
' "agentName": "leia"',
|
|
8689
|
+
" },",
|
|
8690
|
+
" {",
|
|
8691
|
+
' "roleID": "scribe",',
|
|
8692
|
+
' "agentName": "c3po"',
|
|
8693
|
+
" },",
|
|
8694
|
+
" {",
|
|
8695
|
+
' "roleID": "ralph",',
|
|
8696
|
+
' "agentName": "r2d2"',
|
|
8697
|
+
" },",
|
|
8698
|
+
" {",
|
|
8699
|
+
' "roleID": "tester",',
|
|
8700
|
+
' "agentName": "rex"',
|
|
8701
|
+
" }",
|
|
8702
|
+
" ]",
|
|
8703
|
+
"}",
|
|
8704
|
+
"```",
|
|
8568
8705
|
"",
|
|
8569
8706
|
"## How to create the team when it does not exist",
|
|
8570
8707
|
"",
|
|
8571
8708
|
"1. Analyze the goal and explore the repository (`read`/`glob`/`grep`).",
|
|
8572
8709
|
"2. Break the request into tasks and design the **minimal team** needed:",
|
|
8573
|
-
" define only the roles the work requires
|
|
8574
|
-
"
|
|
8575
|
-
"
|
|
8710
|
+
" define only the roles the work requires. Reuse curated roleIDs such as",
|
|
8711
|
+
" `architect`, `integration`, `reviewer`, `tester`, `local-runtime` and",
|
|
8712
|
+
" `routing-cost`; use custom roleIDs for uncovered project specialties.",
|
|
8713
|
+
" Do not invent roles you will not use.",
|
|
8714
|
+
"3. Create each subagent **on demand** by writing `.opencode/agent/<agentName>.md`:",
|
|
8576
8715
|
" - Frontmatter: `description` (required, include role and universe),",
|
|
8577
8716
|
" `mode: subagent`, `temperature`, `permission` with the minimum needed,",
|
|
8578
8717
|
" and `model` **optional**.",
|
|
@@ -8593,8 +8732,10 @@ function buildOrchestratorAgent(frontier, options = {}) {
|
|
|
8593
8732
|
"",
|
|
8594
8733
|
"## Standard team roles",
|
|
8595
8734
|
"",
|
|
8596
|
-
"
|
|
8597
|
-
"
|
|
8735
|
+
"Every roster MUST include these guaranteed canonical roleIDs exactly:",
|
|
8736
|
+
"`orchestrator`, `guardian`, `scribe`, and `ralph`. Map `orchestrator` to",
|
|
8737
|
+
"agentName `openteam` (the primary agent). Create the `guardian`, `scribe`,",
|
|
8738
|
+
"and `ralph` subagent files on demand with their themed `agentName` values:",
|
|
8598
8739
|
"",
|
|
8599
8740
|
"- **scribe** — the team's silent memory. Records decisions and",
|
|
8600
8741
|
" learnings in a shared log (`.opencode/openteam/decisions.md`) without",
|
|
@@ -9805,16 +9946,165 @@ var RosterSchema = z17.object({
|
|
|
9805
9946
|
universe: z17.string().min(1),
|
|
9806
9947
|
entries: z17.array(RosterEntrySchema)
|
|
9807
9948
|
}).strict();
|
|
9949
|
+
var ROSTER_JSON_FENCE = /```json\s*([\s\S]*?)```/;
|
|
9950
|
+
|
|
9951
|
+
class RosterParseError extends Error {
|
|
9952
|
+
code;
|
|
9953
|
+
constructor(code, message) {
|
|
9954
|
+
super(message);
|
|
9955
|
+
this.name = "RosterParseError";
|
|
9956
|
+
this.code = code;
|
|
9957
|
+
}
|
|
9958
|
+
}
|
|
9959
|
+
function parseRoster(text2) {
|
|
9960
|
+
const result = parseRosterResult(text2);
|
|
9961
|
+
if (!result.ok) {
|
|
9962
|
+
throw result.error;
|
|
9963
|
+
}
|
|
9964
|
+
return result.roster;
|
|
9965
|
+
}
|
|
9966
|
+
function parseRosterResult(text2) {
|
|
9967
|
+
const match = ROSTER_JSON_FENCE.exec(text2);
|
|
9968
|
+
if (match === null || match[1] === undefined) {
|
|
9969
|
+
return {
|
|
9970
|
+
ok: false,
|
|
9971
|
+
error: new RosterParseError("missing-json-block", "openteam roster: no JSON block found in roster file")
|
|
9972
|
+
};
|
|
9973
|
+
}
|
|
9974
|
+
let data;
|
|
9975
|
+
try {
|
|
9976
|
+
data = JSON.parse(match[1]);
|
|
9977
|
+
} catch (error) {
|
|
9978
|
+
return {
|
|
9979
|
+
ok: false,
|
|
9980
|
+
error: new RosterParseError("malformed-json", `openteam roster: malformed JSON in roster file: ${String(error)}`)
|
|
9981
|
+
};
|
|
9982
|
+
}
|
|
9983
|
+
const parsed = RosterSchema.safeParse(data);
|
|
9984
|
+
if (!parsed.success) {
|
|
9985
|
+
return {
|
|
9986
|
+
ok: false,
|
|
9987
|
+
error: new RosterParseError("invalid-roster-schema", `openteam roster: invalid roster schema: ${parsed.error.message}`)
|
|
9988
|
+
};
|
|
9989
|
+
}
|
|
9990
|
+
return { ok: true, roster: parsed.data };
|
|
9991
|
+
}
|
|
9808
9992
|
var GUARANTEED_ROLE_IDS = [
|
|
9809
9993
|
"orchestrator",
|
|
9810
9994
|
"guardian",
|
|
9811
9995
|
"scribe",
|
|
9812
9996
|
"ralph"
|
|
9813
9997
|
];
|
|
9998
|
+
var GUARANTEED_ROLE_DEFAULT_AGENT_NAMES = {
|
|
9999
|
+
orchestrator: "openteam",
|
|
10000
|
+
guardian: "guardian",
|
|
10001
|
+
scribe: "scribe",
|
|
10002
|
+
ralph: "ralph"
|
|
10003
|
+
};
|
|
10004
|
+
function defaultAgentNameForGuaranteedRole(roleID) {
|
|
10005
|
+
return GUARANTEED_ROLE_DEFAULT_AGENT_NAMES[roleID];
|
|
10006
|
+
}
|
|
10007
|
+
function isGuaranteedRoleID(roleID) {
|
|
10008
|
+
return GUARANTEED_ROLE_IDS.includes(roleID);
|
|
10009
|
+
}
|
|
9814
10010
|
function missingGuaranteedRoles(roster) {
|
|
9815
10011
|
const present = new Set(roster.entries.map((entry) => entry.roleID));
|
|
9816
10012
|
return GUARANTEED_ROLE_IDS.filter((roleID) => !present.has(roleID));
|
|
9817
10013
|
}
|
|
10014
|
+
function isRoleProfileLike(value) {
|
|
10015
|
+
return typeof value === "object" && value !== null && "roleID" in value && "defaultTier" in value;
|
|
10016
|
+
}
|
|
10017
|
+
function configuredRolesFromAuditInput(input) {
|
|
10018
|
+
if ("configuredRoles" in input && !isRoleProfileLike(input.configuredRoles)) {
|
|
10019
|
+
return input.configuredRoles;
|
|
10020
|
+
}
|
|
10021
|
+
return input;
|
|
10022
|
+
}
|
|
10023
|
+
function auditRoster(rosterOrText, configuredRolesOrOptions = {}) {
|
|
10024
|
+
const configuredRoles = configuredRolesFromAuditInput(configuredRolesOrOptions);
|
|
10025
|
+
if (typeof rosterOrText === "string") {
|
|
10026
|
+
const parsed = parseRosterResult(rosterOrText);
|
|
10027
|
+
if (!parsed.ok) {
|
|
10028
|
+
return {
|
|
10029
|
+
findings: [
|
|
10030
|
+
{
|
|
10031
|
+
kind: "unparseable-roster",
|
|
10032
|
+
roleID: "roster",
|
|
10033
|
+
code: parsed.error.code,
|
|
10034
|
+
message: parsed.error.message
|
|
10035
|
+
}
|
|
10036
|
+
],
|
|
10037
|
+
hasAllGuaranteedRoles: false,
|
|
10038
|
+
missingGuaranteedRoleIDs: [],
|
|
10039
|
+
unprofiledRoleEntries: [],
|
|
10040
|
+
misassignedGuaranteedRoleEntries: []
|
|
10041
|
+
};
|
|
10042
|
+
}
|
|
10043
|
+
return auditParsedRoster(parsed.roster, configuredRoles);
|
|
10044
|
+
}
|
|
10045
|
+
return auditParsedRoster(rosterOrText, configuredRoles);
|
|
10046
|
+
}
|
|
10047
|
+
function auditParsedRoster(roster, configuredRoles) {
|
|
10048
|
+
const missingGuaranteedRoleIDs = missingGuaranteedRoles(roster);
|
|
10049
|
+
const missing = new Set(missingGuaranteedRoleIDs);
|
|
10050
|
+
const findings = missingGuaranteedRoleIDs.map((roleID) => ({
|
|
10051
|
+
kind: "missing-guaranteed-role",
|
|
10052
|
+
roleID
|
|
10053
|
+
}));
|
|
10054
|
+
const unprofiledRoleEntries = [];
|
|
10055
|
+
const misassignedGuaranteedRoleEntries = [];
|
|
10056
|
+
roster.entries.forEach((entry) => {
|
|
10057
|
+
const profile = getRoleProfile(entry.roleID, configuredRoles);
|
|
10058
|
+
if (profile === undefined && !isKnownNonWorkerRole(entry.roleID)) {
|
|
10059
|
+
unprofiledRoleEntries.push({
|
|
10060
|
+
roleID: entry.roleID,
|
|
10061
|
+
agentName: entry.agentName,
|
|
10062
|
+
severity: "info"
|
|
10063
|
+
});
|
|
10064
|
+
findings.push({
|
|
10065
|
+
kind: "unresolved-role-profile",
|
|
10066
|
+
roleID: entry.roleID,
|
|
10067
|
+
agentName: entry.agentName
|
|
10068
|
+
});
|
|
10069
|
+
}
|
|
10070
|
+
if (isGuaranteedRoleID(entry.agentName) && entry.roleID !== entry.agentName && missing.has(entry.agentName)) {
|
|
10071
|
+
misassignedGuaranteedRoleEntries.push({
|
|
10072
|
+
roleID: entry.roleID,
|
|
10073
|
+
agentName: entry.agentName,
|
|
10074
|
+
missingRoleID: entry.agentName,
|
|
10075
|
+
reason: "agentNameMatchesMissingGuaranteedRole"
|
|
10076
|
+
});
|
|
10077
|
+
findings.push({
|
|
10078
|
+
kind: "non-canonical-role-id",
|
|
10079
|
+
roleID: entry.roleID,
|
|
10080
|
+
agentName: entry.agentName,
|
|
10081
|
+
canonicalRoleID: entry.agentName
|
|
10082
|
+
});
|
|
10083
|
+
return;
|
|
10084
|
+
}
|
|
10085
|
+
if (profile !== undefined && isGuaranteedRoleID(profile.roleID) && profile.roleID !== entry.roleID && missing.has(profile.roleID)) {
|
|
10086
|
+
misassignedGuaranteedRoleEntries.push({
|
|
10087
|
+
roleID: entry.roleID,
|
|
10088
|
+
agentName: entry.agentName,
|
|
10089
|
+
missingRoleID: profile.roleID,
|
|
10090
|
+
reason: "roleIDResolvesToMissingGuaranteedRole"
|
|
10091
|
+
});
|
|
10092
|
+
findings.push({
|
|
10093
|
+
kind: "non-canonical-role-id",
|
|
10094
|
+
roleID: entry.roleID,
|
|
10095
|
+
agentName: entry.agentName,
|
|
10096
|
+
canonicalRoleID: profile.roleID
|
|
10097
|
+
});
|
|
10098
|
+
}
|
|
10099
|
+
});
|
|
10100
|
+
return {
|
|
10101
|
+
findings,
|
|
10102
|
+
hasAllGuaranteedRoles: missingGuaranteedRoleIDs.length === 0,
|
|
10103
|
+
missingGuaranteedRoleIDs,
|
|
10104
|
+
unprofiledRoleEntries,
|
|
10105
|
+
misassignedGuaranteedRoleEntries
|
|
10106
|
+
};
|
|
10107
|
+
}
|
|
9818
10108
|
function enforceGuaranteedRoles(roster) {
|
|
9819
10109
|
const missing = missingGuaranteedRoles(roster);
|
|
9820
10110
|
if (missing.length === 0) {
|
|
@@ -9822,7 +10112,7 @@ function enforceGuaranteedRoles(roster) {
|
|
|
9822
10112
|
}
|
|
9823
10113
|
const added = missing.map((roleID) => ({
|
|
9824
10114
|
roleID,
|
|
9825
|
-
agentName: roleID
|
|
10115
|
+
agentName: defaultAgentNameForGuaranteedRole(roleID)
|
|
9826
10116
|
}));
|
|
9827
10117
|
return { universe: roster.universe, entries: [...roster.entries, ...added] };
|
|
9828
10118
|
}
|
|
@@ -10227,6 +10517,105 @@ function agentModelLine(diagnostic, nameWidth, searchedPaths) {
|
|
|
10227
10517
|
}
|
|
10228
10518
|
return ` ${mark} ${name} [${diagnostic.mode}] ${modelText2} ${provenance} ${parts.join(" · ")}`;
|
|
10229
10519
|
}
|
|
10520
|
+
function diagnosticMark(level) {
|
|
10521
|
+
switch (level) {
|
|
10522
|
+
case "error":
|
|
10523
|
+
return "✗";
|
|
10524
|
+
case "warn":
|
|
10525
|
+
return "⚠";
|
|
10526
|
+
default:
|
|
10527
|
+
return "·";
|
|
10528
|
+
}
|
|
10529
|
+
}
|
|
10530
|
+
function diagnosticsSection(diagnostics) {
|
|
10531
|
+
const notable = diagnostics.filter((diagnostic) => diagnostic.level === "warn" || diagnostic.level === "error");
|
|
10532
|
+
if (notable.length === 0) {
|
|
10533
|
+
return [];
|
|
10534
|
+
}
|
|
10535
|
+
const recent = notable.slice(-5);
|
|
10536
|
+
const lines = [
|
|
10537
|
+
` plugin diagnostics: ${notable.length} warning/error event(s)`
|
|
10538
|
+
];
|
|
10539
|
+
for (const diagnostic of recent) {
|
|
10540
|
+
lines.push(` ${diagnosticMark(diagnostic.level)} [${diagnostic.code}] ${diagnosticDescription(diagnostic.code)}`);
|
|
10541
|
+
}
|
|
10542
|
+
if (notable.length > recent.length) {
|
|
10543
|
+
lines.push(` … ${notable.length - recent.length} older diagnostic event(s) omitted`);
|
|
10544
|
+
}
|
|
10545
|
+
lines.push(" note: plugin diagnostics are bounded classes recorded instead of writing to the opencode TUI console.");
|
|
10546
|
+
return lines;
|
|
10547
|
+
}
|
|
10548
|
+
function rosterEntryText(entry) {
|
|
10549
|
+
return entry.agentName === undefined ? `"${entry.roleID}"` : `"${entry.roleID}" (agentName "${entry.agentName}")`;
|
|
10550
|
+
}
|
|
10551
|
+
function canonicalRoleID(entry) {
|
|
10552
|
+
return entry.canonicalRoleID ?? entry.missingRoleID;
|
|
10553
|
+
}
|
|
10554
|
+
function legacyRosterFindings(audit) {
|
|
10555
|
+
const findings = [];
|
|
10556
|
+
for (const roleID of audit.missingGuaranteedRoleIDs ?? []) {
|
|
10557
|
+
findings.push({ kind: "missing-guaranteed-role", roleID });
|
|
10558
|
+
}
|
|
10559
|
+
for (const entry of audit.misassignedGuaranteedRoleEntries ?? []) {
|
|
10560
|
+
const canonical2 = canonicalRoleID(entry);
|
|
10561
|
+
findings.push({
|
|
10562
|
+
kind: "non-canonical-role-id",
|
|
10563
|
+
roleID: entry.roleID,
|
|
10564
|
+
...entry.agentName !== undefined ? { agentName: entry.agentName } : {},
|
|
10565
|
+
...canonical2 !== undefined ? { canonicalRoleID: canonical2 } : {}
|
|
10566
|
+
});
|
|
10567
|
+
}
|
|
10568
|
+
for (const entry of audit.unprofiledRoleEntries ?? []) {
|
|
10569
|
+
findings.push({
|
|
10570
|
+
kind: "unresolved-role-profile",
|
|
10571
|
+
roleID: entry.roleID,
|
|
10572
|
+
...entry.agentName !== undefined ? { agentName: entry.agentName } : {}
|
|
10573
|
+
});
|
|
10574
|
+
}
|
|
10575
|
+
return findings;
|
|
10576
|
+
}
|
|
10577
|
+
function rosterHealthSection(audit, loadError, rosterRoleCount) {
|
|
10578
|
+
const auditFindings = audit?.findings ?? legacyRosterFindings(audit ?? {});
|
|
10579
|
+
const unparseable = auditFindings.find((finding) => finding.kind === "unparseable-roster");
|
|
10580
|
+
const unparseableMessage = loadError ?? unparseable?.message;
|
|
10581
|
+
if (unparseableMessage !== undefined) {
|
|
10582
|
+
return [
|
|
10583
|
+
" roster health:",
|
|
10584
|
+
` ✗ unparseable roster: ${OPENTEAM_ROSTER_PATH} — ${unparseableMessage}`,
|
|
10585
|
+
" problem: the roster is present but is not machine-readable by openteam.",
|
|
10586
|
+
" remedy: keep any human notes outside the JSON fence, add one fenced `json` block containing `{ universe, entries: [{ roleID, agentName }] }`, then re-run `openteam doctor`."
|
|
10587
|
+
];
|
|
10588
|
+
}
|
|
10589
|
+
if (audit === undefined) {
|
|
10590
|
+
return [];
|
|
10591
|
+
}
|
|
10592
|
+
const missing = auditFindings.filter((finding) => finding.kind === "missing-guaranteed-role").map((finding) => finding.roleID);
|
|
10593
|
+
const misassigned = auditFindings.filter((finding) => finding.kind === "non-canonical-role-id");
|
|
10594
|
+
const unprofiled = auditFindings.filter((finding) => finding.kind === "unresolved-role-profile");
|
|
10595
|
+
if (missing.length === 0 && misassigned.length === 0 && unprofiled.length === 0) {
|
|
10596
|
+
return rosterRoleCount === undefined ? [] : [
|
|
10597
|
+
" roster health:",
|
|
10598
|
+
` ✓ ${OPENTEAM_ROSTER_PATH} read (${rosterRoleCount} roles); guaranteed roles present.`
|
|
10599
|
+
];
|
|
10600
|
+
}
|
|
10601
|
+
const lines = [" roster health:"];
|
|
10602
|
+
if (missing.length > 0) {
|
|
10603
|
+
lines.push(` ✗ missing guaranteed roleID(s): ${missing.join(", ")}`);
|
|
10604
|
+
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" }.`);
|
|
10605
|
+
}
|
|
10606
|
+
for (const entry of misassigned) {
|
|
10607
|
+
const canonical2 = canonicalRoleID(entry);
|
|
10608
|
+
const target = canonical2 === undefined ? "the canonical functional roleID" : `"${canonical2}"`;
|
|
10609
|
+
lines.push(` ⚠ non-canonical roleID ${rosterEntryText(entry)}`);
|
|
10610
|
+
lines.push(` remedy: change roleID to ${target} and keep the themed name in agentName.`);
|
|
10611
|
+
}
|
|
10612
|
+
if (unprofiled.length > 0) {
|
|
10613
|
+
const roleIDs = unprofiled.map((entry) => entry.roleID).join(", ");
|
|
10614
|
+
lines.push(` · unprofiled roleID(s): ${roleIDs}`);
|
|
10615
|
+
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.");
|
|
10616
|
+
}
|
|
10617
|
+
return lines;
|
|
10618
|
+
}
|
|
10230
10619
|
function agentModelsSection(diagnostics, searchedPaths) {
|
|
10231
10620
|
const lines = [" agent models:"];
|
|
10232
10621
|
if (diagnostics.length === 0) {
|
|
@@ -10262,6 +10651,10 @@ function renderDoctor(input) {
|
|
|
10262
10651
|
}
|
|
10263
10652
|
}
|
|
10264
10653
|
lines.push(` telemetry: ${input.telemetryPath} — ${input.telemetryRecords} record(s)`);
|
|
10654
|
+
if (input.diagnostics !== undefined) {
|
|
10655
|
+
lines.push(...diagnosticsSection(input.diagnostics));
|
|
10656
|
+
}
|
|
10657
|
+
lines.push(...rosterHealthSection(input.rosterAudit, input.rosterLoadError, input.rosterRoleCount));
|
|
10265
10658
|
if (input.otelBackend !== undefined) {
|
|
10266
10659
|
const otel = input.otelBackend;
|
|
10267
10660
|
lines.push(" opentelemetry:");
|
|
@@ -12487,13 +12880,23 @@ async function guaranteedAgentStatuses(runtime) {
|
|
|
12487
12880
|
}
|
|
12488
12881
|
return statuses;
|
|
12489
12882
|
}
|
|
12883
|
+
function rosterPlanSummary(rosterFile) {
|
|
12884
|
+
switch (rosterFile.status) {
|
|
12885
|
+
case "loaded":
|
|
12886
|
+
return `${rosterFile.roster.universe} (${rosterFile.roster.entries.length} roles)`;
|
|
12887
|
+
case "absent":
|
|
12888
|
+
return "none yet — one would be generated on the first real run";
|
|
12889
|
+
case "invalid":
|
|
12890
|
+
return `${rosterFile.path} is present but unparseable — run 'openteam doctor'`;
|
|
12891
|
+
}
|
|
12892
|
+
}
|
|
12490
12893
|
function renderLoopPlan(plan2) {
|
|
12491
12894
|
const { flags } = plan2;
|
|
12492
12895
|
const budget = flags.budgetUsd !== undefined ? `$${flags.budgetUsd}` : "unbounded";
|
|
12493
12896
|
const freshContext = flags.freshContext ? "on (pure Ralph pattern)" : "off";
|
|
12494
12897
|
const budgetStop = flags.budgetUsd !== undefined ? ", budget" : "";
|
|
12495
12898
|
const redTestsStop = flags.stopOnRedTests ? ", red tests" : "";
|
|
12496
|
-
const rosterSummary =
|
|
12899
|
+
const rosterSummary = rosterPlanSummary(plan2.rosterFile);
|
|
12497
12900
|
const lines = [
|
|
12498
12901
|
"openteam loop — plan (dry run, nothing dispatched):",
|
|
12499
12902
|
` backlog: ${flags.backlogPath} (${plan2.uncheckedCount} unchecked item(s))`,
|
|
@@ -12537,14 +12940,14 @@ ${LOOP_HELP}` };
|
|
|
12537
12940
|
const backlogText = await runtime.readBacklog(flags.backlogPath) ?? "";
|
|
12538
12941
|
const items = parseBacklog2(backlogText);
|
|
12539
12942
|
const unchecked = uncheckedBacklogItems(items);
|
|
12540
|
-
const
|
|
12943
|
+
const rosterFile = await runtime.loadRosterFile();
|
|
12541
12944
|
const guaranteed = await guaranteedAgentStatuses(runtime);
|
|
12542
12945
|
const plannedIterations = Math.max(0, Math.min(flags.maxIterations, unchecked.length));
|
|
12543
12946
|
const plan2 = {
|
|
12544
12947
|
flags,
|
|
12545
12948
|
uncheckedCount: unchecked.length,
|
|
12546
12949
|
plannedIterations,
|
|
12547
|
-
|
|
12950
|
+
rosterFile,
|
|
12548
12951
|
guaranteed
|
|
12549
12952
|
};
|
|
12550
12953
|
if (flags.dryRun) {
|
|
@@ -12622,9 +13025,19 @@ async function runCli(argv, deps) {
|
|
|
12622
13025
|
if (command === "doctor") {
|
|
12623
13026
|
const config = await deps.loadConfig(configPath, configResolution);
|
|
12624
13027
|
let opencodeConfigError;
|
|
12625
|
-
|
|
13028
|
+
let rosterLoadError;
|
|
13029
|
+
const [
|
|
13030
|
+
snapshots,
|
|
13031
|
+
records,
|
|
13032
|
+
diagnostics,
|
|
13033
|
+
cacheEntries,
|
|
13034
|
+
agentFiles,
|
|
13035
|
+
opencodeConfig,
|
|
13036
|
+
roster
|
|
13037
|
+
] = await Promise.all([
|
|
12626
13038
|
deps.probe(config),
|
|
12627
13039
|
deps.readTelemetry(telemetryPath),
|
|
13040
|
+
deps.readDiagnostics !== undefined ? deps.readDiagnostics() : Promise.resolve(undefined),
|
|
12628
13041
|
deps.cachePort !== undefined ? deps.cachePort.listEntries(deps.cachePort.resolveCacheRoot(parsed.cacheRoot)).catch(() => {
|
|
12629
13042
|
return;
|
|
12630
13043
|
}) : Promise.resolve(undefined),
|
|
@@ -12632,7 +13045,11 @@ async function runCli(argv, deps) {
|
|
|
12632
13045
|
deps.readOpencodeConfig(opencodeConfigPaths).catch((err) => {
|
|
12633
13046
|
opencodeConfigError = err instanceof Error ? err.message : String(err);
|
|
12634
13047
|
return;
|
|
12635
|
-
})
|
|
13048
|
+
}),
|
|
13049
|
+
deps.loadRosterForDoctor !== undefined ? deps.loadRosterForDoctor().catch((err) => {
|
|
13050
|
+
rosterLoadError = err instanceof Error ? err.message : String(err);
|
|
13051
|
+
return;
|
|
13052
|
+
}) : Promise.resolve(undefined)
|
|
12636
13053
|
]);
|
|
12637
13054
|
const defaultModel = defaultModelFromOpencode(opencodeConfig);
|
|
12638
13055
|
const agentModels = diagnoseAgentModels(agentFiles, {
|
|
@@ -12647,6 +13064,18 @@ async function runCli(argv, deps) {
|
|
|
12647
13064
|
backend: "opentelemetry",
|
|
12648
13065
|
connectionEnv: "APPLICATIONINSIGHTS_CONNECTION_STRING"
|
|
12649
13066
|
}, (name) => process.env[name]);
|
|
13067
|
+
let rosterAudit;
|
|
13068
|
+
let rosterRoleCount;
|
|
13069
|
+
if (roster !== undefined && deps.auditRoster !== undefined) {
|
|
13070
|
+
if (typeof roster !== "string") {
|
|
13071
|
+
rosterRoleCount = roster.entries.length;
|
|
13072
|
+
}
|
|
13073
|
+
try {
|
|
13074
|
+
rosterAudit = deps.auditRoster(roster, config.orchestrator?.roles);
|
|
13075
|
+
} catch (err) {
|
|
13076
|
+
rosterLoadError = err instanceof Error ? err.message : String(err);
|
|
13077
|
+
}
|
|
13078
|
+
}
|
|
12650
13079
|
return {
|
|
12651
13080
|
exitCode: 0,
|
|
12652
13081
|
stdout: renderDoctor({
|
|
@@ -12656,6 +13085,10 @@ async function runCli(argv, deps) {
|
|
|
12656
13085
|
telemetryRecords: records.length,
|
|
12657
13086
|
agentModels,
|
|
12658
13087
|
opencodeConfigPaths,
|
|
13088
|
+
...rosterAudit !== undefined ? { rosterAudit } : {},
|
|
13089
|
+
...rosterRoleCount !== undefined ? { rosterRoleCount } : {},
|
|
13090
|
+
...rosterLoadError !== undefined ? { rosterLoadError } : {},
|
|
13091
|
+
...diagnostics !== undefined && diagnostics.length > 0 ? { diagnostics } : {},
|
|
12659
13092
|
...opencodeConfigError !== undefined ? { opencodeConfigError } : {},
|
|
12660
13093
|
...cacheEntries !== undefined ? { cacheEntries, cliVersion: deps.version } : {},
|
|
12661
13094
|
...legacyLayout !== undefined && legacyLayout.length > 0 ? { legacyLayout } : {},
|
|
@@ -13618,16 +14051,16 @@ function insertRecords(db, records) {
|
|
|
13618
14051
|
throw error;
|
|
13619
14052
|
}
|
|
13620
14053
|
}
|
|
13621
|
-
function warnMemoryIndexRecordRejections(result) {
|
|
14054
|
+
function warnMemoryIndexRecordRejections(result, warn) {
|
|
13622
14055
|
if (result.rejectedRows === 0) {
|
|
13623
14056
|
return;
|
|
13624
14057
|
}
|
|
13625
|
-
console.warn(`[openteam] memory index reader rejected ${result.rejectedRows} row(s); semantic memory may be incomplete.`);
|
|
14058
|
+
(warn ?? console.warn)(`[openteam] memory index reader rejected ${result.rejectedRows} row(s); semantic memory may be incomplete.`);
|
|
13626
14059
|
for (const rejection of result.rejections) {
|
|
13627
|
-
console.warn(`[openteam] • memory_records:${rejection.recordId}: ${rejection.detail}`);
|
|
14060
|
+
(warn ?? console.warn)(`[openteam] • memory_records:${rejection.recordId}: ${rejection.detail}`);
|
|
13628
14061
|
}
|
|
13629
14062
|
}
|
|
13630
|
-
function createMemoryIndex(db) {
|
|
14063
|
+
function createMemoryIndex(db, warn) {
|
|
13631
14064
|
db.exec(SCHEMA2);
|
|
13632
14065
|
const allRecordsFn = () => {
|
|
13633
14066
|
const rows = db.query("SELECT id, payload FROM memory_records ORDER BY created_at ASC, id ASC").all();
|
|
@@ -13655,7 +14088,7 @@ function createMemoryIndex(db) {
|
|
|
13655
14088
|
};
|
|
13656
14089
|
const allRecordsWithWarning = () => {
|
|
13657
14090
|
const result = allRecordsFn();
|
|
13658
|
-
warnMemoryIndexRecordRejections(result);
|
|
14091
|
+
warnMemoryIndexRecordRejections(result, warn);
|
|
13659
14092
|
return result.records;
|
|
13660
14093
|
};
|
|
13661
14094
|
const state = () => {
|
|
@@ -14218,7 +14651,6 @@ async function* linesFromReadable(readable) {
|
|
|
14218
14651
|
}
|
|
14219
14652
|
|
|
14220
14653
|
// src/orchestrator/rosterPersistence.ts
|
|
14221
|
-
var ROSTER_JSON_FENCE = /```json\s*([\s\S]*?)```/;
|
|
14222
14654
|
function serializeRoster(roster) {
|
|
14223
14655
|
const data = { universe: roster.universe, entries: roster.entries };
|
|
14224
14656
|
const json = JSON.stringify(data, null, 2);
|
|
@@ -14231,38 +14663,41 @@ ${json}
|
|
|
14231
14663
|
\`\`\`
|
|
14232
14664
|
`;
|
|
14233
14665
|
}
|
|
14234
|
-
function parseRoster(text2) {
|
|
14235
|
-
const match = ROSTER_JSON_FENCE.exec(text2);
|
|
14236
|
-
if (match === null || match[1] === undefined) {
|
|
14237
|
-
throw new Error("openteam roster: no JSON block found in roster file");
|
|
14238
|
-
}
|
|
14239
|
-
let data;
|
|
14240
|
-
try {
|
|
14241
|
-
data = JSON.parse(match[1]);
|
|
14242
|
-
} catch (error) {
|
|
14243
|
-
throw new Error(`openteam roster: malformed JSON in roster file: ${String(error)}`);
|
|
14244
|
-
}
|
|
14245
|
-
return RosterSchema.parse(data);
|
|
14246
|
-
}
|
|
14247
14666
|
async function persistRoster(storage, roster, path4 = OPENTEAM_ROSTER_PATH) {
|
|
14248
14667
|
await storage.write(path4, serializeRoster(roster));
|
|
14249
14668
|
return path4;
|
|
14250
14669
|
}
|
|
14251
|
-
async function
|
|
14670
|
+
async function loadRosterFile(storage, path4 = OPENTEAM_ROSTER_PATH) {
|
|
14252
14671
|
const content = await storage.read(path4);
|
|
14253
14672
|
if (content === undefined) {
|
|
14254
|
-
return;
|
|
14673
|
+
return { status: "absent", path: path4 };
|
|
14674
|
+
}
|
|
14675
|
+
const parsed = parseRosterResult(content);
|
|
14676
|
+
if (!parsed.ok) {
|
|
14677
|
+
return { status: "invalid", path: path4, error: parsed.error };
|
|
14255
14678
|
}
|
|
14256
|
-
return
|
|
14679
|
+
return {
|
|
14680
|
+
status: "loaded",
|
|
14681
|
+
path: path4,
|
|
14682
|
+
roster: enforceGuaranteedRoles(parsed.roster)
|
|
14683
|
+
};
|
|
14257
14684
|
}
|
|
14258
14685
|
async function loadOrGenerateRoster(storage, generate, path4 = OPENTEAM_ROSTER_PATH) {
|
|
14259
|
-
const existing = await
|
|
14260
|
-
if (existing
|
|
14261
|
-
return { roster: existing, regenerated: false };
|
|
14686
|
+
const existing = await loadRosterFile(storage, path4);
|
|
14687
|
+
if (existing.status === "loaded") {
|
|
14688
|
+
return { roster: existing.roster, regenerated: false };
|
|
14262
14689
|
}
|
|
14263
|
-
const roster = await generate();
|
|
14264
|
-
|
|
14265
|
-
|
|
14690
|
+
const roster = enforceGuaranteedRoles(await generate());
|
|
14691
|
+
if (existing.status === "absent") {
|
|
14692
|
+
await persistRoster(storage, roster, path4);
|
|
14693
|
+
return { roster, regenerated: true };
|
|
14694
|
+
}
|
|
14695
|
+
return {
|
|
14696
|
+
roster,
|
|
14697
|
+
regenerated: true,
|
|
14698
|
+
recoveredFromInvalidPersisted: true,
|
|
14699
|
+
invalidPersistedRosterError: existing.error.message
|
|
14700
|
+
};
|
|
14266
14701
|
}
|
|
14267
14702
|
|
|
14268
14703
|
// src/plugin/orchestrateTool.ts
|
|
@@ -14497,7 +14932,7 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
|
|
|
14497
14932
|
const warn = deps.warn ?? console.warn;
|
|
14498
14933
|
try {
|
|
14499
14934
|
const db = await openDatabase(semantic.indexPath);
|
|
14500
|
-
const index = createMemoryIndex(db);
|
|
14935
|
+
const index = createMemoryIndex(db, warn);
|
|
14501
14936
|
const rebuildResult = await rebuildMemoryIndexFromStorage(index, semantic.logPath, { storage: deps.storage });
|
|
14502
14937
|
if (rebuildResult.rejectedLines > 0) {
|
|
14503
14938
|
warn(`[openteam] memory log rejected ${rebuildResult.rejectedLines} line(s); semantic memory may be incomplete.`);
|
|
@@ -14518,7 +14953,7 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
|
|
|
14518
14953
|
// package.json
|
|
14519
14954
|
var package_default = {
|
|
14520
14955
|
name: "@jmanuelcorral/openteam",
|
|
14521
|
-
version: "0.9.
|
|
14956
|
+
version: "0.9.2",
|
|
14522
14957
|
packageManager: "bun@1.3.14",
|
|
14523
14958
|
description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
|
|
14524
14959
|
license: "MIT",
|
|
@@ -14643,6 +15078,18 @@ function createGitLastCommit(exec) {
|
|
|
14643
15078
|
var execFileAsync2 = promisify2(execFile2);
|
|
14644
15079
|
var AGENT_DIR2 = dirname5(ORCHESTRATOR_AGENT_PATH);
|
|
14645
15080
|
var storage = createFsStorageProvider(process.cwd());
|
|
15081
|
+
async function readStoredRosterForDoctor() {
|
|
15082
|
+
const content = await storage.read(OPENTEAM_ROSTER_PATH);
|
|
15083
|
+
if (content === undefined) {
|
|
15084
|
+
return;
|
|
15085
|
+
}
|
|
15086
|
+
try {
|
|
15087
|
+
return parseRoster(content);
|
|
15088
|
+
} catch {
|
|
15089
|
+
return content;
|
|
15090
|
+
}
|
|
15091
|
+
}
|
|
15092
|
+
var auditStoredRosterForDoctor = (roster, configuredRoles) => typeof roster === "string" ? auditRoster(roster, configuredRoles) : auditRoster(roster, configuredRoles);
|
|
14646
15093
|
var migratePort = createMigrateAdapter(process.cwd());
|
|
14647
15094
|
var nodeExec2 = async (command, args) => {
|
|
14648
15095
|
try {
|
|
@@ -14798,6 +15245,9 @@ var deps = {
|
|
|
14798
15245
|
storage,
|
|
14799
15246
|
legacyTelemetryPath: path4
|
|
14800
15247
|
}),
|
|
15248
|
+
readDiagnostics: () => readDiagnosticEvents(DEFAULT_SESSIONS_DIR, {
|
|
15249
|
+
storage
|
|
15250
|
+
}),
|
|
14801
15251
|
readGraphSnapshot: createGraphStatusSnapshotReader({
|
|
14802
15252
|
storage,
|
|
14803
15253
|
fallbackConfig: loadOpenTeamConfig({}),
|
|
@@ -14818,6 +15268,8 @@ var deps = {
|
|
|
14818
15268
|
cachePort: realCacheAdapter,
|
|
14819
15269
|
purge: createCliPurgeRuntime(),
|
|
14820
15270
|
loopRuntime: createCliLoopRuntime(),
|
|
15271
|
+
loadRosterForDoctor: readStoredRosterForDoctor,
|
|
15272
|
+
auditRoster: auditStoredRosterForDoctor,
|
|
14821
15273
|
version: PACKAGE_VERSION
|
|
14822
15274
|
};
|
|
14823
15275
|
function createCliPurgeRuntime() {
|
|
@@ -14887,7 +15339,7 @@ function createCliLoopRuntime() {
|
|
|
14887
15339
|
const agentFileExists = (agentName) => storage.exists(`${AGENT_DIR2}/${agentName}.md`);
|
|
14888
15340
|
const generate = () => generateRoster("openteam initial roster", fallbackRosterSource);
|
|
14889
15341
|
return {
|
|
14890
|
-
|
|
15342
|
+
loadRosterFile: () => loadRosterFile(storage),
|
|
14891
15343
|
loadOrGenerateRoster: async () => (await loadOrGenerateRoster(storage, generate)).roster,
|
|
14892
15344
|
agentFileExists,
|
|
14893
15345
|
readBacklog: (path4) => storage.read(path4),
|
|
@@ -14995,6 +15447,9 @@ async function main() {
|
|
|
14995
15447
|
storage,
|
|
14996
15448
|
legacyTelemetryPath: path4
|
|
14997
15449
|
}),
|
|
15450
|
+
readDiagnostics: () => readDiagnosticEvents(effectiveSessionsDir, {
|
|
15451
|
+
storage
|
|
15452
|
+
}),
|
|
14998
15453
|
detectLegacyLayout: async () => detectLegacyLayout(legacyExists),
|
|
14999
15454
|
migrateLegacyLayout: () => migrateLegacyLayout(planLegacyMigration(legacyExists), migratePort)
|
|
15000
15455
|
};
|