@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/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; the",
3665
- " file name (`.opencode/agent/<name>.md`) is its alias for",
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
- `3. **Register the cast** in \`${OPENTEAM_ROSTER_PATH}\` (universe + table`,
3668
- " name role model) so that the names **persist** across sessions.",
3669
- " This file goes **outside** `.opencode/agent/` (otherwise opencode would load it",
3670
- " as a phantom agent). Always reuse the same cast; do not re-cast",
3671
- " without reason.",
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 (e.g. architect, backend,",
3678
- " frontend, reviewer, tester, docs). Do not invent roles you will not use.",
3679
- "3. Create each subagent **on demand** by writing `.opencode/agent/<name>.md`:",
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
- "Besides the project-specific roles, include these standard roles",
3701
- "(create them on demand, with their universe name) when they add value:",
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 loadRoster(storage, path2 = OPENTEAM_ROSTER_PATH) {
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 parseRoster(content);
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 loadRoster(storage, path2);
6702
- if (existing !== undefined) {
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
- const roster = await generate();
6706
- await persistRoster(storage, roster, path2);
6707
- return { roster, regenerated: true };
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 = plan2.roster !== undefined ? `${plan2.roster.universe} (${plan2.roster.entries.length} roles)` : "none yet — one would be generated on the first real run";
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 roster = await runtime.loadRoster();
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
- roster,
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.1",
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 + namerole→model table). Lives
8
+ * Path to the casting registry (universe + roleIDagentName 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,CAiOR"}
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;AAYF,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"}
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"}