@jmanuelcorral/openteam 0.16.0 → 0.16.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
@@ -2,7 +2,8 @@ import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
4
  // src/index.ts
5
- import { dirname as dirname4 } from "node:path";
5
+ import { readFile as readFile2 } from "node:fs/promises";
6
+ import { basename as basename2, dirname as dirname4 } from "node:path";
6
7
  import {
7
8
  tool as tool6
8
9
  } from "@opencode-ai/plugin";
@@ -2449,7 +2450,15 @@ function splitVirtualPath(path) {
2449
2450
  }
2450
2451
 
2451
2452
  // src/cli/graphOperator.ts
2452
- var ALLOWED_GATE = { allowed: true, violations: [] };
2453
+ var ALLOWED_GATE = {
2454
+ allowed: true,
2455
+ violations: [],
2456
+ advisories: []
2457
+ };
2458
+ var ALLOWED_EVALUATION = {
2459
+ kind: "evaluated",
2460
+ gate: ALLOWED_GATE
2461
+ };
2453
2462
  function journalRoot(config) {
2454
2463
  return config.graph?.journalRoot ?? DEFAULT_GRAPH_JOURNAL_ROOT;
2455
2464
  }
@@ -2539,7 +2548,7 @@ async function listWorktreeObservations(config, options) {
2539
2548
  })));
2540
2549
  }
2541
2550
  function createGraphOperator(options) {
2542
- const evaluateCutoverGate = options.evaluateCutoverGate ?? (async () => ALLOWED_GATE);
2551
+ const evaluateCutoverGate = options.evaluateCutoverGate ?? (async () => ALLOWED_EVALUATION);
2543
2552
  const worktreeOptions = options.exec === undefined ? undefined : {
2544
2553
  exec: options.exec,
2545
2554
  repoRoot: options.repoRoot ?? process.cwd(),
@@ -4181,6 +4190,7 @@ function checkCertificate(status, name) {
4181
4190
  }
4182
4191
  function evaluateGraphGate(input) {
4183
4192
  const violations = [];
4193
+ const advisories = [];
4184
4194
  const shadowV = checkCertificate(input.shadow, "shadow");
4185
4195
  if (shadowV !== undefined)
4186
4196
  violations.push(shadowV);
@@ -4189,7 +4199,7 @@ function evaluateGraphGate(input) {
4189
4199
  violations.push(releaseV);
4190
4200
  const soakV = checkCertificate(input.soak, "soak");
4191
4201
  if (soakV !== undefined)
4192
- violations.push(soakV);
4202
+ advisories.push(soakV);
4193
4203
  if (input.migration === "pending") {
4194
4204
  violations.push({
4195
4205
  code: "migration-pending",
@@ -4209,7 +4219,8 @@ function evaluateGraphGate(input) {
4209
4219
  }
4210
4220
  return {
4211
4221
  allowed: violations.length === 0,
4212
- violations
4222
+ violations,
4223
+ advisories
4213
4224
  };
4214
4225
  }
4215
4226
  function isGateApplicable(mode) {
@@ -4298,6 +4309,36 @@ var SUPPORTED_OPENCODE_VERSIONS = [
4298
4309
  "1.18.18",
4299
4310
  "1.18.19"
4300
4311
  ];
4312
+ var SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)/;
4313
+ function parseSemver(version) {
4314
+ const normalized = version.trim().replace(/^[=^~v]+/, "");
4315
+ const match = SEMVER_RE.exec(normalized);
4316
+ if (match === null) {
4317
+ throw new Error(`invalid semver: "${version}"`);
4318
+ }
4319
+ return {
4320
+ major: Number(match[1]),
4321
+ minor: Number(match[2]),
4322
+ patch: Number(match[3])
4323
+ };
4324
+ }
4325
+ function isCertificateVersionCompatible(certifiedVersion, liveVersion) {
4326
+ if (certifiedVersion === liveVersion) {
4327
+ return true;
4328
+ }
4329
+ let certified;
4330
+ let live;
4331
+ try {
4332
+ certified = parseSemver(certifiedVersion);
4333
+ live = parseSemver(liveVersion);
4334
+ } catch {
4335
+ return false;
4336
+ }
4337
+ if (certified.major !== live.major || certified.minor !== live.minor) {
4338
+ return false;
4339
+ }
4340
+ return live.patch >= certified.patch;
4341
+ }
4301
4342
 
4302
4343
  // src/graph/soakLedger.ts
4303
4344
  import { z as z15 } from "zod";
@@ -4556,8 +4597,8 @@ function parseShadowCertificate(value, opencodeVersion) {
4556
4597
  if (sha256Hex(canonicalCertificate(unsigned)) !== digest) {
4557
4598
  throw new ShadowCertificateError("digest-mismatch", "certificate digest does not cover the presented evidence");
4558
4599
  }
4559
- if (certificate.opencodeVersion !== opencodeVersion) {
4560
- throw new ShadowCertificateError("opencode-mismatch", `certified for "${certificate.opencodeVersion}", expected "${opencodeVersion}"`);
4600
+ if (!isCertificateVersionCompatible(certificate.opencodeVersion, opencodeVersion)) {
4601
+ throw new ShadowCertificateError("opencode-mismatch", `certified for "${certificate.opencodeVersion}", running "${opencodeVersion}" — evidence is accepted only on ${certificate.opencodeVersion} or a newer patch of the same minor line`);
4561
4602
  }
4562
4603
  return certificate;
4563
4604
  }
@@ -4741,8 +4782,8 @@ function parseReleaseCertificate(value, opencodeVersion, expectedShadowDigest) {
4741
4782
  if (sha256Hex(canonicalReleaseCertificate(unsigned)) !== digest) {
4742
4783
  throw new ReleaseCertificateError("digest-mismatch", "certificate digest does not cover the presented evidence");
4743
4784
  }
4744
- if (certificate.opencodeVersion !== opencodeVersion) {
4745
- throw new ReleaseCertificateError("opencode-mismatch", `certified for "${certificate.opencodeVersion}", expected "${opencodeVersion}"`);
4785
+ if (!isCertificateVersionCompatible(certificate.opencodeVersion, opencodeVersion)) {
4786
+ throw new ReleaseCertificateError("opencode-mismatch", `certified for "${certificate.opencodeVersion}", running "${opencodeVersion}" — evidence is accepted only on ${certificate.opencodeVersion} or a newer patch of the same minor line`);
4746
4787
  }
4747
4788
  return certificate;
4748
4789
  }
@@ -8344,7 +8385,7 @@ function legacyRosterFindings(audit) {
8344
8385
  }
8345
8386
  return findings;
8346
8387
  }
8347
- function rosterHealthSection(audit, loadError, rosterRoleCount) {
8388
+ function rosterHealthSection(audit, loadError, rosterRoleCount, rosterMissing) {
8348
8389
  const auditFindings = audit?.findings ?? legacyRosterFindings(audit ?? {});
8349
8390
  const unparseable = auditFindings.find((finding) => finding.kind === "unparseable-roster");
8350
8391
  const unparseableMessage = loadError ?? unparseable?.message;
@@ -8357,6 +8398,14 @@ function rosterHealthSection(audit, loadError, rosterRoleCount) {
8357
8398
  ];
8358
8399
  }
8359
8400
  if (audit === undefined) {
8401
+ if (rosterMissing === true) {
8402
+ return [
8403
+ " roster health:",
8404
+ ` ✗ ${OPENTEAM_ROSTER_PATH} does not exist — no team roster has been cast.`,
8405
+ " consequence: without a roster openteam cannot distribute work across roles, so every request is handled by a single agent and no work splitting happens.",
8406
+ ` remedy: run \`openteam setup\` to cast a team and write the roster (${OPENTEAM_ROSTER_PATH}), then re-run \`openteam doctor\`.`
8407
+ ];
8408
+ }
8360
8409
  return [];
8361
8410
  }
8362
8411
  const missing = auditFindings.filter((finding) => finding.kind === "missing-guaranteed-role").map((finding) => finding.roleID);
@@ -8392,6 +8441,14 @@ function rosterHealthSection(audit, loadError, rosterRoleCount) {
8392
8441
  }
8393
8442
  return lines;
8394
8443
  }
8444
+ function orchestratorAgentSection() {
8445
+ return [
8446
+ " orchestrator agent:",
8447
+ ` ✗ ${ORCHESTRATOR_AGENT_PATH} does not exist — opencode has no openteam coordinator agent.`,
8448
+ " consequence: this is the primary agent that reads the roster and calls `openteam-orchestrate` to fan work out across roles; without it opencode has no openteam coordinator, so every request is handled by a single agent and no work splitting happens.",
8449
+ ` remedy: run \`openteam setup\` to generate ${ORCHESTRATOR_AGENT_PATH}, then re-run \`openteam doctor\`.`
8450
+ ];
8451
+ }
8395
8452
  function gitignoreSection(gitignore) {
8396
8453
  if (gitignore.missingRules.length === 0) {
8397
8454
  return [
@@ -8455,7 +8512,10 @@ function renderDoctor(input) {
8455
8512
  if (input.diagnostics !== undefined) {
8456
8513
  lines.push(...diagnosticsSection(input.diagnostics));
8457
8514
  }
8458
- lines.push(...rosterHealthSection(input.rosterAudit, input.rosterLoadError, input.rosterRoleCount));
8515
+ lines.push(...rosterHealthSection(input.rosterAudit, input.rosterLoadError, input.rosterRoleCount, input.rosterMissing));
8516
+ if (input.orchestratorAgentMissing === true) {
8517
+ lines.push(...orchestratorAgentSection());
8518
+ }
8459
8519
  if (input.gitignore !== undefined) {
8460
8520
  lines.push(...gitignoreSection(input.gitignore));
8461
8521
  }
@@ -8539,14 +8599,38 @@ function leaseLine(lease) {
8539
8599
  const state = lease.released ? "released" : "active";
8540
8600
  return ` lease: ${lease.holder} → ${lease.runID} (${state})`;
8541
8601
  }
8542
- function renderGraphStatus(snapshot) {
8602
+ function appendAdvisoryLines(lines, advisories) {
8603
+ for (const advisory of advisories ?? []) {
8604
+ lines.push(` ⚠ advisory (non-blocking) ${advisory.code}: ${advisory.detail}`);
8605
+ }
8606
+ }
8607
+ function appendEffectiveLines(lines, gate) {
8608
+ switch (gate.kind) {
8609
+ case "not-applicable":
8610
+ return;
8611
+ case "passed":
8612
+ lines.push(" effective: active");
8613
+ appendAdvisoryLines(lines, gate.advisories);
8614
+ return;
8615
+ case "unevaluable":
8616
+ lines.push(" effective: unknown — could not evaluate the cutover gate");
8617
+ lines.push(` ${gate.reason}`);
8618
+ return;
8619
+ case "denied":
8620
+ lines.push(" effective: off — cutover gate denied");
8621
+ for (const violation of gate.violations) {
8622
+ lines.push(` • ${violation.code}: ${violation.detail}`);
8623
+ }
8624
+ appendAdvisoryLines(lines, gate.advisories);
8625
+ }
8626
+ }
8627
+ function renderGraphStatus(snapshot, gate = { kind: "not-applicable" }) {
8543
8628
  const lines = [
8544
8629
  "openteam graph status:",
8545
- ` config: ${snapshot.effectiveMode} (source: ${snapshot.source})`,
8546
- leaseLine(snapshot.lease),
8547
- ` journal: ${snapshot.journal}`,
8548
- ` effects: ${snapshot.pendingEffects} pending, ${snapshot.unknownEffects} unknown`
8630
+ ` config: ${snapshot.effectiveMode} (source: ${snapshot.source})`
8549
8631
  ];
8632
+ appendEffectiveLines(lines, gate);
8633
+ lines.push(leaseLine(snapshot.lease), ` journal: ${snapshot.journal}`, ` effects: ${snapshot.pendingEffects} pending, ${snapshot.unknownEffects} unknown`);
8550
8634
  const needsReconciliation = isRecoveryRequired(snapshot.journal, snapshot.unknownEffects);
8551
8635
  if (needsReconciliation) {
8552
8636
  lines.push(" ⚠ Reconciliation required: journal or effects in an uncertain state.");
@@ -9990,8 +10074,7 @@ async function runAgents(deps, configPath, opencodeConfigPaths, configResolution
9990
10074
  async function runGraph(positionals, deps, configPath, configResolution) {
9991
10075
  const sub = positionals[1];
9992
10076
  if (sub === "status") {
9993
- const snapshot = await deps.readGraphSnapshot(configPath, configResolution);
9994
- return { exitCode: 0, stdout: renderGraphStatus(snapshot) };
10077
+ return runGraphStatus(deps, configPath, configResolution);
9995
10078
  }
9996
10079
  if (sub === "cancel") {
9997
10080
  return runGraphCancel(positionals, deps, configPath, configResolution);
@@ -10046,7 +10129,9 @@ function renderGraphOutcome(outcome) {
10046
10129
  async function resolveOperatorGraphPolicy(config, operator) {
10047
10130
  let graph = resolveGraphConfig(config, "file");
10048
10131
  if (isGateApplicable(graph.effectiveMode)) {
10049
- graph = applyGraphGate(graph, await operator.evaluateCutoverGate());
10132
+ const evaluation = await operator.evaluateCutoverGate(config);
10133
+ const gate = evaluation.kind === "evaluated" ? evaluation.gate : { allowed: false, violations: [], advisories: [] };
10134
+ graph = applyGraphGate(graph, gate);
10050
10135
  }
10051
10136
  return {
10052
10137
  graph,
@@ -10064,6 +10149,36 @@ function checkOperatorGate(policy) {
10064
10149
  stdout: renderLegacyGraphReason(decision2)
10065
10150
  };
10066
10151
  }
10152
+ async function runGraphStatus(deps, configPath, configResolution) {
10153
+ const snapshot = await deps.readGraphSnapshot(configPath, configResolution);
10154
+ if (!isGateApplicable(snapshot.effectiveMode)) {
10155
+ return { exitCode: 0, stdout: renderGraphStatus(snapshot) };
10156
+ }
10157
+ const operator = deps.graphOperator;
10158
+ if (operator === undefined) {
10159
+ const gate2 = {
10160
+ kind: "unevaluable",
10161
+ reason: "graph operator ports are not configured"
10162
+ };
10163
+ return { exitCode: 0, stdout: renderGraphStatus(snapshot, gate2) };
10164
+ }
10165
+ const config = await deps.loadConfig(configPath, configResolution);
10166
+ const evaluation = await operator.evaluateCutoverGate(config);
10167
+ if (evaluation.kind === "unevaluable") {
10168
+ const gate2 = {
10169
+ kind: "unevaluable",
10170
+ reason: evaluation.reason
10171
+ };
10172
+ return { exitCode: 0, stdout: renderGraphStatus(snapshot, gate2) };
10173
+ }
10174
+ const resolved = applyGraphGate(resolveGraphConfig(config, "file"), evaluation.gate);
10175
+ const gate = resolved.gateDenied === true ? {
10176
+ kind: "denied",
10177
+ violations: resolved.gateViolations ?? [],
10178
+ advisories: evaluation.gate.advisories
10179
+ } : { kind: "passed", advisories: evaluation.gate.advisories };
10180
+ return { exitCode: 0, stdout: renderGraphStatus(snapshot, gate) };
10181
+ }
10067
10182
  async function runGraphCancel(positionals, deps, configPath, configResolution) {
10068
10183
  const runID = positionals[2];
10069
10184
  if (runID === undefined || runID.trim() === "") {
@@ -10510,6 +10625,9 @@ async function runCli(argv, deps) {
10510
10625
  const summary = summarizeQueueWaits(queueEvents);
10511
10626
  queueWait = summary !== undefined ? { summary } : {};
10512
10627
  }
10628
+ const rosterMissing = deps.loadRosterForDoctor !== undefined && roster === undefined && rosterLoadError === undefined;
10629
+ const orchestratorAgentName = ORCHESTRATOR_AGENT_PATH.slice(ORCHESTRATOR_AGENT_PATH.lastIndexOf("/") + 1).replace(/\.md$/, "");
10630
+ const orchestratorAgentMissing = !agentFiles.some((file) => file.name === orchestratorAgentName);
10513
10631
  return {
10514
10632
  exitCode: 0,
10515
10633
  stdout: renderDoctor({
@@ -10523,6 +10641,8 @@ async function runCli(argv, deps) {
10523
10641
  ...rosterAudit !== undefined ? { rosterAudit } : {},
10524
10642
  ...rosterRoleCount !== undefined ? { rosterRoleCount } : {},
10525
10643
  ...rosterLoadError !== undefined ? { rosterLoadError } : {},
10644
+ ...rosterMissing ? { rosterMissing } : {},
10645
+ ...orchestratorAgentMissing ? { orchestratorAgentMissing } : {},
10526
10646
  ...diagnostics !== undefined && diagnostics.length > 0 ? { diagnostics } : {},
10527
10647
  ...opencodeConfigError !== undefined ? { opencodeConfigError } : {},
10528
10648
  ...cacheEntries !== undefined ? { cacheEntries, cliVersion: deps.version } : {},
@@ -13387,7 +13507,7 @@ var createOtlpSpanExporter = (connection, config) => {
13387
13507
  // package.json
13388
13508
  var package_default = {
13389
13509
  name: "@jmanuelcorral/openteam",
13390
- version: "0.16.0",
13510
+ version: "0.16.2",
13391
13511
  packageManager: "bun@1.3.14",
13392
13512
  description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
13393
13513
  license: "MIT",
@@ -13446,7 +13566,7 @@ var package_default = {
13446
13566
  sideEffects: false,
13447
13567
  scripts: {
13448
13568
  prebuild: "bun run clean",
13449
- build: "bun run build:js && bun run build:cli && bun run build:types",
13569
+ build: "bun run build:js && bun run build:cli && bun run build:types && bun run build:certificates",
13450
13570
  "build:js": "bun build ./src/index.ts --target=node --format=esm --outfile=dist/index.js --external @opencode-ai/plugin --external @opencode-ai/sdk --external zod --external @opentelemetry/api --external @opentelemetry/sdk-trace-base --external @opentelemetry/exporter-trace-otlp-http --external @opentelemetry/resources --external @langchain/langgraph",
13451
13571
  "build:cli": 'bun build ./src/cli.ts --target=node --format=esm --outfile=dist/cli.js --banner "#!/usr/bin/env node" --external @opencode-ai/plugin --external @opencode-ai/sdk --external zod --external @clack/prompts --external @opentelemetry/api --external @opentelemetry/sdk-trace-base --external @opentelemetry/exporter-trace-otlp-http --external @opentelemetry/resources --external @langchain/langgraph',
13452
13572
  "build:types": "tsc -p tsconfig.build.json",
@@ -13455,6 +13575,7 @@ var package_default = {
13455
13575
  "test:cov": "bun test --coverage",
13456
13576
  "certify:shadow": "bun run scripts/certify-artifact.ts shadow",
13457
13577
  "certify:release": "bun run scripts/certify-artifact.ts release",
13578
+ "build:certificates": "bun run certify:shadow && bun run certify:release && bun run scripts/bundle-certificates.ts",
13458
13579
  "coverage:check": "node scripts/check-coverage.mjs",
13459
13580
  typecheck: "tsc --noEmit",
13460
13581
  lint: "biome check .",
@@ -13563,9 +13684,16 @@ function warnSoakEvidenceRejections(result, warn) {
13563
13684
  var SHADOW_CERT_PATH = "artifacts/graph-shadow-certificate.json";
13564
13685
  var RELEASE_CERT_PATH = "artifacts/graph-release-certificate.json";
13565
13686
  var SOAK_CERT_PATH = "artifacts/graph-soak-certificate.json";
13566
- async function readAndParseCertificate(storage, path4, parse2, digestExtractor) {
13687
+ async function readPackagedCertificate(path4) {
13688
+ try {
13689
+ return await readFile2(new URL(`./certificates/${basename2(path4)}`, import.meta.url), "utf8");
13690
+ } catch {
13691
+ return;
13692
+ }
13693
+ }
13694
+ async function readAndParseCertificate(storage, path4, parse2, digestExtractor, fallbackRead) {
13567
13695
  try {
13568
- const content = await storage.read(path4);
13696
+ const content = await storage.read(path4) ?? (fallbackRead === undefined ? undefined : await fallbackRead(path4));
13569
13697
  if (content === undefined) {
13570
13698
  return { status: "absent" };
13571
13699
  }
@@ -13733,24 +13861,30 @@ function createLazyOpencodeVersionReader(serverUrl, options = {}) {
13733
13861
  }
13734
13862
  };
13735
13863
  }
13736
- async function buildGateInput(config, storage, opencodeVersion, warn) {
13737
- const shadow = await readAndParseCertificate(storage, SHADOW_CERT_PATH, (raw) => parseShadowCertificate(raw, opencodeVersion), (cert) => shadowCertificateBindingDigest(cert.evidence));
13864
+ async function buildGateInput(config, storage, opencodeVersion, warn, packagedRead = readPackagedCertificate) {
13865
+ const shadow = await readAndParseCertificate(storage, SHADOW_CERT_PATH, (raw) => parseShadowCertificate(raw, opencodeVersion), (cert) => shadowCertificateBindingDigest(cert.evidence), packagedRead);
13738
13866
  const shadowDigest = shadow.status === "valid" ? shadow.digest : "0".repeat(64);
13739
- const release = await readAndParseCertificate(storage, RELEASE_CERT_PATH, (raw) => parseReleaseCertificate(raw, opencodeVersion, shadowDigest), (cert) => cert.digest);
13867
+ const release = await readAndParseCertificate(storage, RELEASE_CERT_PATH, (raw) => parseReleaseCertificate(raw, opencodeVersion, shadowDigest), (cert) => cert.digest, packagedRead);
13740
13868
  let soakLedgerDigest = "0".repeat(64);
13741
13869
  try {
13742
13870
  const recorderPorts = recorderPortsFromStorage(storage);
13743
13871
  const chainRead = await readAllChains(recorderPorts, SOAK_EVIDENCE_DIR);
13744
13872
  warnSoakEvidenceRejections(chainRead, warn);
13745
- if (chainRead.chains.length > 0) {
13746
- soakLedgerDigest = canonicalLedger(chainRead.chains);
13747
- }
13873
+ soakLedgerDigest = canonicalLedger(chainRead.chains);
13748
13874
  } catch {}
13749
13875
  const soak = await readAndParseCertificate(storage, SOAK_CERT_PATH, (raw) => parseSoakCertificate(raw, DEFAULT_SOAK_POLICY, soakLedgerDigest), (cert) => cert.digest);
13750
13876
  const migration = "complete";
13751
13877
  const operatorApproval = config.graph?.operatorApproval ?? false;
13752
13878
  return { shadow, release, soak, migration, operatorApproval };
13753
13879
  }
13880
+ var OPENCODE_VERSION_UNRESOLVED_REASON = "the opencode version is not determinable outside a running opencode session, " + "so the cutover gate cannot be verified here — the running plugin logs the " + "authoritative gate result and warns loudly on a real denial";
13881
+ async function evaluateCutoverGateForBoundary(config, storage, version) {
13882
+ if (version === undefined) {
13883
+ return { kind: "unevaluable", reason: OPENCODE_VERSION_UNRESOLVED_REASON };
13884
+ }
13885
+ const gateInput = await buildGateInput(config, storage, version, () => {});
13886
+ return { kind: "evaluated", gate: evaluateGraphGate(gateInput) };
13887
+ }
13754
13888
  function recorderPortsFromStorage(storage) {
13755
13889
  return {
13756
13890
  fs: {
@@ -13838,7 +13972,7 @@ async function readStoredRosterForDoctor(storage) {
13838
13972
  }
13839
13973
  }
13840
13974
  var auditStoredRosterForDoctor = (roster, configuredRoles) => typeof roster === "string" ? auditRoster(roster, configuredRoles) : auditRoster(roster, configuredRoles);
13841
- function createCliDeps(config, registry, telemetryPath, sessionsDir = DEFAULT_SESSIONS_DIR, storage = createFsStorageProvider(process.cwd()), fallbackSource = "options", rawOptions) {
13975
+ function createCliDeps(config, registry, telemetryPath, sessionsDir = DEFAULT_SESSIONS_DIR, storage = createFsStorageProvider(process.cwd()), fallbackSource = "options", rawOptions, resolveOpencodeVersion) {
13842
13976
  return {
13843
13977
  loadConfig: async (path4, resolution = { explicit: false }) => {
13844
13978
  const content = await readCliConfigFromStorage(storage, path4, resolution);
@@ -13912,7 +14046,8 @@ function createCliDeps(config, registry, telemetryPath, sessionsDir = DEFAULT_SE
13912
14046
  }),
13913
14047
  graphOperator: createGraphOperator({
13914
14048
  storage,
13915
- holder: "openteam-cli"
14049
+ holder: "openteam-cli",
14050
+ evaluateCutoverGate: async (gateConfig) => evaluateCutoverGateForBoundary(gateConfig, storage, resolveOpencodeVersion === undefined ? undefined : await resolveOpencodeVersion())
13916
14051
  }),
13917
14052
  configPath: DEFAULT_CONFIG_PATH,
13918
14053
  telemetryPath,
@@ -14042,7 +14177,14 @@ var server = async (ctx, rawOptions) => {
14042
14177
  injectMemory,
14043
14178
  onRoute: (sid, model) => sessionTracker.trackRoutedModel(sid, model)
14044
14179
  });
14045
- const cliDeps = createCliDeps(config, registry, telemetryPath, sessionsDir, storage, runtimeConfig.source, rawOptions);
14180
+ const getOpencodeVersion = createLazyOpencodeVersionReader(ctx.serverUrl, {
14181
+ client: ctx.client,
14182
+ onFailure: createOpencodeVersionReadFailureLogger(MAX_LOGGED_OPENCODE_VERSION_FAILURE_CAUSES, diagnostics.warn("opencode-version-read-failed", { toast: false }))
14183
+ });
14184
+ const cliDeps = createCliDeps(config, registry, telemetryPath, sessionsDir, storage, runtimeConfig.source, rawOptions, async () => {
14185
+ const version = await getOpencodeVersion();
14186
+ return version === "unknown" ? undefined : version;
14187
+ });
14046
14188
  const toolcalls = createToolcallTracker({ sink });
14047
14189
  const announcedSessions = new Set;
14048
14190
  const announceEndpoint = (sessionID) => {
@@ -14065,10 +14207,6 @@ var server = async (ctx, rawOptions) => {
14065
14207
  if (config.privacyMode === "forceLocalOnSensitive" && localDefault(config) === null) {
14066
14208
  warnInertPrivacySetting(runtimeConfig.searchedPaths, diagnostics.warn("privacy-inert"));
14067
14209
  }
14068
- const getOpencodeVersion = createLazyOpencodeVersionReader(ctx.serverUrl, {
14069
- client: ctx.client,
14070
- onFailure: createOpencodeVersionReadFailureLogger(MAX_LOGGED_OPENCODE_VERSION_FAILURE_CAUSES, diagnostics.warn("opencode-version-read-failed", { toast: false }))
14071
- });
14072
14210
  if (isGateApplicable(graphConfig.effectiveMode)) {
14073
14211
  const opencodeVersion = await getOpencodeVersion();
14074
14212
  const gateInput = await buildGateInput(config, storage, opencodeVersion, diagnostics.warn("soak-evidence-rejected", { toast: false }));
@@ -14227,12 +14365,14 @@ export {
14227
14365
  warnInertPrivacySetting,
14228
14366
  server,
14229
14367
  recorderPortsFromStorage,
14368
+ readPackagedCertificate,
14230
14369
  readOpencodeVersion,
14231
14370
  logTelemetryWarning,
14232
14371
  logTelemetryError,
14233
14372
  logOpencodeServerUrl,
14234
14373
  logAvailabilityRefreshError,
14235
14374
  isMissingFile,
14375
+ evaluateCutoverGateForBoundary,
14236
14376
  src_default as default,
14237
14377
  createOpencodeVersionReadFailureLogger,
14238
14378
  createLazyOpencodeVersionReader,
@@ -14240,5 +14380,6 @@ export {
14240
14380
  createCliDeps,
14241
14381
  buildGateInput,
14242
14382
  PACKAGE_VERSION,
14383
+ OPENCODE_VERSION_UNRESOLVED_REASON,
14243
14384
  MAX_LOGGED_OPENCODE_VERSION_FAILURE_CAUSES
14244
14385
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jmanuelcorral/openteam",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
4
4
  "packageManager": "bun@1.3.14",
5
5
  "description": "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
6
6
  "license": "MIT",
@@ -59,7 +59,7 @@
59
59
  "sideEffects": false,
60
60
  "scripts": {
61
61
  "prebuild": "bun run clean",
62
- "build": "bun run build:js && bun run build:cli && bun run build:types",
62
+ "build": "bun run build:js && bun run build:cli && bun run build:types && bun run build:certificates",
63
63
  "build:js": "bun build ./src/index.ts --target=node --format=esm --outfile=dist/index.js --external @opencode-ai/plugin --external @opencode-ai/sdk --external zod --external @opentelemetry/api --external @opentelemetry/sdk-trace-base --external @opentelemetry/exporter-trace-otlp-http --external @opentelemetry/resources --external @langchain/langgraph",
64
64
  "build:cli": "bun build ./src/cli.ts --target=node --format=esm --outfile=dist/cli.js --banner \"#!/usr/bin/env node\" --external @opencode-ai/plugin --external @opencode-ai/sdk --external zod --external @clack/prompts --external @opentelemetry/api --external @opentelemetry/sdk-trace-base --external @opentelemetry/exporter-trace-otlp-http --external @opentelemetry/resources --external @langchain/langgraph",
65
65
  "build:types": "tsc -p tsconfig.build.json",
@@ -68,6 +68,7 @@
68
68
  "test:cov": "bun test --coverage",
69
69
  "certify:shadow": "bun run scripts/certify-artifact.ts shadow",
70
70
  "certify:release": "bun run scripts/certify-artifact.ts release",
71
+ "build:certificates": "bun run certify:shadow && bun run certify:release && bun run scripts/bundle-certificates.ts",
71
72
  "coverage:check": "node scripts/check-coverage.mjs",
72
73
  "typecheck": "tsc --noEmit",
73
74
  "lint": "biome check .",