@jmanuelcorral/openteam 0.9.1 → 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 +362 -44
- package/dist/commands/dispatch.d.ts +18 -4
- package/dist/commands/dispatch.d.ts.map +1 -1
- package/dist/commands/doctor.d.ts +34 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +361 -43
- package/dist/opencodeArtifacts/orchestratorAgent.d.ts +1 -1
- package/dist/opencodeArtifacts/orchestratorAgent.d.ts.map +1 -1
- 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/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -199,7 +199,7 @@ var LEGACY_TEAM_ROLE_ALIASES = {
|
|
|
199
199
|
basher: "routing-cost",
|
|
200
200
|
linus: "tester"
|
|
201
201
|
};
|
|
202
|
-
var KNOWN_NON_WORKER_ROLE_IDS = ["openteam"];
|
|
202
|
+
var KNOWN_NON_WORKER_ROLE_IDS = ["orchestrator", "openteam"];
|
|
203
203
|
function isKnownNonWorkerRole(roleID) {
|
|
204
204
|
return KNOWN_NON_WORKER_ROLE_IDS.includes(roleID);
|
|
205
205
|
}
|
|
@@ -3661,22 +3661,70 @@ function buildOrchestratorAgent(frontier, options = {}) {
|
|
|
3661
3661
|
"1. When you create the team for the first time, **choose a thematic universe**",
|
|
3662
3662
|
" (e.g. a film, series, comic or mythology). If the user has a preference,",
|
|
3663
3663
|
" ask them for it; if not, propose one and move forward without blocking work.",
|
|
3664
|
-
"2. Give each subagent the **name of a character** from that universe;
|
|
3665
|
-
" 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",
|
|
3666
3666
|
" `@mention`, and its role is clear from the `description`.",
|
|
3667
|
-
|
|
3668
|
-
"
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
"
|
|
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
|
+
"```",
|
|
3672
3718
|
"",
|
|
3673
3719
|
"## How to create the team when it does not exist",
|
|
3674
3720
|
"",
|
|
3675
3721
|
"1. Analyze the goal and explore the repository (`read`/`glob`/`grep`).",
|
|
3676
3722
|
"2. Break the request into tasks and design the **minimal team** needed:",
|
|
3677
|
-
" define only the roles the work requires
|
|
3678
|
-
"
|
|
3679
|
-
"
|
|
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`:",
|
|
3680
3728
|
" - Frontmatter: `description` (required, include role and universe),",
|
|
3681
3729
|
" `mode: subagent`, `temperature`, `permission` with the minimum needed,",
|
|
3682
3730
|
" and `model` **optional**.",
|
|
@@ -3697,8 +3745,10 @@ function buildOrchestratorAgent(frontier, options = {}) {
|
|
|
3697
3745
|
"",
|
|
3698
3746
|
"## Standard team roles",
|
|
3699
3747
|
"",
|
|
3700
|
-
"
|
|
3701
|
-
"
|
|
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:",
|
|
3702
3752
|
"",
|
|
3703
3753
|
"- **scribe** — the team's silent memory. Records decisions and",
|
|
3704
3754
|
" learnings in a shared log (`.opencode/openteam/decisions.md`) without",
|
|
@@ -6608,12 +6658,67 @@ var RosterSchema = z18.object({
|
|
|
6608
6658
|
universe: z18.string().min(1),
|
|
6609
6659
|
entries: z18.array(RosterEntrySchema)
|
|
6610
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
|
+
}
|
|
6611
6704
|
var GUARANTEED_ROLE_IDS = [
|
|
6612
6705
|
"orchestrator",
|
|
6613
6706
|
"guardian",
|
|
6614
6707
|
"scribe",
|
|
6615
6708
|
"ralph"
|
|
6616
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
|
+
}
|
|
6617
6722
|
function missingGuaranteedRoles(roster) {
|
|
6618
6723
|
const present = new Set(roster.entries.map((entry) => entry.roleID));
|
|
6619
6724
|
return GUARANTEED_ROLE_IDS.filter((roleID) => !present.has(roleID));
|
|
@@ -6621,6 +6726,100 @@ function missingGuaranteedRoles(roster) {
|
|
|
6621
6726
|
function hasAllGuaranteedRoles(roster) {
|
|
6622
6727
|
return missingGuaranteedRoles(roster).length === 0;
|
|
6623
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
|
+
}
|
|
6624
6823
|
function enforceGuaranteedRoles(roster) {
|
|
6625
6824
|
const missing = missingGuaranteedRoles(roster);
|
|
6626
6825
|
if (missing.length === 0) {
|
|
@@ -6628,7 +6827,7 @@ function enforceGuaranteedRoles(roster) {
|
|
|
6628
6827
|
}
|
|
6629
6828
|
const added = missing.map((roleID) => ({
|
|
6630
6829
|
roleID,
|
|
6631
|
-
agentName: roleID
|
|
6830
|
+
agentName: defaultAgentNameForGuaranteedRole(roleID)
|
|
6632
6831
|
}));
|
|
6633
6832
|
return { universe: roster.universe, entries: [...roster.entries, ...added] };
|
|
6634
6833
|
}
|
|
@@ -6660,7 +6859,6 @@ async function dispatchGuaranteedRole(agentName, agentFileExists, dispatch) {
|
|
|
6660
6859
|
}
|
|
6661
6860
|
|
|
6662
6861
|
// src/orchestrator/rosterPersistence.ts
|
|
6663
|
-
var ROSTER_JSON_FENCE = /```json\s*([\s\S]*?)```/;
|
|
6664
6862
|
function serializeRoster(roster) {
|
|
6665
6863
|
const data = { universe: roster.universe, entries: roster.entries };
|
|
6666
6864
|
const json = JSON.stringify(data, null, 2);
|
|
@@ -6673,38 +6871,41 @@ ${json}
|
|
|
6673
6871
|
\`\`\`
|
|
6674
6872
|
`;
|
|
6675
6873
|
}
|
|
6676
|
-
function parseRoster(text) {
|
|
6677
|
-
const match = ROSTER_JSON_FENCE.exec(text);
|
|
6678
|
-
if (match === null || match[1] === undefined) {
|
|
6679
|
-
throw new Error("openteam roster: no JSON block found in roster file");
|
|
6680
|
-
}
|
|
6681
|
-
let data;
|
|
6682
|
-
try {
|
|
6683
|
-
data = JSON.parse(match[1]);
|
|
6684
|
-
} catch (error) {
|
|
6685
|
-
throw new Error(`openteam roster: malformed JSON in roster file: ${String(error)}`);
|
|
6686
|
-
}
|
|
6687
|
-
return RosterSchema.parse(data);
|
|
6688
|
-
}
|
|
6689
6874
|
async function persistRoster(storage, roster, path2 = OPENTEAM_ROSTER_PATH) {
|
|
6690
6875
|
await storage.write(path2, serializeRoster(roster));
|
|
6691
6876
|
return path2;
|
|
6692
6877
|
}
|
|
6693
|
-
async function
|
|
6878
|
+
async function loadRosterFile(storage, path2 = OPENTEAM_ROSTER_PATH) {
|
|
6694
6879
|
const content = await storage.read(path2);
|
|
6695
6880
|
if (content === undefined) {
|
|
6696
|
-
return;
|
|
6881
|
+
return { status: "absent", path: path2 };
|
|
6882
|
+
}
|
|
6883
|
+
const parsed = parseRosterResult(content);
|
|
6884
|
+
if (!parsed.ok) {
|
|
6885
|
+
return { status: "invalid", path: path2, error: parsed.error };
|
|
6697
6886
|
}
|
|
6698
|
-
return
|
|
6887
|
+
return {
|
|
6888
|
+
status: "loaded",
|
|
6889
|
+
path: path2,
|
|
6890
|
+
roster: enforceGuaranteedRoles(parsed.roster)
|
|
6891
|
+
};
|
|
6699
6892
|
}
|
|
6700
6893
|
async function loadOrGenerateRoster(storage, generate, path2 = OPENTEAM_ROSTER_PATH) {
|
|
6701
|
-
const existing = await
|
|
6702
|
-
if (existing
|
|
6703
|
-
return { roster: existing, regenerated: false };
|
|
6894
|
+
const existing = await loadRosterFile(storage, path2);
|
|
6895
|
+
if (existing.status === "loaded") {
|
|
6896
|
+
return { roster: existing.roster, regenerated: false };
|
|
6897
|
+
}
|
|
6898
|
+
const roster = enforceGuaranteedRoles(await generate());
|
|
6899
|
+
if (existing.status === "absent") {
|
|
6900
|
+
await persistRoster(storage, roster, path2);
|
|
6901
|
+
return { roster, regenerated: true };
|
|
6704
6902
|
}
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6903
|
+
return {
|
|
6904
|
+
roster,
|
|
6905
|
+
regenerated: true,
|
|
6906
|
+
recoveredFromInvalidPersisted: true,
|
|
6907
|
+
invalidPersistedRosterError: existing.error.message
|
|
6908
|
+
};
|
|
6708
6909
|
}
|
|
6709
6910
|
|
|
6710
6911
|
// src/plugin/availability.ts
|
|
@@ -7760,6 +7961,77 @@ function diagnosticsSection(diagnostics) {
|
|
|
7760
7961
|
lines.push(" note: plugin diagnostics are bounded classes recorded instead of writing to the opencode TUI console.");
|
|
7761
7962
|
return lines;
|
|
7762
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
|
+
}
|
|
7763
8035
|
function agentModelsSection(diagnostics, searchedPaths) {
|
|
7764
8036
|
const lines = [" agent models:"];
|
|
7765
8037
|
if (diagnostics.length === 0) {
|
|
@@ -7798,6 +8070,7 @@ function renderDoctor(input) {
|
|
|
7798
8070
|
if (input.diagnostics !== undefined) {
|
|
7799
8071
|
lines.push(...diagnosticsSection(input.diagnostics));
|
|
7800
8072
|
}
|
|
8073
|
+
lines.push(...rosterHealthSection(input.rosterAudit, input.rosterLoadError, input.rosterRoleCount));
|
|
7801
8074
|
if (input.otelBackend !== undefined) {
|
|
7802
8075
|
const otel = input.otelBackend;
|
|
7803
8076
|
lines.push(" opentelemetry:");
|
|
@@ -9442,13 +9715,23 @@ async function guaranteedAgentStatuses(runtime) {
|
|
|
9442
9715
|
}
|
|
9443
9716
|
return statuses;
|
|
9444
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
|
+
}
|
|
9445
9728
|
function renderLoopPlan(plan2) {
|
|
9446
9729
|
const { flags } = plan2;
|
|
9447
9730
|
const budget = flags.budgetUsd !== undefined ? `$${flags.budgetUsd}` : "unbounded";
|
|
9448
9731
|
const freshContext = flags.freshContext ? "on (pure Ralph pattern)" : "off";
|
|
9449
9732
|
const budgetStop = flags.budgetUsd !== undefined ? ", budget" : "";
|
|
9450
9733
|
const redTestsStop = flags.stopOnRedTests ? ", red tests" : "";
|
|
9451
|
-
const rosterSummary =
|
|
9734
|
+
const rosterSummary = rosterPlanSummary(plan2.rosterFile);
|
|
9452
9735
|
const lines = [
|
|
9453
9736
|
"openteam loop — plan (dry run, nothing dispatched):",
|
|
9454
9737
|
` backlog: ${flags.backlogPath} (${plan2.uncheckedCount} unchecked item(s))`,
|
|
@@ -9492,14 +9775,14 @@ ${LOOP_HELP}` };
|
|
|
9492
9775
|
const backlogText = await runtime.readBacklog(flags.backlogPath) ?? "";
|
|
9493
9776
|
const items = parseBacklog(backlogText);
|
|
9494
9777
|
const unchecked = uncheckedBacklogItems(items);
|
|
9495
|
-
const
|
|
9778
|
+
const rosterFile = await runtime.loadRosterFile();
|
|
9496
9779
|
const guaranteed = await guaranteedAgentStatuses(runtime);
|
|
9497
9780
|
const plannedIterations = Math.max(0, Math.min(flags.maxIterations, unchecked.length));
|
|
9498
9781
|
const plan2 = {
|
|
9499
9782
|
flags,
|
|
9500
9783
|
uncheckedCount: unchecked.length,
|
|
9501
9784
|
plannedIterations,
|
|
9502
|
-
|
|
9785
|
+
rosterFile,
|
|
9503
9786
|
guaranteed
|
|
9504
9787
|
};
|
|
9505
9788
|
if (flags.dryRun) {
|
|
@@ -9577,13 +9860,15 @@ async function runCli(argv, deps) {
|
|
|
9577
9860
|
if (command === "doctor") {
|
|
9578
9861
|
const config = await deps.loadConfig(configPath, configResolution);
|
|
9579
9862
|
let opencodeConfigError;
|
|
9863
|
+
let rosterLoadError;
|
|
9580
9864
|
const [
|
|
9581
9865
|
snapshots,
|
|
9582
9866
|
records,
|
|
9583
9867
|
diagnostics,
|
|
9584
9868
|
cacheEntries,
|
|
9585
9869
|
agentFiles,
|
|
9586
|
-
opencodeConfig
|
|
9870
|
+
opencodeConfig,
|
|
9871
|
+
roster
|
|
9587
9872
|
] = await Promise.all([
|
|
9588
9873
|
deps.probe(config),
|
|
9589
9874
|
deps.readTelemetry(telemetryPath),
|
|
@@ -9595,7 +9880,11 @@ async function runCli(argv, deps) {
|
|
|
9595
9880
|
deps.readOpencodeConfig(opencodeConfigPaths).catch((err) => {
|
|
9596
9881
|
opencodeConfigError = err instanceof Error ? err.message : String(err);
|
|
9597
9882
|
return;
|
|
9598
|
-
})
|
|
9883
|
+
}),
|
|
9884
|
+
deps.loadRosterForDoctor !== undefined ? deps.loadRosterForDoctor().catch((err) => {
|
|
9885
|
+
rosterLoadError = err instanceof Error ? err.message : String(err);
|
|
9886
|
+
return;
|
|
9887
|
+
}) : Promise.resolve(undefined)
|
|
9599
9888
|
]);
|
|
9600
9889
|
const defaultModel = defaultModelFromOpencode(opencodeConfig);
|
|
9601
9890
|
const agentModels = diagnoseAgentModels(agentFiles, {
|
|
@@ -9610,6 +9899,18 @@ async function runCli(argv, deps) {
|
|
|
9610
9899
|
backend: "opentelemetry",
|
|
9611
9900
|
connectionEnv: "APPLICATIONINSIGHTS_CONNECTION_STRING"
|
|
9612
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
|
+
}
|
|
9613
9914
|
return {
|
|
9614
9915
|
exitCode: 0,
|
|
9615
9916
|
stdout: renderDoctor({
|
|
@@ -9619,6 +9920,9 @@ async function runCli(argv, deps) {
|
|
|
9619
9920
|
telemetryRecords: records.length,
|
|
9620
9921
|
agentModels,
|
|
9621
9922
|
opencodeConfigPaths,
|
|
9923
|
+
...rosterAudit !== undefined ? { rosterAudit } : {},
|
|
9924
|
+
...rosterRoleCount !== undefined ? { rosterRoleCount } : {},
|
|
9925
|
+
...rosterLoadError !== undefined ? { rosterLoadError } : {},
|
|
9622
9926
|
...diagnostics !== undefined && diagnostics.length > 0 ? { diagnostics } : {},
|
|
9623
9927
|
...opencodeConfigError !== undefined ? { opencodeConfigError } : {},
|
|
9624
9928
|
...cacheEntries !== undefined ? { cacheEntries, cliVersion: deps.version } : {},
|
|
@@ -12504,7 +12808,7 @@ var createOtlpSpanExporter = (connection, config) => {
|
|
|
12504
12808
|
// package.json
|
|
12505
12809
|
var package_default = {
|
|
12506
12810
|
name: "@jmanuelcorral/openteam",
|
|
12507
|
-
version: "0.9.
|
|
12811
|
+
version: "0.9.2",
|
|
12508
12812
|
packageManager: "bun@1.3.14",
|
|
12509
12813
|
description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
|
|
12510
12814
|
license: "MIT",
|
|
@@ -12944,6 +13248,18 @@ async function readCliConfigFromStorage(storage, path4, resolution) {
|
|
|
12944
13248
|
}
|
|
12945
13249
|
return content;
|
|
12946
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);
|
|
12947
13263
|
function createCliDeps(config, registry, telemetryPath, sessionsDir = DEFAULT_SESSIONS_DIR, storage = createFsStorageProvider(process.cwd()), fallbackSource = "options", rawOptions) {
|
|
12948
13264
|
return {
|
|
12949
13265
|
loadConfig: async (path4, resolution = { explicit: false }) => {
|
|
@@ -13027,6 +13343,8 @@ function createCliDeps(config, registry, telemetryPath, sessionsDir = DEFAULT_SE
|
|
|
13027
13343
|
agentDir: dirname4(ORCHESTRATOR_AGENT_PATH),
|
|
13028
13344
|
cachePort: realCacheAdapter,
|
|
13029
13345
|
purge: createPluginPurgeRuntime(sessionsDir),
|
|
13346
|
+
loadRosterForDoctor: () => readStoredRosterForDoctor(storage),
|
|
13347
|
+
auditRoster: auditStoredRosterForDoctor,
|
|
13030
13348
|
version: PACKAGE_VERSION
|
|
13031
13349
|
};
|
|
13032
13350
|
}
|
|
@@ -5,7 +5,7 @@ import type { FrontierChoice } from "../commands/setup";
|
|
|
5
5
|
*/
|
|
6
6
|
export declare const ORCHESTRATOR_AGENT_PATH = ".opencode/agent/openteam.md";
|
|
7
7
|
/**
|
|
8
|
-
* Path to the casting registry (universe +
|
|
8
|
+
* Path to the casting registry (universe + roleID→agentName JSON roster). Lives
|
|
9
9
|
* **outside** `.opencode/agent/` intentionally: opencode loads as an agent every
|
|
10
10
|
* `.md` in that folder, so a stray roster markdown file inside would appear as a
|
|
11
11
|
* phantom agent in the Tab-switcher. It is portable team-state (P15b), so it lives
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestratorAgent.d.ts","sourceRoot":"","sources":["../../src/opencodeArtifacts/orchestratorAgent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,gCAAgC,CAAC;AAErE;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,iCAAiC,CAAC;AAInE;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,cAAc,EACxB,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAC/B,MAAM,
|
|
1
|
+
{"version":3,"file":"orchestratorAgent.d.ts","sourceRoot":"","sources":["../../src/opencodeArtifacts/orchestratorAgent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,gCAAgC,CAAC;AAErE;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,iCAAiC,CAAC;AAInE;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,cAAc,EACxB,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAC/B,MAAM,CAmRR"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roles.d.ts","sourceRoot":"","sources":["../../src/orchestrator/roles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EACV,qBAAqB,EACrB,cAAc,EACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,cAAc,CAAC;IAC5B,gBAAgB,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpC,kBAAkB,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtC,yBAAyB,EAAE,OAAO,CAAC;IACnC,uBAAuB,EAAE,KAAK,CAAC,cAAc,GAAG,MAAM,CAAC,CAAC;IACxD,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;;;;;OASG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;OAaG;IACH,aAAa,CAAC,EAAE,cAAc,EAAE,CAAC;CAClC,CAAC;AAiBF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;kBAkBxB,CAAC;AAGZ,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CA8FvD,CAAC;
|
|
1
|
+
{"version":3,"file":"roles.d.ts","sourceRoot":"","sources":["../../src/orchestrator/roles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EACV,qBAAqB,EACrB,cAAc,EACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,cAAc,CAAC;IAC5B,gBAAgB,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpC,kBAAkB,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtC,yBAAyB,EAAE,OAAO,CAAC;IACnC,uBAAuB,EAAE,KAAK,CAAC,cAAc,GAAG,MAAM,CAAC,CAAC;IACxD,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;;;;;OASG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;OAaG;IACH,aAAa,CAAC,EAAE,cAAc,EAAE,CAAC;CAClC,CAAC;AAiBF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;kBAkBxB,CAAC;AAGZ,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CA8FvD,CAAC;AAcF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAI5D;AA6BD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,GAC3D,gBAAgB,GAAG,SAAS,CAO9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAW1E;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,gBAAgB,EACtB,IAAI,EAAE,WAAW,EACjB,IAAI,CAAC,EAAE,cAAc,GACpB,qBAAqB,CAevB"}
|