@jmanuelcorral/openteam 0.15.1 → 0.16.1

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/cli.js CHANGED
@@ -3,8 +3,9 @@ import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // src/cli.ts
6
- import { execFile as execFile2 } from "node:child_process";
6
+ import { exec, execFile as execFile2 } from "node:child_process";
7
7
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
8
+ import { createRequire as createRequire2 } from "node:module";
8
9
  import { dirname as dirname5, resolve as resolve6 } from "node:path";
9
10
  import { promisify as promisify2 } from "node:util";
10
11
  import {
@@ -448,7 +449,8 @@ var LocalRuntimeSchema = z2.object({
448
449
  baseURL: z2.string().url().optional(),
449
450
  discovery: z2.enum(["cli", "sdk", "manual"]).optional(),
450
451
  defaultModel: ModelRefSchema,
451
- maxConcurrency: z2.number().int().positive().optional()
452
+ maxConcurrency: z2.number().int().positive().optional(),
453
+ reservedInteractiveSlots: z2.number().int().nonnegative().optional()
452
454
  }).strict().superRefine((runtime, ctx) => {
453
455
  const resolvedKind = runtime.kind ?? runtime.id;
454
456
  if (!LocalRuntimeKindSchema.safeParse(resolvedKind).success) {
@@ -1900,6 +1902,9 @@ function warnUnknownRole(roleID, warn, warnedRoles) {
1900
1902
  function roleFor(input) {
1901
1903
  return getRoleProfile(input.roleID, input.config.orchestrator?.roles) ?? synthesiseFallbackProfile(input.roleID);
1902
1904
  }
1905
+ function priorityForRole(role) {
1906
+ return role.roleID === "scribe" ? "background" : "worker";
1907
+ }
1903
1908
  function mandatoryLocalRuntimeMessage(roleID) {
1904
1909
  return `openteam: role "${roleID}" requires a local runtime, but no local runtime is reachable. ` + `This role must run locally and will not fall back to a frontier provider. ` + `Start one of the supported local runtimes (Ollama, LM Studio, or Lemonade) and try again. ` + "Run `openteam doctor` to diagnose your local runtime configuration.";
1905
1910
  }
@@ -2258,10 +2263,11 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch) {
2258
2263
  let queueObservation;
2259
2264
  if (runtimeId !== undefined && deps.runtimeLimiter !== undefined) {
2260
2265
  const limiter = deps.runtimeLimiter;
2261
- release = limiter.tryAcquire(runtimeId);
2266
+ const priority = priorityForRole(role);
2267
+ release = limiter.tryAcquire(runtimeId, priority);
2262
2268
  let waitedMs = 0;
2263
2269
  if (release === undefined) {
2264
- const acquisition = await limiter.acquire(runtimeId);
2270
+ const acquisition = await limiter.acquire(runtimeId, priority);
2265
2271
  release = acquisition.release;
2266
2272
  waitedMs = acquisition.waitedMs;
2267
2273
  }
@@ -3069,7 +3075,8 @@ var KNOWN_CONFIG_KEYS_BY_PATH = new Map([
3069
3075
  "baseURL",
3070
3076
  "discovery",
3071
3077
  "defaultModel",
3072
- "maxConcurrency"
3078
+ "maxConcurrency",
3079
+ "reservedInteractiveSlots"
3073
3080
  ]
3074
3081
  ],
3075
3082
  ["local.runtimes[].defaultModel", MODEL_REF_KEYS],
@@ -8546,6 +8553,10 @@ function recoverJournal(spec, text) {
8546
8553
 
8547
8554
  // src/cli/graphOperator.ts
8548
8555
  var ALLOWED_GATE = { allowed: true, violations: [] };
8556
+ var ALLOWED_EVALUATION = {
8557
+ kind: "evaluated",
8558
+ gate: ALLOWED_GATE
8559
+ };
8549
8560
  function journalRoot(config) {
8550
8561
  return config.graph?.journalRoot ?? DEFAULT_GRAPH_JOURNAL_ROOT;
8551
8562
  }
@@ -8635,7 +8646,7 @@ async function listWorktreeObservations(config, options) {
8635
8646
  })));
8636
8647
  }
8637
8648
  function createGraphOperator(options) {
8638
- const evaluateCutoverGate = options.evaluateCutoverGate ?? (async () => ALLOWED_GATE);
8649
+ const evaluateCutoverGate = options.evaluateCutoverGate ?? (async () => ALLOWED_EVALUATION);
8639
8650
  const worktreeOptions = options.exec === undefined ? undefined : {
8640
8651
  exec: options.exec,
8641
8652
  repoRoot: options.repoRoot ?? process.cwd(),
@@ -8914,6 +8925,35 @@ function createMigrateAdapter(root) {
8914
8925
  };
8915
8926
  }
8916
8927
 
8928
+ // src/cli/opencodeVersion.ts
8929
+ var SEMVER_PATTERN = /(\d+\.\d+\.\d+)/;
8930
+ function normalizeVersion(raw) {
8931
+ if (raw === undefined) {
8932
+ return;
8933
+ }
8934
+ const trimmed = raw.trim();
8935
+ if (trimmed === "" || trimmed.toLowerCase() === "unknown") {
8936
+ return;
8937
+ }
8938
+ const match = SEMVER_PATTERN.exec(trimmed);
8939
+ return match?.[1] ?? undefined;
8940
+ }
8941
+ async function resolveOpencodeVersionBestEffort(deps) {
8942
+ try {
8943
+ const fromPackage = normalizeVersion(deps.readPackageVersion("opencode-ai"));
8944
+ if (fromPackage !== undefined) {
8945
+ return fromPackage;
8946
+ }
8947
+ } catch {}
8948
+ try {
8949
+ const fromCommand = normalizeVersion(await deps.runVersionCommand());
8950
+ if (fromCommand !== undefined) {
8951
+ return fromCommand;
8952
+ }
8953
+ } catch {}
8954
+ return;
8955
+ }
8956
+
8917
8957
  // src/cli/purgeAdapter.ts
8918
8958
  import { execFile } from "node:child_process";
8919
8959
  import * as fs2 from "node:fs";
@@ -10233,6 +10273,54 @@ function createProbeRuntime(exec) {
10233
10273
  }
10234
10274
 
10235
10275
  // src/config/graphFeatureGate.ts
10276
+ function checkCertificate(status, name) {
10277
+ if (status.status === "absent") {
10278
+ return {
10279
+ code: `${name}-certificate-absent`,
10280
+ detail: `${name} certificate not found — run the ${name} certification suite to produce it`
10281
+ };
10282
+ }
10283
+ if (status.status === "invalid") {
10284
+ return {
10285
+ code: `${name}-certificate-invalid`,
10286
+ detail: `${name} certificate rejected by recomputing parser: ${status.code} — ${status.detail}`
10287
+ };
10288
+ }
10289
+ return;
10290
+ }
10291
+ function evaluateGraphGate(input) {
10292
+ const violations = [];
10293
+ const shadowV = checkCertificate(input.shadow, "shadow");
10294
+ if (shadowV !== undefined)
10295
+ violations.push(shadowV);
10296
+ const releaseV = checkCertificate(input.release, "release");
10297
+ if (releaseV !== undefined)
10298
+ violations.push(releaseV);
10299
+ const soakV = checkCertificate(input.soak, "soak");
10300
+ if (soakV !== undefined)
10301
+ violations.push(soakV);
10302
+ if (input.migration === "pending") {
10303
+ violations.push({
10304
+ code: "migration-pending",
10305
+ detail: "legacy migration has pending work — run the migration tool to completion before enabling active mode"
10306
+ });
10307
+ } else if (input.migration === "unknown") {
10308
+ violations.push({
10309
+ code: "migration-unknown",
10310
+ detail: "could not determine migration status — check ledger readability and storage access"
10311
+ });
10312
+ }
10313
+ if (!input.operatorApproval) {
10314
+ violations.push({
10315
+ code: "operator-approval-missing",
10316
+ detail: "set graph.operatorApproval to true in .opencode/openteam.json after reviewing all certificates and completing migration — this is a declaration of intent, not a proof"
10317
+ });
10318
+ }
10319
+ return {
10320
+ allowed: violations.length === 0,
10321
+ violations
10322
+ };
10323
+ }
10236
10324
  function isGateApplicable(mode) {
10237
10325
  return mode === "active";
10238
10326
  }
@@ -11051,7 +11139,7 @@ function legacyRosterFindings(audit) {
11051
11139
  }
11052
11140
  return findings;
11053
11141
  }
11054
- function rosterHealthSection(audit, loadError, rosterRoleCount) {
11142
+ function rosterHealthSection(audit, loadError, rosterRoleCount, rosterMissing) {
11055
11143
  const auditFindings = audit?.findings ?? legacyRosterFindings(audit ?? {});
11056
11144
  const unparseable = auditFindings.find((finding) => finding.kind === "unparseable-roster");
11057
11145
  const unparseableMessage = loadError ?? unparseable?.message;
@@ -11064,6 +11152,14 @@ function rosterHealthSection(audit, loadError, rosterRoleCount) {
11064
11152
  ];
11065
11153
  }
11066
11154
  if (audit === undefined) {
11155
+ if (rosterMissing === true) {
11156
+ return [
11157
+ " roster health:",
11158
+ ` ✗ ${OPENTEAM_ROSTER_PATH} does not exist — no team roster has been cast.`,
11159
+ " consequence: without a roster openteam cannot distribute work across roles, so every request is handled by a single agent and no work splitting happens.",
11160
+ ` remedy: run \`openteam setup\` to cast a team and write the roster (${OPENTEAM_ROSTER_PATH}), then re-run \`openteam doctor\`.`
11161
+ ];
11162
+ }
11067
11163
  return [];
11068
11164
  }
11069
11165
  const missing = auditFindings.filter((finding) => finding.kind === "missing-guaranteed-role").map((finding) => finding.roleID);
@@ -11099,6 +11195,14 @@ function rosterHealthSection(audit, loadError, rosterRoleCount) {
11099
11195
  }
11100
11196
  return lines;
11101
11197
  }
11198
+ function orchestratorAgentSection() {
11199
+ return [
11200
+ " orchestrator agent:",
11201
+ ` ✗ ${ORCHESTRATOR_AGENT_PATH} does not exist — opencode has no openteam coordinator agent.`,
11202
+ " 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.",
11203
+ ` remedy: run \`openteam setup\` to generate ${ORCHESTRATOR_AGENT_PATH}, then re-run \`openteam doctor\`.`
11204
+ ];
11205
+ }
11102
11206
  function gitignoreSection(gitignore) {
11103
11207
  if (gitignore.missingRules.length === 0) {
11104
11208
  return [
@@ -11162,7 +11266,10 @@ function renderDoctor(input) {
11162
11266
  if (input.diagnostics !== undefined) {
11163
11267
  lines.push(...diagnosticsSection(input.diagnostics));
11164
11268
  }
11165
- lines.push(...rosterHealthSection(input.rosterAudit, input.rosterLoadError, input.rosterRoleCount));
11269
+ lines.push(...rosterHealthSection(input.rosterAudit, input.rosterLoadError, input.rosterRoleCount, input.rosterMissing));
11270
+ if (input.orchestratorAgentMissing === true) {
11271
+ lines.push(...orchestratorAgentSection());
11272
+ }
11166
11273
  if (input.gitignore !== undefined) {
11167
11274
  lines.push(...gitignoreSection(input.gitignore));
11168
11275
  }
@@ -11231,14 +11338,31 @@ function leaseLine(lease) {
11231
11338
  const state = lease.released ? "released" : "active";
11232
11339
  return ` lease: ${lease.holder} → ${lease.runID} (${state})`;
11233
11340
  }
11234
- function renderGraphStatus(snapshot) {
11341
+ function appendEffectiveLines(lines, gate) {
11342
+ switch (gate.kind) {
11343
+ case "not-applicable":
11344
+ return;
11345
+ case "passed":
11346
+ lines.push(" effective: active");
11347
+ return;
11348
+ case "unevaluable":
11349
+ lines.push(" effective: unknown — could not evaluate the cutover gate");
11350
+ lines.push(` ${gate.reason}`);
11351
+ return;
11352
+ case "denied":
11353
+ lines.push(" effective: off — cutover gate denied");
11354
+ for (const violation of gate.violations) {
11355
+ lines.push(` • ${violation.code}: ${violation.detail}`);
11356
+ }
11357
+ }
11358
+ }
11359
+ function renderGraphStatus(snapshot, gate = { kind: "not-applicable" }) {
11235
11360
  const lines = [
11236
11361
  "openteam graph status:",
11237
- ` config: ${snapshot.effectiveMode} (source: ${snapshot.source})`,
11238
- leaseLine(snapshot.lease),
11239
- ` journal: ${snapshot.journal}`,
11240
- ` effects: ${snapshot.pendingEffects} pending, ${snapshot.unknownEffects} unknown`
11362
+ ` config: ${snapshot.effectiveMode} (source: ${snapshot.source})`
11241
11363
  ];
11364
+ appendEffectiveLines(lines, gate);
11365
+ lines.push(leaseLine(snapshot.lease), ` journal: ${snapshot.journal}`, ` effects: ${snapshot.pendingEffects} pending, ${snapshot.unknownEffects} unknown`);
11242
11366
  const needsReconciliation = isRecoveryRequired(snapshot.journal, snapshot.unknownEffects);
11243
11367
  if (needsReconciliation) {
11244
11368
  lines.push(" ⚠ Reconciliation required: journal or effects in an uncertain state.");
@@ -11489,11 +11613,152 @@ function mergeOpencodeConfigs(base, override) {
11489
11613
  return result;
11490
11614
  }
11491
11615
 
11616
+ // src/graph/soakLedger.ts
11617
+ import { z as z19 } from "zod";
11618
+ var Sha256Schema2 = z19.string().regex(/^[0-9a-f]{64}$/);
11619
+ var SoakPlatformSchema = z19.enum(["linux", "win32", "darwin"]);
11620
+ var PrivacyPairSchema = z19.object({
11621
+ surfacesScanned: z19.number().int().nonnegative(),
11622
+ rawFindings: z19.number().int().nonnegative()
11623
+ }).strict();
11624
+ var DuplicateEffectPairSchema = z19.object({
11625
+ effectsExamined: z19.number().int().nonnegative(),
11626
+ duplicatesFound: z19.number().int().nonnegative()
11627
+ }).strict();
11628
+ var ModelVerificationPairSchema = z19.object({
11629
+ nodesChecked: z19.number().int().nonnegative(),
11630
+ unverified: z19.number().int().nonnegative(),
11631
+ mismatched: z19.number().int().nonnegative()
11632
+ }).strict();
11633
+ var SoakObservationSchema = z19.object({
11634
+ version: z19.literal(1),
11635
+ seq: z19.number().int().nonnegative(),
11636
+ timestamp: z19.string().datetime(),
11637
+ platform: SoakPlatformSchema,
11638
+ opencodeVersion: z19.string().min(1),
11639
+ provenance: z19.enum(["genuine-usage", "ci-synthetic"]),
11640
+ traceDigest: Sha256Schema2,
11641
+ criticalDivergences: z19.number().int().nonnegative().nullable(),
11642
+ privacy: PrivacyPairSchema.nullable(),
11643
+ duplicateEffects: DuplicateEffectPairSchema.nullable(),
11644
+ modelVerification: ModelVerificationPairSchema.nullable(),
11645
+ chainDigest: Sha256Schema2
11646
+ }).strict();
11647
+ function canonicalObservationPreimage(obs) {
11648
+ return JSON.stringify({
11649
+ version: obs.version,
11650
+ seq: obs.seq,
11651
+ timestamp: obs.timestamp,
11652
+ platform: obs.platform,
11653
+ opencodeVersion: obs.opencodeVersion,
11654
+ provenance: obs.provenance,
11655
+ traceDigest: obs.traceDigest,
11656
+ criticalDivergences: obs.criticalDivergences,
11657
+ privacy: obs.privacy,
11658
+ duplicateEffects: obs.duplicateEffects,
11659
+ modelVerification: obs.modelVerification
11660
+ });
11661
+ }
11662
+ function validateChain(observations) {
11663
+ if (observations.length === 0) {
11664
+ return { valid: true, observations: [] };
11665
+ }
11666
+ let prevChainDigest = "0".repeat(64);
11667
+ for (let i = 0;i < observations.length; i++) {
11668
+ const obs = observations[i];
11669
+ if (obs.seq !== i) {
11670
+ return { valid: false, observations };
11671
+ }
11672
+ if (i > 0) {
11673
+ const prev = observations[i - 1];
11674
+ if (obs.timestamp <= prev.timestamp) {
11675
+ return { valid: false, observations };
11676
+ }
11677
+ }
11678
+ const preimage = canonicalObservationPreimage(obs);
11679
+ const expectedDigest = sha256Hex(prevChainDigest + preimage);
11680
+ if (obs.chainDigest !== expectedDigest) {
11681
+ return { valid: false, observations };
11682
+ }
11683
+ prevChainDigest = obs.chainDigest;
11684
+ }
11685
+ return { valid: true, observations };
11686
+ }
11687
+ function canonicalLedger(chains) {
11688
+ const validChains = [];
11689
+ for (const chain of chains) {
11690
+ const result = validateChain(chain);
11691
+ if (result.valid && chain.length > 0) {
11692
+ validChains.push(chain);
11693
+ }
11694
+ }
11695
+ validChains.sort((a, b) => {
11696
+ const aDigest = a[0].chainDigest;
11697
+ const bDigest = b[0].chainDigest;
11698
+ return aDigest < bDigest ? -1 : aDigest > bDigest ? 1 : 0;
11699
+ });
11700
+ const lines = [];
11701
+ for (const chain of validChains) {
11702
+ for (const obs of chain) {
11703
+ lines.push(JSON.stringify(obs));
11704
+ }
11705
+ }
11706
+ return sha256Hex(lines.join(`
11707
+ `));
11708
+ }
11709
+
11492
11710
  // src/storage/graph/soakRecorder.ts
11493
11711
  var SOAK_EVIDENCE_DIR = ".opencode/soak";
11494
11712
  var SOAK_RECORDER_BASE_DIR = ".opencode";
11495
11713
  var SOAK_RECORDER_ID_FILENAME = "soak-recorder-id";
11496
11714
  var SOAK_RECORDER_ID_PATH = `${SOAK_RECORDER_BASE_DIR}/${SOAK_RECORDER_ID_FILENAME}`;
11715
+ async function readAllChains(ports, evidenceDir) {
11716
+ if (!await ports.fs.exists(evidenceDir)) {
11717
+ return { chains: [], rejectedLines: 0, rejections: [] };
11718
+ }
11719
+ const files = await ports.fs.readDir(evidenceDir);
11720
+ const chains = [];
11721
+ const rejections = [];
11722
+ for (const file of files) {
11723
+ if (!file.endsWith(".jsonl")) {
11724
+ continue;
11725
+ }
11726
+ const content = await ports.fs.readFile(`${evidenceDir}/${file}`);
11727
+ const lines = content.split(`
11728
+ `);
11729
+ const observations = [];
11730
+ for (const [index, line] of lines.entries()) {
11731
+ if (line.trim().length === 0) {
11732
+ continue;
11733
+ }
11734
+ const lineNumber = index + 1;
11735
+ let rawParsed;
11736
+ try {
11737
+ rawParsed = JSON.parse(line);
11738
+ } catch {
11739
+ rejections.push({ file, lineNumber, detail: "invalid JSON" });
11740
+ continue;
11741
+ }
11742
+ const parsed = SoakObservationSchema.safeParse(rawParsed);
11743
+ if (parsed.success) {
11744
+ observations.push(parsed.data);
11745
+ } else {
11746
+ rejections.push({
11747
+ file,
11748
+ lineNumber,
11749
+ detail: formatSoakObservationIssues(parsed.error.issues)
11750
+ });
11751
+ }
11752
+ }
11753
+ if (observations.length > 0) {
11754
+ chains.push(observations);
11755
+ }
11756
+ }
11757
+ return { chains, rejectedLines: rejections.length, rejections };
11758
+ }
11759
+ function formatSoakObservationIssues(issues) {
11760
+ return issues.map((i) => `${i.path.join(".")}: ${i.code}`).join("; ");
11761
+ }
11497
11762
 
11498
11763
  // src/commands/frontierCatalog.ts
11499
11764
  function weightedCost(inputUSD, outputUSD) {
@@ -13266,8 +13531,7 @@ async function runAgents(deps, configPath, opencodeConfigPaths, configResolution
13266
13531
  async function runGraph(positionals, deps, configPath, configResolution) {
13267
13532
  const sub = positionals[1];
13268
13533
  if (sub === "status") {
13269
- const snapshot = await deps.readGraphSnapshot(configPath, configResolution);
13270
- return { exitCode: 0, stdout: renderGraphStatus(snapshot) };
13534
+ return runGraphStatus(deps, configPath, configResolution);
13271
13535
  }
13272
13536
  if (sub === "cancel") {
13273
13537
  return runGraphCancel(positionals, deps, configPath, configResolution);
@@ -13322,7 +13586,9 @@ function renderGraphOutcome(outcome) {
13322
13586
  async function resolveOperatorGraphPolicy(config, operator) {
13323
13587
  let graph = resolveGraphConfig(config, "file");
13324
13588
  if (isGateApplicable(graph.effectiveMode)) {
13325
- graph = applyGraphGate(graph, await operator.evaluateCutoverGate());
13589
+ const evaluation = await operator.evaluateCutoverGate(config);
13590
+ const gate = evaluation.kind === "evaluated" ? evaluation.gate : { allowed: false, violations: [] };
13591
+ graph = applyGraphGate(graph, gate);
13326
13592
  }
13327
13593
  return {
13328
13594
  graph,
@@ -13340,6 +13606,32 @@ function checkOperatorGate(policy) {
13340
13606
  stdout: renderLegacyGraphReason(decision2)
13341
13607
  };
13342
13608
  }
13609
+ async function runGraphStatus(deps, configPath, configResolution) {
13610
+ const snapshot = await deps.readGraphSnapshot(configPath, configResolution);
13611
+ if (!isGateApplicable(snapshot.effectiveMode)) {
13612
+ return { exitCode: 0, stdout: renderGraphStatus(snapshot) };
13613
+ }
13614
+ const operator = deps.graphOperator;
13615
+ if (operator === undefined) {
13616
+ const gate2 = {
13617
+ kind: "unevaluable",
13618
+ reason: "graph operator ports are not configured"
13619
+ };
13620
+ return { exitCode: 0, stdout: renderGraphStatus(snapshot, gate2) };
13621
+ }
13622
+ const config = await deps.loadConfig(configPath, configResolution);
13623
+ const evaluation = await operator.evaluateCutoverGate(config);
13624
+ if (evaluation.kind === "unevaluable") {
13625
+ const gate2 = {
13626
+ kind: "unevaluable",
13627
+ reason: evaluation.reason
13628
+ };
13629
+ return { exitCode: 0, stdout: renderGraphStatus(snapshot, gate2) };
13630
+ }
13631
+ const resolved = applyGraphGate(resolveGraphConfig(config, "file"), evaluation.gate);
13632
+ const gate = resolved.gateDenied === true ? { kind: "denied", violations: resolved.gateViolations ?? [] } : { kind: "passed" };
13633
+ return { exitCode: 0, stdout: renderGraphStatus(snapshot, gate) };
13634
+ }
13343
13635
  async function runGraphCancel(positionals, deps, configPath, configResolution) {
13344
13636
  const runID = positionals[2];
13345
13637
  if (runID === undefined || runID.trim() === "") {
@@ -13786,6 +14078,9 @@ async function runCli(argv, deps) {
13786
14078
  const summary = summarizeQueueWaits(queueEvents);
13787
14079
  queueWait = summary !== undefined ? { summary } : {};
13788
14080
  }
14081
+ const rosterMissing = deps.loadRosterForDoctor !== undefined && roster === undefined && rosterLoadError === undefined;
14082
+ const orchestratorAgentName = ORCHESTRATOR_AGENT_PATH.slice(ORCHESTRATOR_AGENT_PATH.lastIndexOf("/") + 1).replace(/\.md$/, "");
14083
+ const orchestratorAgentMissing = !agentFiles.some((file) => file.name === orchestratorAgentName);
13789
14084
  return {
13790
14085
  exitCode: 0,
13791
14086
  stdout: renderDoctor({
@@ -13799,6 +14094,8 @@ async function runCli(argv, deps) {
13799
14094
  ...rosterAudit !== undefined ? { rosterAudit } : {},
13800
14095
  ...rosterRoleCount !== undefined ? { rosterRoleCount } : {},
13801
14096
  ...rosterLoadError !== undefined ? { rosterLoadError } : {},
14097
+ ...rosterMissing ? { rosterMissing } : {},
14098
+ ...orchestratorAgentMissing ? { orchestratorAgentMissing } : {},
13802
14099
  ...diagnostics !== undefined && diagnostics.length > 0 ? { diagnostics } : {},
13803
14100
  ...opencodeConfigError !== undefined ? { opencodeConfigError } : {},
13804
14101
  ...cacheEntries !== undefined ? { cacheEntries, cliVersion: deps.version } : {},
@@ -14252,469 +14549,1037 @@ function planStartupHygieneMigration(exists) {
14252
14549
  return planLegacyMigration(exists).filter((move) => move.portable === false && move.relocation === "move" && move.from.startsWith(".opencode/openteam/"));
14253
14550
  }
14254
14551
 
14255
- // src/local/cacheAdapter.ts
14256
- import * as fs3 from "node:fs";
14257
- import * as os from "node:os";
14258
- import * as path3 from "node:path";
14259
- var SCOPE_PREFIX2 = "@jmanuelcorral/openteam";
14260
- var SCOPE_SEGMENTS = SCOPE_PREFIX2.split("/");
14261
- function resolveCacheRoot(override) {
14262
- if (override !== undefined && override.trim() !== "") {
14263
- return override;
14552
+ // src/index.ts
14553
+ import {
14554
+ tool as tool2
14555
+ } from "@opencode-ai/plugin";
14556
+
14557
+ // src/graph/certificate.ts
14558
+ import { z as z20 } from "zod";
14559
+
14560
+ // src/contract/opencode.ts
14561
+ var SUPPORTED_OPENCODE_VERSIONS = [
14562
+ "1.17.13",
14563
+ "1.18.18",
14564
+ "1.18.19"
14565
+ ];
14566
+ var SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)/;
14567
+ function parseSemver(version) {
14568
+ const normalized = version.trim().replace(/^[=^~v]+/, "");
14569
+ const match = SEMVER_RE.exec(normalized);
14570
+ if (match === null) {
14571
+ throw new Error(`invalid semver: "${version}"`);
14264
14572
  }
14265
- const base = process.env.XDG_CACHE_HOME ?? path3.join(os.homedir(), ".cache");
14266
- return path3.join(base, "opencode", "packages");
14573
+ return {
14574
+ major: Number(match[1]),
14575
+ minor: Number(match[2]),
14576
+ patch: Number(match[3])
14577
+ };
14267
14578
  }
14268
- function readJsonSafe(filePath) {
14269
- try {
14270
- const text2 = fs3.readFileSync(filePath, "utf8");
14271
- const parsed = JSON.parse(text2);
14272
- if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
14273
- return parsed;
14274
- }
14275
- return;
14276
- } catch {
14277
- return;
14579
+ function isCertificateVersionCompatible(certifiedVersion, liveVersion) {
14580
+ if (certifiedVersion === liveVersion) {
14581
+ return true;
14278
14582
  }
14279
- }
14280
- function extractVersion(pkg) {
14281
- if (pkg === undefined)
14282
- return "";
14283
- const v = pkg.version;
14284
- return typeof v === "string" ? v : "";
14285
- }
14286
- function readDirectoryEntries(dir) {
14583
+ let certified;
14584
+ let live;
14287
14585
  try {
14288
- return fs3.readdirSync(dir, { withFileTypes: true });
14586
+ certified = parseSemver(certifiedVersion);
14587
+ live = parseSemver(liveVersion);
14289
14588
  } catch {
14290
- return [];
14589
+ return false;
14590
+ }
14591
+ if (certified.major !== live.major || certified.minor !== live.minor) {
14592
+ return false;
14291
14593
  }
14594
+ return live.patch >= certified.patch;
14292
14595
  }
14293
- function lstatPlainDirectory(dir) {
14294
- try {
14295
- const stat2 = fs3.lstatSync(dir);
14296
- if (!stat2.isDirectory() || stat2.isSymbolicLink()) {
14297
- return;
14298
- }
14299
- return stat2;
14300
- } catch {
14301
- return;
14596
+
14597
+ // src/graph/certificate.ts
14598
+ var SHADOW_CERTIFICATE_MIN_RUNS = 1000;
14599
+ var DivergenceCodeSchema = z20.enum([
14600
+ "status",
14601
+ "order",
14602
+ "roles",
14603
+ "models",
14604
+ "fixes",
14605
+ "outcome"
14606
+ ]);
14607
+ var ProvenanceSchema = z20.enum([
14608
+ "recomputed",
14609
+ "compiled",
14610
+ "validated",
14611
+ "carried"
14612
+ ]);
14613
+ var EvidenceSchema = z20.object({
14614
+ opencodeVersion: z20.string().min(1),
14615
+ parity: z20.object({
14616
+ runs: z20.number().int().nonnegative(),
14617
+ match: z20.number().int().nonnegative(),
14618
+ divergent: z20.number().int().nonnegative(),
14619
+ inconclusive: z20.number().int().nonnegative(),
14620
+ divergences: z20.array(DivergenceCodeSchema)
14621
+ }).strict(),
14622
+ legacy: z20.object({
14623
+ observed: z20.number().int().nonnegative(),
14624
+ duplicated: z20.number().int().nonnegative()
14625
+ }).strict(),
14626
+ privacy: z20.object({
14627
+ surfacesScanned: z20.number().int().nonnegative(),
14628
+ rawFindings: z20.number().int().nonnegative()
14629
+ }).strict(),
14630
+ overhead: z20.object({
14631
+ p95Millis: z20.number().nonnegative(),
14632
+ budgetMillis: z20.number().positive(),
14633
+ samples: z20.number().int().nonnegative()
14634
+ }).strict(),
14635
+ provenance: z20.record(DivergenceCodeSchema, ProvenanceSchema)
14636
+ }).strict();
14637
+ var CertificateSchema = z20.object({
14638
+ version: z20.literal(1),
14639
+ certificate: z20.literal("graph-shadow"),
14640
+ opencodeVersion: z20.string().min(1),
14641
+ evidence: EvidenceSchema,
14642
+ verdict: z20.enum(["pass", "fail"]),
14643
+ failedGates: z20.array(z20.enum([
14644
+ "sample",
14645
+ "parity",
14646
+ "one-legacy-execution",
14647
+ "privacy",
14648
+ "overhead"
14649
+ ])),
14650
+ digest: z20.string().regex(/^[0-9a-f]{64}$/)
14651
+ }).strict();
14652
+
14653
+ class ShadowCertificateError extends Error {
14654
+ code;
14655
+ detail;
14656
+ constructor(code, detail) {
14657
+ super(`${code}: ${detail}`);
14658
+ this.name = "ShadowCertificateError";
14659
+ this.code = code;
14660
+ this.detail = detail;
14302
14661
  }
14303
14662
  }
14304
- function findEntryDirs(cacheRoot) {
14305
- const candidates = [];
14306
- for (const topEntry of readDirectoryEntries(cacheRoot)) {
14307
- const topPath = path3.join(cacheRoot, topEntry.name);
14308
- const topStat = lstatPlainDirectory(topPath);
14309
- if (topStat === undefined) {
14310
- continue;
14311
- }
14312
- if (!topEntry.name.startsWith("@")) {
14313
- candidates.push({
14314
- specDir: topEntry.name,
14315
- absolutePath: topPath,
14316
- stat: topStat
14317
- });
14318
- continue;
14319
- }
14320
- for (const packageEntry of readDirectoryEntries(topPath)) {
14321
- const packagePath = path3.join(topPath, packageEntry.name);
14322
- const packageStat = lstatPlainDirectory(packagePath);
14323
- if (packageStat === undefined) {
14324
- continue;
14325
- }
14326
- candidates.push({
14327
- specDir: `${topEntry.name}/${packageEntry.name}`,
14328
- absolutePath: packagePath,
14329
- stat: packageStat
14330
- });
14331
- }
14663
+ function evaluateGates(evidence) {
14664
+ const failed = [];
14665
+ if (evidence.parity.runs < SHADOW_CERTIFICATE_MIN_RUNS) {
14666
+ failed.push("sample");
14332
14667
  }
14333
- return candidates.sort((a, b) => a.specDir.localeCompare(b.specDir));
14668
+ if (evidence.parity.divergent > 0 || evidence.parity.inconclusive > 0 || evidence.parity.divergences.length > 0 || evidence.parity.match !== evidence.parity.runs) {
14669
+ failed.push("parity");
14670
+ }
14671
+ if (evidence.legacy.duplicated > 0 || evidence.legacy.observed < 1) {
14672
+ failed.push("one-legacy-execution");
14673
+ }
14674
+ if (evidence.privacy.rawFindings > 0 || evidence.privacy.surfacesScanned < 1) {
14675
+ failed.push("privacy");
14676
+ }
14677
+ if (evidence.overhead.samples < 1 || evidence.overhead.p95Millis > evidence.overhead.budgetMillis) {
14678
+ failed.push("overhead");
14679
+ }
14680
+ return failed;
14334
14681
  }
14335
- async function listEntries(cacheRoot) {
14336
- const entries = [];
14337
- for (const candidate of findEntryDirs(cacheRoot)) {
14338
- const { specDir, absolutePath, stat: stat2 } = candidate;
14339
- if (!specDir.startsWith(SCOPE_PREFIX2))
14340
- continue;
14341
- const mtimeIso = stat2.mtime.toISOString();
14342
- const entryPkg = readJsonSafe(path3.join(absolutePath, "package.json"));
14343
- const deps = entryPkg?.dependencies;
14344
- let pinnedVersion = "";
14345
- if (deps !== null && typeof deps === "object" && !Array.isArray(deps)) {
14346
- const depsObj = deps;
14347
- const pinned = depsObj[SCOPE_PREFIX2];
14348
- pinnedVersion = typeof pinned === "string" ? pinned : "";
14682
+ function shadowCertificateBindingDigest(evidence) {
14683
+ return sha256Hex(JSON.stringify({
14684
+ opencodeVersion: evidence.opencodeVersion,
14685
+ parity: {
14686
+ runs: evidence.parity.runs,
14687
+ match: evidence.parity.match,
14688
+ divergent: evidence.parity.divergent,
14689
+ inconclusive: evidence.parity.inconclusive,
14690
+ divergences: [...evidence.parity.divergences].sort()
14691
+ },
14692
+ legacy: {
14693
+ observed: evidence.legacy.observed,
14694
+ duplicated: evidence.legacy.duplicated
14695
+ },
14696
+ privacy: {
14697
+ surfacesScanned: evidence.privacy.surfacesScanned,
14698
+ rawFindings: evidence.privacy.rawFindings
14699
+ },
14700
+ overhead: {
14701
+ budgetMillis: evidence.overhead.budgetMillis,
14702
+ samples: evidence.overhead.samples
14703
+ },
14704
+ provenance: Object.fromEntries(Object.entries(evidence.provenance).sort(([a], [b]) => a < b ? -1 : 1))
14705
+ }));
14706
+ }
14707
+ function canonicalCertificate(certificate) {
14708
+ const evidence = certificate.evidence;
14709
+ return JSON.stringify({
14710
+ version: certificate.version,
14711
+ certificate: certificate.certificate,
14712
+ opencodeVersion: certificate.opencodeVersion,
14713
+ verdict: certificate.verdict,
14714
+ failedGates: [...certificate.failedGates],
14715
+ evidence: {
14716
+ opencodeVersion: evidence.opencodeVersion,
14717
+ parity: {
14718
+ runs: evidence.parity.runs,
14719
+ match: evidence.parity.match,
14720
+ divergent: evidence.parity.divergent,
14721
+ inconclusive: evidence.parity.inconclusive,
14722
+ divergences: [...evidence.parity.divergences].sort()
14723
+ },
14724
+ legacy: {
14725
+ observed: evidence.legacy.observed,
14726
+ duplicated: evidence.legacy.duplicated
14727
+ },
14728
+ privacy: {
14729
+ surfacesScanned: evidence.privacy.surfacesScanned,
14730
+ rawFindings: evidence.privacy.rawFindings
14731
+ },
14732
+ overhead: {
14733
+ p95Millis: evidence.overhead.p95Millis,
14734
+ budgetMillis: evidence.overhead.budgetMillis,
14735
+ samples: evidence.overhead.samples
14736
+ },
14737
+ provenance: Object.fromEntries(Object.entries(evidence.provenance).sort(([a], [b]) => a < b ? -1 : 1))
14349
14738
  }
14350
- const installedPkg = readJsonSafe(path3.join(absolutePath, "node_modules", ...SCOPE_SEGMENTS, "package.json"));
14351
- const installedVersion = extractVersion(installedPkg);
14352
- entries.push({
14353
- specDir,
14354
- absolutePath,
14355
- pinnedVersion,
14356
- installedVersion,
14357
- mtimeIso
14358
- });
14359
- }
14360
- return entries;
14739
+ });
14361
14740
  }
14362
- async function deleteEntry(entryPath) {
14363
- fs3.rmSync(entryPath, { recursive: true, force: true });
14741
+ function sameGates(a, b) {
14742
+ return a.length === b.length && a.every((gate, index) => gate === b[index]);
14364
14743
  }
14365
- async function isReparsePath(entryPath) {
14366
- try {
14367
- const stat2 = await fs3.promises.lstat(entryPath);
14368
- return stat2.isSymbolicLink();
14369
- } catch {
14370
- return true;
14744
+ function parseShadowCertificate(value, opencodeVersion) {
14745
+ const parsed = CertificateSchema.safeParse(value);
14746
+ if (!parsed.success) {
14747
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.code}`).join("; ");
14748
+ throw new ShadowCertificateError("invalid-certificate", detail);
14371
14749
  }
14750
+ const certificate = parsed.data;
14751
+ const failedGates = evaluateGates(certificate.evidence);
14752
+ const verdict = failedGates.length === 0 ? "pass" : "fail";
14753
+ if (certificate.verdict !== verdict || !sameGates(certificate.failedGates, failedGates)) {
14754
+ throw new ShadowCertificateError("verdict-mismatch", `evidence yields "${verdict}" (${failedGates.join(",") || "no failed gate"})`);
14755
+ }
14756
+ const { digest, ...unsigned } = certificate;
14757
+ if (sha256Hex(canonicalCertificate(unsigned)) !== digest) {
14758
+ throw new ShadowCertificateError("digest-mismatch", "certificate digest does not cover the presented evidence");
14759
+ }
14760
+ if (!isCertificateVersionCompatible(certificate.opencodeVersion, opencodeVersion)) {
14761
+ 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`);
14762
+ }
14763
+ return certificate;
14372
14764
  }
14373
- var realCacheAdapter = {
14374
- resolveCacheRoot,
14375
- listEntries,
14376
- deleteEntry,
14377
- isReparsePath
14378
- };
14765
+ var ReleaseDeterminismSchema = z20.object({ runs: z20.number().int().nonnegative(), allMatch: z20.boolean() }).strict();
14766
+ var ReleaseCrashRecoverySchema = z20.object({
14767
+ scenarios: z20.number().int().nonnegative(),
14768
+ allConverge: z20.boolean()
14769
+ }).strict();
14770
+ var ReleaseReplayEquivalenceSchema = z20.object({
14771
+ checks: z20.number().int().nonnegative(),
14772
+ allEquivalent: z20.boolean()
14773
+ }).strict();
14774
+ var ReleasePrivacySchema = z20.object({
14775
+ surfacesScanned: z20.number().int().nonnegative(),
14776
+ rawFindings: z20.number().int().nonnegative()
14777
+ }).strict();
14778
+ var ReleaseContractSchema = z20.object({
14779
+ roundTrips: z20.number().int().nonnegative(),
14780
+ allSettled: z20.boolean()
14781
+ }).strict();
14782
+ var ReleaseHealthSchema = z20.object({
14783
+ samples: z20.number().int().nonnegative(),
14784
+ bounded: z20.boolean(),
14785
+ idempotent: z20.boolean()
14786
+ }).strict();
14787
+ var ReleasePerformanceSchema = z20.object({
14788
+ p95Millis: z20.number().nonnegative(),
14789
+ budgetMillis: z20.number().positive(),
14790
+ samples: z20.number().int().nonnegative()
14791
+ }).strict();
14792
+ var ReleaseEvidenceSchema = z20.object({
14793
+ opencodeVersion: z20.string().min(1),
14794
+ platform: z20.enum(["linux", "win32"]),
14795
+ shadowCertificateDigest: z20.string().regex(/^[0-9a-f]{64}$/),
14796
+ determinism: ReleaseDeterminismSchema,
14797
+ crashRecovery: ReleaseCrashRecoverySchema,
14798
+ replayEquivalence: ReleaseReplayEquivalenceSchema,
14799
+ privacy: ReleasePrivacySchema,
14800
+ contract: ReleaseContractSchema,
14801
+ health: ReleaseHealthSchema,
14802
+ performance: ReleasePerformanceSchema
14803
+ }).strict();
14804
+ var ReleaseCertificateSchema = z20.object({
14805
+ version: z20.literal(2),
14806
+ certificate: z20.literal("graph-release"),
14807
+ platform: z20.enum(["linux", "win32"]),
14808
+ opencodeVersion: z20.string().min(1),
14809
+ shadowCertificateDigest: z20.string().regex(/^[0-9a-f]{64}$/),
14810
+ evidence: ReleaseEvidenceSchema,
14811
+ verdict: z20.enum(["pass", "fail"]),
14812
+ failedGates: z20.array(z20.enum([
14813
+ "shadow-valid",
14814
+ "determinism",
14815
+ "crash-recovery",
14816
+ "replay-equivalence",
14817
+ "privacy",
14818
+ "contract",
14819
+ "health",
14820
+ "performance"
14821
+ ])),
14822
+ digest: z20.string().regex(/^[0-9a-f]{64}$/)
14823
+ }).strict();
14379
14824
 
14380
- // src/memory/rank.ts
14381
- var DAY_MS = 86400000;
14382
- var DEFAULT_RANK_SIGNALS = {
14383
- similarityWeight: 1,
14384
- recencyWeight: 0.2,
14385
- accessWeight: 0.1,
14386
- recencyHalfLifeMs: 14 * DAY_MS
14387
- };
14388
- function recencyTimestamp(record) {
14389
- if (record.kind === "preference" && record.lastAccessedAt !== null) {
14390
- return record.lastAccessedAt;
14825
+ class ReleaseCertificateError extends Error {
14826
+ code;
14827
+ detail;
14828
+ constructor(code, detail) {
14829
+ super(`${code}: ${detail}`);
14830
+ this.name = "ReleaseCertificateError";
14831
+ this.code = code;
14832
+ this.detail = detail;
14391
14833
  }
14392
- return record.createdAt;
14393
- }
14394
- function recencyScore(record, signals) {
14395
- const ageMs = Math.max(0, signals.now - recencyTimestamp(record));
14396
- return 0.5 ** (ageMs / signals.recencyHalfLifeMs);
14397
14834
  }
14398
- function accessScore(record) {
14399
- if (record.kind !== "preference") {
14400
- return 0;
14835
+ function evaluateReleaseGates(evidence, expectedShadowDigest) {
14836
+ const failed = [];
14837
+ if (evidence.shadowCertificateDigest !== expectedShadowDigest) {
14838
+ failed.push("shadow-valid");
14401
14839
  }
14402
- return 1 - 1 / (1 + record.accessCount);
14403
- }
14404
- function scoreCandidate(candidate, signals) {
14405
- return signals.similarityWeight * candidate.similarity + signals.recencyWeight * recencyScore(candidate.record, signals) + signals.accessWeight * accessScore(candidate.record);
14840
+ if (evidence.determinism.runs < 1 || !evidence.determinism.allMatch) {
14841
+ failed.push("determinism");
14842
+ }
14843
+ if (evidence.crashRecovery.scenarios < 1 || !evidence.crashRecovery.allConverge) {
14844
+ failed.push("crash-recovery");
14845
+ }
14846
+ if (evidence.replayEquivalence.checks < 1 || !evidence.replayEquivalence.allEquivalent) {
14847
+ failed.push("replay-equivalence");
14848
+ }
14849
+ if (evidence.privacy.surfacesScanned < 1 || evidence.privacy.rawFindings > 0) {
14850
+ failed.push("privacy");
14851
+ }
14852
+ if (evidence.contract.roundTrips < 1 || !evidence.contract.allSettled) {
14853
+ failed.push("contract");
14854
+ }
14855
+ if (evidence.health.samples < 1 || !evidence.health.bounded || !evidence.health.idempotent) {
14856
+ failed.push("health");
14857
+ }
14858
+ if (evidence.performance.samples < 1 || evidence.performance.p95Millis > evidence.performance.budgetMillis) {
14859
+ failed.push("performance");
14860
+ }
14861
+ return failed;
14406
14862
  }
14407
- function rankRecall(candidates, signals) {
14408
- return candidates.map((candidate) => ({
14409
- ...candidate,
14410
- score: scoreCandidate(candidate, signals)
14411
- })).sort((a, b) => {
14412
- if (b.score !== a.score) {
14413
- return b.score - a.score;
14863
+ function canonicalReleaseCertificate(certificate) {
14864
+ const ev = certificate.evidence;
14865
+ return JSON.stringify({
14866
+ version: certificate.version,
14867
+ certificate: certificate.certificate,
14868
+ platform: certificate.platform,
14869
+ opencodeVersion: certificate.opencodeVersion,
14870
+ shadowCertificateDigest: certificate.shadowCertificateDigest,
14871
+ verdict: certificate.verdict,
14872
+ failedGates: [...certificate.failedGates],
14873
+ evidence: {
14874
+ opencodeVersion: ev.opencodeVersion,
14875
+ platform: ev.platform,
14876
+ shadowCertificateDigest: ev.shadowCertificateDigest,
14877
+ determinism: {
14878
+ runs: ev.determinism.runs,
14879
+ allMatch: ev.determinism.allMatch
14880
+ },
14881
+ crashRecovery: {
14882
+ scenarios: ev.crashRecovery.scenarios,
14883
+ allConverge: ev.crashRecovery.allConverge
14884
+ },
14885
+ replayEquivalence: {
14886
+ checks: ev.replayEquivalence.checks,
14887
+ allEquivalent: ev.replayEquivalence.allEquivalent
14888
+ },
14889
+ privacy: {
14890
+ surfacesScanned: ev.privacy.surfacesScanned,
14891
+ rawFindings: ev.privacy.rawFindings
14892
+ },
14893
+ contract: {
14894
+ roundTrips: ev.contract.roundTrips,
14895
+ allSettled: ev.contract.allSettled
14896
+ },
14897
+ health: {
14898
+ samples: ev.health.samples,
14899
+ bounded: ev.health.bounded,
14900
+ idempotent: ev.health.idempotent
14901
+ },
14902
+ performance: {
14903
+ p95Millis: ev.performance.p95Millis,
14904
+ budgetMillis: ev.performance.budgetMillis,
14905
+ samples: ev.performance.samples
14906
+ }
14414
14907
  }
14415
- return a.record.id < b.record.id ? -1 : a.record.id > b.record.id ? 1 : 0;
14416
14908
  });
14417
14909
  }
14418
-
14419
- // src/memory/temporal.ts
14420
- function isTransactionActiveAt(record, asOf) {
14421
- return record.invalidatedAt === null || record.invalidatedAt > asOf;
14910
+ function sameReleaseGates(a, b) {
14911
+ return a.length === b.length && a.every((gate, index) => gate === b[index]);
14422
14912
  }
14423
- function isValidAt(record, asOf) {
14424
- if (record.validFrom > asOf) {
14425
- return false;
14913
+ function parseReleaseCertificate(value, opencodeVersion, expectedShadowDigest) {
14914
+ if (typeof value === "object" && value !== null && "version" in value && value.version === 1) {
14915
+ throw new ReleaseCertificateError("invalid-certificate", "certificate is schema version 1 (carried a removed 'migration' gate); " + "rebuild the release certificate against the current schema");
14426
14916
  }
14427
- return record.validUntil === null || record.validUntil > asOf;
14428
- }
14429
- function isActiveAt(record, asOf) {
14430
- return isTransactionActiveAt(record, asOf) && isValidAt(record, asOf);
14431
- }
14432
- function filterActive(records, asOf) {
14433
- return records.filter((record) => isActiveAt(record, asOf));
14434
- }
14435
- function coalesceWindow(a, b) {
14436
- const validFrom = Math.min(a.validFrom, b.validFrom);
14437
- const validUntil = a.validUntil === null || b.validUntil === null ? null : Math.max(a.validUntil, b.validUntil);
14438
- return { validFrom, validUntil };
14917
+ const parsed = ReleaseCertificateSchema.safeParse(value);
14918
+ if (!parsed.success) {
14919
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.code}`).join("; ");
14920
+ throw new ReleaseCertificateError("invalid-certificate", detail);
14921
+ }
14922
+ const certificate = parsed.data;
14923
+ const inconsistencies = [];
14924
+ if (certificate.platform !== certificate.evidence.platform) {
14925
+ inconsistencies.push("platform");
14926
+ }
14927
+ if (certificate.opencodeVersion !== certificate.evidence.opencodeVersion) {
14928
+ inconsistencies.push("opencodeVersion");
14929
+ }
14930
+ if (certificate.shadowCertificateDigest !== certificate.evidence.shadowCertificateDigest) {
14931
+ inconsistencies.push("shadowCertificateDigest");
14932
+ }
14933
+ if (inconsistencies.length > 0) {
14934
+ throw new ReleaseCertificateError("field-inconsistency", `top-level/evidence divergence: ${inconsistencies.join(", ")}`);
14935
+ }
14936
+ const failedGates = evaluateReleaseGates(certificate.evidence, expectedShadowDigest);
14937
+ const verdict = failedGates.length === 0 ? "pass" : "fail";
14938
+ if (certificate.verdict !== verdict || !sameReleaseGates(certificate.failedGates, failedGates)) {
14939
+ throw new ReleaseCertificateError("verdict-mismatch", `evidence yields "${verdict}" (${failedGates.join(",") || "no failed gate"})`);
14940
+ }
14941
+ const { digest, ...unsigned } = certificate;
14942
+ if (sha256Hex(canonicalReleaseCertificate(unsigned)) !== digest) {
14943
+ throw new ReleaseCertificateError("digest-mismatch", "certificate digest does not cover the presented evidence");
14944
+ }
14945
+ if (!isCertificateVersionCompatible(certificate.opencodeVersion, opencodeVersion)) {
14946
+ 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`);
14947
+ }
14948
+ return certificate;
14439
14949
  }
14440
-
14441
- // src/memory/types.ts
14442
- import { z as z19 } from "zod";
14443
- var SHARED_OWNER_KEY = "*";
14444
- var OwnerKeySchema = z19.string().min(1);
14445
- var MemoryKindSchema = z19.enum(["fact", "preference", "entity"]);
14446
- var MemoryBaseSchema = z19.object({
14447
- id: z19.string().min(1),
14448
- ownerKey: OwnerKeySchema,
14449
- confidence: z19.number().min(0).max(1),
14450
- validFrom: z19.number().finite(),
14451
- validUntil: z19.number().finite().nullable().default(null),
14452
- createdAt: z19.number().finite(),
14453
- invalidatedAt: z19.number().finite().nullable().default(null),
14454
- sourceHash: z19.string().min(1),
14455
- supersededBy: z19.string().min(1).nullable().default(null)
14456
- }).strict();
14457
- var FactSchema = MemoryBaseSchema.extend({
14458
- kind: z19.literal("fact"),
14459
- subject: z19.string().min(1),
14460
- predicate: z19.string().min(1),
14461
- object: z19.string().min(1),
14462
- category: z19.string().min(1).nullable().default(null)
14463
- });
14464
- var PreferenceSchema = MemoryBaseSchema.extend({
14465
- kind: z19.literal("preference"),
14466
- category: z19.string().min(1),
14467
- preference: z19.string().min(1),
14468
- context: z19.string().min(1).nullable().default(null),
14469
- lastAccessedAt: z19.number().finite().nullable().default(null),
14470
- accessCount: z19.number().int().min(0).default(0)
14471
- });
14472
- var EntitySchema = MemoryBaseSchema.extend({
14473
- kind: z19.literal("entity"),
14474
- canonicalName: z19.string().min(1),
14475
- type: z19.string().min(1),
14476
- aliases: z19.array(z19.string().min(1)).default([])
14477
- });
14478
- var RelationSchema = MemoryBaseSchema.extend({
14479
- kind: z19.literal("relation"),
14480
- from: z19.string().min(1),
14481
- to: z19.string().min(1),
14482
- predicate: z19.string().min(1),
14483
- annotation: z19.string().min(1).nullable().default(null)
14484
- });
14485
- var MemoryRecordSchema = z19.discriminatedUnion("kind", [
14486
- FactSchema,
14487
- PreferenceSchema,
14488
- EntitySchema,
14489
- RelationSchema
14950
+ var ShadowDivergenceCodeSchema = z20.enum([
14951
+ "status",
14952
+ "order",
14953
+ "roles",
14954
+ "models",
14955
+ "fixes",
14956
+ "outcome"
14490
14957
  ]);
14491
- var RecallQuerySchema = z19.object({
14492
- ownerKey: OwnerKeySchema,
14493
- kinds: z19.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
14494
- asOf: z19.number().finite().nullable().default(null),
14495
- limit: z19.number().int().positive().default(8),
14496
- minSimilarity: z19.number().min(0).max(1).default(0.2),
14497
- includeShared: z19.boolean().default(true)
14958
+ var RECOMPUTED_DIVERGENCE_CODES = ["fixes"];
14959
+ var RecomputedDivergenceCodeSchema = z20.enum(RECOMPUTED_DIVERGENCE_CODES);
14960
+ var SoakPolicySchema = z20.object({
14961
+ minimumGenuineObservations: z20.number().int().positive(),
14962
+ minimumDistinctDays: z20.number().int().positive(),
14963
+ requireGenuineUsageOnAllPlatforms: z20.boolean(),
14964
+ supportedOpencodeVersions: z20.array(z20.string().min(1)).min(1),
14965
+ criticalDivergenceCodes: z20.array(RecomputedDivergenceCodeSchema).min(1)
14966
+ }).strict();
14967
+ var DEFAULT_SOAK_POLICY = {
14968
+ minimumGenuineObservations: 100,
14969
+ minimumDistinctDays: 14,
14970
+ requireGenuineUsageOnAllPlatforms: true,
14971
+ supportedOpencodeVersions: [...SUPPORTED_OPENCODE_VERSIONS],
14972
+ criticalDivergenceCodes: ["fixes"]
14973
+ };
14974
+ var SoakPlatformEnumSchema = z20.enum(["linux", "win32", "darwin"]);
14975
+ var SoakPrivacyPairEvidenceSchema = z20.object({
14976
+ surfacesScanned: z20.number().int().nonnegative(),
14977
+ rawFindings: z20.number().int().nonnegative()
14978
+ }).strict();
14979
+ var SoakDuplicateEffectPairEvidenceSchema = z20.object({
14980
+ effectsExamined: z20.number().int().nonnegative(),
14981
+ duplicatesFound: z20.number().int().nonnegative()
14982
+ }).strict();
14983
+ var SoakModelVerificationPairEvidenceSchema = z20.object({
14984
+ nodesChecked: z20.number().int().nonnegative(),
14985
+ unverified: z20.number().int().nonnegative(),
14986
+ mismatched: z20.number().int().nonnegative()
14987
+ }).strict();
14988
+ var SoakEvidenceSchema = z20.object({
14989
+ totalObservations: z20.number().int().nonnegative(),
14990
+ genuineUsageObservations: z20.number().int().nonnegative(),
14991
+ ciSyntheticObservations: z20.number().int().nonnegative(),
14992
+ recorderCount: z20.number().int().nonnegative(),
14993
+ ineligibleObservations: z20.number().int().nonnegative(),
14994
+ firstTimestamp: z20.string().datetime().nullable(),
14995
+ lastTimestamp: z20.string().datetime().nullable(),
14996
+ distinctDays: z20.number().int().nonnegative(),
14997
+ criticalDivergences: z20.number().int().nonnegative().nullable(),
14998
+ privacy: SoakPrivacyPairEvidenceSchema.nullable(),
14999
+ duplicateEffects: SoakDuplicateEffectPairEvidenceSchema.nullable(),
15000
+ modelVerification: SoakModelVerificationPairEvidenceSchema.nullable(),
15001
+ platformsGenuine: z20.array(SoakPlatformEnumSchema),
15002
+ platformsSynthetic: z20.array(SoakPlatformEnumSchema),
15003
+ opencodeVersionsObserved: z20.array(z20.string().min(1)),
15004
+ invalidChains: z20.number().int().nonnegative()
15005
+ }).strict();
15006
+ var SoakCertificateSchema = z20.object({
15007
+ version: z20.literal(1),
15008
+ certificate: z20.literal("graph-soak"),
15009
+ trustBoundary: z20.literal("contributors"),
15010
+ policy: SoakPolicySchema,
15011
+ evidence: SoakEvidenceSchema,
15012
+ verdict: z20.enum(["pass", "fail"]),
15013
+ failedGates: z20.array(z20.enum([
15014
+ "minimum-observations",
15015
+ "minimum-duration",
15016
+ "chain-integrity",
15017
+ "zero-critical-divergence",
15018
+ "zero-privacy-breach",
15019
+ "zero-duplicate-effect",
15020
+ "zero-model-mismatch",
15021
+ "os-diversity",
15022
+ "opencode-pin"
15023
+ ])),
15024
+ ledgerDigest: z20.string().regex(/^[0-9a-f]{64}$/),
15025
+ digest: z20.string().regex(/^[0-9a-f]{64}$/)
14498
15026
  }).strict();
14499
15027
 
14500
- // src/memory/vector.ts
14501
- function dot(a, b) {
14502
- if (a.length !== b.length) {
14503
- throw new Error(`dimension mismatch: ${a.length} vs ${b.length}`);
14504
- }
14505
- let sum = 0;
14506
- for (let i = 0;i < a.length; i += 1) {
14507
- sum += a[i] * b[i];
15028
+ class SoakCertificateError extends Error {
15029
+ code;
15030
+ detail;
15031
+ constructor(code, detail) {
15032
+ super(`${code}: ${detail}`);
15033
+ this.name = "SoakCertificateError";
15034
+ this.code = code;
15035
+ this.detail = detail;
14508
15036
  }
14509
- return sum;
14510
15037
  }
14511
- function norm(a) {
14512
- let sum = 0;
14513
- for (let i = 0;i < a.length; i += 1) {
14514
- const value = a[i];
14515
- sum += value * value;
15038
+ function evaluateSoakGates(evidence, policy) {
15039
+ const failed = [];
15040
+ if (evidence.genuineUsageObservations < policy.minimumGenuineObservations) {
15041
+ failed.push("minimum-observations");
14516
15042
  }
14517
- return Math.sqrt(sum);
15043
+ if (evidence.distinctDays < policy.minimumDistinctDays) {
15044
+ failed.push("minimum-duration");
15045
+ }
15046
+ if (evidence.invalidChains > 0) {
15047
+ failed.push("chain-integrity");
15048
+ }
15049
+ if (evidence.criticalDivergences === null || evidence.criticalDivergences > 0) {
15050
+ failed.push("zero-critical-divergence");
15051
+ }
15052
+ if (evidence.privacy === null || evidence.privacy.surfacesScanned < 1 || evidence.privacy.rawFindings > 0) {
15053
+ failed.push("zero-privacy-breach");
15054
+ }
15055
+ if (evidence.duplicateEffects === null || evidence.duplicateEffects.effectsExamined < 1 || evidence.duplicateEffects.duplicatesFound > 0) {
15056
+ failed.push("zero-duplicate-effect");
15057
+ }
15058
+ if (evidence.modelVerification === null || evidence.modelVerification.nodesChecked < 1 || evidence.modelVerification.mismatched > 0) {
15059
+ failed.push("zero-model-mismatch");
15060
+ }
15061
+ if (policy.requireGenuineUsageOnAllPlatforms) {
15062
+ if (!evidence.platformsGenuine.includes("linux") || !evidence.platformsGenuine.includes("win32")) {
15063
+ failed.push("os-diversity");
15064
+ }
15065
+ } else {
15066
+ const allPlatforms = new Set([
15067
+ ...evidence.platformsGenuine,
15068
+ ...evidence.platformsSynthetic
15069
+ ]);
15070
+ if (!allPlatforms.has("linux") || !allPlatforms.has("win32") || evidence.platformsGenuine.length < 1) {
15071
+ failed.push("os-diversity");
15072
+ }
15073
+ }
15074
+ const supported = new Set(policy.supportedOpencodeVersions);
15075
+ if (evidence.opencodeVersionsObserved.length === 0 || !evidence.opencodeVersionsObserved.every((v) => supported.has(v))) {
15076
+ failed.push("opencode-pin");
15077
+ }
15078
+ return failed;
14518
15079
  }
14519
- function cosineSimilarity(a, b) {
14520
- const denom = norm(a) * norm(b);
14521
- if (denom === 0) {
14522
- return 0;
15080
+ function canonicalSoakCertificate(certificate) {
15081
+ const ev = certificate.evidence;
15082
+ const pol = certificate.policy;
15083
+ return JSON.stringify({
15084
+ version: certificate.version,
15085
+ certificate: certificate.certificate,
15086
+ trustBoundary: certificate.trustBoundary,
15087
+ policy: {
15088
+ minimumGenuineObservations: pol.minimumGenuineObservations,
15089
+ minimumDistinctDays: pol.minimumDistinctDays,
15090
+ requireGenuineUsageOnAllPlatforms: pol.requireGenuineUsageOnAllPlatforms,
15091
+ supportedOpencodeVersions: [...pol.supportedOpencodeVersions].sort(),
15092
+ criticalDivergenceCodes: [...pol.criticalDivergenceCodes].sort()
15093
+ },
15094
+ verdict: certificate.verdict,
15095
+ failedGates: [...certificate.failedGates],
15096
+ ledgerDigest: certificate.ledgerDigest,
15097
+ evidence: {
15098
+ totalObservations: ev.totalObservations,
15099
+ genuineUsageObservations: ev.genuineUsageObservations,
15100
+ ciSyntheticObservations: ev.ciSyntheticObservations,
15101
+ recorderCount: ev.recorderCount,
15102
+ ineligibleObservations: ev.ineligibleObservations,
15103
+ firstTimestamp: ev.firstTimestamp,
15104
+ lastTimestamp: ev.lastTimestamp,
15105
+ distinctDays: ev.distinctDays,
15106
+ criticalDivergences: ev.criticalDivergences,
15107
+ privacy: ev.privacy,
15108
+ duplicateEffects: ev.duplicateEffects,
15109
+ modelVerification: ev.modelVerification,
15110
+ platformsGenuine: [...ev.platformsGenuine].sort(),
15111
+ platformsSynthetic: [...ev.platformsSynthetic].sort(),
15112
+ opencodeVersionsObserved: [...ev.opencodeVersionsObserved].sort(),
15113
+ invalidChains: ev.invalidChains
15114
+ }
15115
+ });
15116
+ }
15117
+ function sameSoakGates(a, b) {
15118
+ return a.length === b.length && a.every((gate, index) => gate === b[index]);
15119
+ }
15120
+ function sameSoakPolicy(a, b) {
15121
+ const aVersions = [...a.supportedOpencodeVersions].sort();
15122
+ const bVersions = [...b.supportedOpencodeVersions].sort();
15123
+ return a.minimumGenuineObservations === b.minimumGenuineObservations && a.minimumDistinctDays === b.minimumDistinctDays && a.requireGenuineUsageOnAllPlatforms === b.requireGenuineUsageOnAllPlatforms && aVersions.length === bVersions.length && aVersions.every((v, i) => v === bVersions[i]) && a.criticalDivergenceCodes.length === b.criticalDivergenceCodes.length && [...a.criticalDivergenceCodes].sort().every((code, i) => code === [...b.criticalDivergenceCodes].sort()[i]);
15124
+ }
15125
+ function parseSoakCertificate(value, expectedPolicy, expectedLedgerDigest) {
15126
+ const parsed = SoakCertificateSchema.safeParse(value);
15127
+ if (!parsed.success) {
15128
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.code}`).join("; ");
15129
+ throw new SoakCertificateError("invalid-certificate", detail);
14523
15130
  }
14524
- return dot(a, b) / denom;
15131
+ const certificate = parsed.data;
15132
+ if (!sameSoakPolicy(certificate.policy, expectedPolicy)) {
15133
+ throw new SoakCertificateError("policy-mismatch", "embedded policy diverges from the expected policy");
15134
+ }
15135
+ const failedGates = evaluateSoakGates(certificate.evidence, certificate.policy);
15136
+ const verdict = failedGates.length === 0 ? "pass" : "fail";
15137
+ if (certificate.verdict !== verdict || !sameSoakGates(certificate.failedGates, failedGates)) {
15138
+ throw new SoakCertificateError("verdict-mismatch", `evidence yields "${verdict}" (${failedGates.join(",") || "no failed gate"})`);
15139
+ }
15140
+ const { digest, ...unsigned } = certificate;
15141
+ if (sha256Hex(canonicalSoakCertificate(unsigned)) !== digest) {
15142
+ throw new SoakCertificateError("digest-mismatch", "certificate digest does not cover the presented evidence");
15143
+ }
15144
+ if (certificate.ledgerDigest !== expectedLedgerDigest) {
15145
+ throw new SoakCertificateError("ledger-mismatch", "ledger digest does not match the evidence the caller holds");
15146
+ }
15147
+ return certificate;
14525
15148
  }
14526
15149
 
14527
- // src/memory/recall.ts
14528
- function matchesScope(record, query) {
14529
- if (record.ownerKey === query.ownerKey) {
14530
- return true;
15150
+ // src/local/cacheAdapter.ts
15151
+ import * as fs3 from "node:fs";
15152
+ import * as os from "node:os";
15153
+ import * as path3 from "node:path";
15154
+ var SCOPE_PREFIX2 = "@jmanuelcorral/openteam";
15155
+ var SCOPE_SEGMENTS = SCOPE_PREFIX2.split("/");
15156
+ function resolveCacheRoot(override) {
15157
+ if (override !== undefined && override.trim() !== "") {
15158
+ return override;
14531
15159
  }
14532
- return query.includeShared && record.ownerKey === SHARED_OWNER_KEY;
15160
+ const base = process.env.XDG_CACHE_HOME ?? path3.join(os.homedir(), ".cache");
15161
+ return path3.join(base, "opencode", "packages");
14533
15162
  }
14534
- function recall(records, queryEmbedding, query, deps) {
14535
- const asOf = query.asOf ?? deps.now;
14536
- const kinds = new Set(query.kinds);
14537
- const candidates = [];
14538
- for (const record of filterActive(records, asOf)) {
14539
- if (!kinds.has(record.kind) || !matchesScope(record, query)) {
14540
- continue;
14541
- }
14542
- const embedding = deps.embeddings.get(record.id);
14543
- if (embedding === undefined) {
14544
- continue;
14545
- }
14546
- const similarity = cosineSimilarity(queryEmbedding, embedding);
14547
- if (similarity < query.minSimilarity) {
14548
- continue;
15163
+ function readJsonSafe(filePath) {
15164
+ try {
15165
+ const text2 = fs3.readFileSync(filePath, "utf8");
15166
+ const parsed = JSON.parse(text2);
15167
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
15168
+ return parsed;
14549
15169
  }
14550
- candidates.push({ record, similarity });
15170
+ return;
15171
+ } catch {
15172
+ return;
14551
15173
  }
14552
- const signals = {
14553
- now: deps.now,
14554
- ...deps.signals ?? DEFAULT_RANK_SIGNALS
14555
- };
14556
- return rankRecall(candidates, signals).slice(0, query.limit);
14557
15174
  }
14558
-
14559
- // src/memory/traverse.ts
14560
- function inOwnerScope(record, ownerKey, includeShared) {
14561
- if (record.ownerKey === ownerKey) {
14562
- return true;
15175
+ function extractVersion(pkg) {
15176
+ if (pkg === undefined)
15177
+ return "";
15178
+ const v = pkg.version;
15179
+ return typeof v === "string" ? v : "";
15180
+ }
15181
+ function readDirectoryEntries(dir) {
15182
+ try {
15183
+ return fs3.readdirSync(dir, { withFileTypes: true });
15184
+ } catch {
15185
+ return [];
14563
15186
  }
14564
- return includeShared && record.ownerKey === SHARED_OWNER_KEY;
14565
15187
  }
14566
- function traverseRelations(records, query) {
14567
- const includeShared = query.includeShared ?? true;
14568
- const adjacency = new Map;
14569
- for (const record of records) {
14570
- if (record.kind !== "relation") {
15188
+ function lstatPlainDirectory(dir) {
15189
+ try {
15190
+ const stat2 = fs3.lstatSync(dir);
15191
+ if (!stat2.isDirectory() || stat2.isSymbolicLink()) {
15192
+ return;
15193
+ }
15194
+ return stat2;
15195
+ } catch {
15196
+ return;
15197
+ }
15198
+ }
15199
+ function findEntryDirs(cacheRoot) {
15200
+ const candidates = [];
15201
+ for (const topEntry of readDirectoryEntries(cacheRoot)) {
15202
+ const topPath = path3.join(cacheRoot, topEntry.name);
15203
+ const topStat = lstatPlainDirectory(topPath);
15204
+ if (topStat === undefined) {
14571
15205
  continue;
14572
15206
  }
14573
- if (!isActiveAt(record, query.asOf)) {
15207
+ if (!topEntry.name.startsWith("@")) {
15208
+ candidates.push({
15209
+ specDir: topEntry.name,
15210
+ absolutePath: topPath,
15211
+ stat: topStat
15212
+ });
14574
15213
  continue;
14575
15214
  }
14576
- if (!inOwnerScope(record, query.ownerKey, includeShared)) {
14577
- continue;
14578
- }
14579
- const list = adjacency.get(record.from);
14580
- if (list === undefined) {
14581
- adjacency.set(record.from, [record]);
14582
- } else {
14583
- list.push(record);
14584
- }
14585
- }
14586
- const visited = new Set([query.seed]);
14587
- const nodes = [{ id: query.seed, depth: 0 }];
14588
- const edges = [];
14589
- let frontier = [query.seed];
14590
- for (let depth = 0;depth < query.maxDepth; depth += 1) {
14591
- const nextFrontier = [];
14592
- for (const nodeId of frontier) {
14593
- const outgoing = adjacency.get(nodeId) ?? [];
14594
- for (const edge of outgoing) {
14595
- if (visited.has(edge.to)) {
14596
- continue;
14597
- }
14598
- visited.add(edge.to);
14599
- const childDepth = depth + 1;
14600
- nodes.push({ id: edge.to, depth: childDepth });
14601
- edges.push({
14602
- from: edge.from,
14603
- to: edge.to,
14604
- predicate: edge.predicate,
14605
- depth: childDepth
14606
- });
14607
- nextFrontier.push(edge.to);
15215
+ for (const packageEntry of readDirectoryEntries(topPath)) {
15216
+ const packagePath = path3.join(topPath, packageEntry.name);
15217
+ const packageStat = lstatPlainDirectory(packagePath);
15218
+ if (packageStat === undefined) {
15219
+ continue;
14608
15220
  }
15221
+ candidates.push({
15222
+ specDir: `${topEntry.name}/${packageEntry.name}`,
15223
+ absolutePath: packagePath,
15224
+ stat: packageStat
15225
+ });
14609
15226
  }
14610
- frontier = nextFrontier;
14611
15227
  }
14612
- return { seed: query.seed, nodes, edges };
14613
- }
14614
- function seedNodeId(record) {
14615
- return record.kind === "entity" ? record.canonicalName : undefined;
15228
+ return candidates.sort((a, b) => a.specDir.localeCompare(b.specDir));
14616
15229
  }
14617
- function recallGraph(records, queryEmbedding, query, options, deps) {
14618
- const seeds = recall(records, queryEmbedding, query, deps);
14619
- const asOf = query.asOf ?? deps.now;
14620
- const includeShared = options.includeShared ?? query.includeShared;
14621
- const traversals = [];
14622
- for (const result of seeds) {
14623
- const seed = seedNodeId(result.record);
14624
- if (seed === undefined) {
15230
+ async function listEntries(cacheRoot) {
15231
+ const entries = [];
15232
+ for (const candidate of findEntryDirs(cacheRoot)) {
15233
+ const { specDir, absolutePath, stat: stat2 } = candidate;
15234
+ if (!specDir.startsWith(SCOPE_PREFIX2))
14625
15235
  continue;
15236
+ const mtimeIso = stat2.mtime.toISOString();
15237
+ const entryPkg = readJsonSafe(path3.join(absolutePath, "package.json"));
15238
+ const deps = entryPkg?.dependencies;
15239
+ let pinnedVersion = "";
15240
+ if (deps !== null && typeof deps === "object" && !Array.isArray(deps)) {
15241
+ const depsObj = deps;
15242
+ const pinned = depsObj[SCOPE_PREFIX2];
15243
+ pinnedVersion = typeof pinned === "string" ? pinned : "";
14626
15244
  }
14627
- traversals.push({
14628
- seedRecordId: result.record.id,
14629
- traversal: traverseRelations(records, {
14630
- seed,
14631
- ownerKey: query.ownerKey,
14632
- asOf,
14633
- maxDepth: options.maxDepth,
14634
- includeShared
14635
- })
15245
+ const installedPkg = readJsonSafe(path3.join(absolutePath, "node_modules", ...SCOPE_SEGMENTS, "package.json"));
15246
+ const installedVersion = extractVersion(installedPkg);
15247
+ entries.push({
15248
+ specDir,
15249
+ absolutePath,
15250
+ pinnedVersion,
15251
+ installedVersion,
15252
+ mtimeIso
14636
15253
  });
14637
15254
  }
14638
- return { recall: seeds, traversals };
14639
- }
14640
-
14641
- // src/memory/merge.ts
14642
- var SEP = "\x00";
14643
- function factSlotKey(fact) {
14644
- return [fact.subject, fact.predicate, fact.ownerKey].join(SEP);
15255
+ return entries;
14645
15256
  }
14646
- function factValueKey(fact) {
14647
- return [factSlotKey(fact), fact.object].join(SEP);
15257
+ async function deleteEntry(entryPath) {
15258
+ fs3.rmSync(entryPath, { recursive: true, force: true });
14648
15259
  }
14649
- function preferenceSlotKey(preference) {
14650
- return [preference.category, preference.ownerKey].join(SEP);
15260
+ async function isReparsePath(entryPath) {
15261
+ try {
15262
+ const stat2 = await fs3.promises.lstat(entryPath);
15263
+ return stat2.isSymbolicLink();
15264
+ } catch {
15265
+ return true;
15266
+ }
14651
15267
  }
14652
- function preferenceValueKey(preference) {
14653
- return [preferenceSlotKey(preference), preference.preference].join(SEP);
15268
+ var realCacheAdapter = {
15269
+ resolveCacheRoot,
15270
+ listEntries,
15271
+ deleteEntry,
15272
+ isReparsePath
15273
+ };
15274
+
15275
+ // src/orchestrator/rosterPersistence.ts
15276
+ function rosterFence(roster) {
15277
+ const data = { universe: roster.universe, entries: roster.entries };
15278
+ return `\`\`\`json
15279
+ ${JSON.stringify(data, null, 2)}
15280
+ \`\`\``;
14654
15281
  }
14655
- function entitySlotKey(entity) {
14656
- return [entity.canonicalName, entity.type, entity.ownerKey].join(SEP);
15282
+ function serializeRoster(roster) {
15283
+ return `# openteam roster
15284
+
15285
+ Universe: ${roster.universe}
15286
+
15287
+ ${rosterFence(roster)}
15288
+ `;
14657
15289
  }
14658
- function relationSlotKey(relation) {
14659
- return [relation.from, relation.predicate, relation.ownerKey].join(SEP);
15290
+ async function persistRoster(storage, roster, path4 = OPENTEAM_ROSTER_PATH) {
15291
+ await storage.write(path4, serializeRoster(roster));
15292
+ return path4;
14660
15293
  }
14661
- function relationValueKey(relation) {
14662
- return [relationSlotKey(relation), relation.to].join(SEP);
15294
+ async function loadRosterFile(storage, path4 = OPENTEAM_ROSTER_PATH) {
15295
+ const content = await storage.read(path4);
15296
+ if (content === undefined) {
15297
+ return { status: "absent", path: path4 };
15298
+ }
15299
+ const parsed = parseRosterResult(content);
15300
+ if (!parsed.ok) {
15301
+ return { status: "invalid", path: path4, error: parsed.error };
15302
+ }
15303
+ return {
15304
+ status: "loaded",
15305
+ path: path4,
15306
+ roster: enforceGuaranteedRoles(parsed.roster)
15307
+ };
14663
15308
  }
14664
- function coalesceFact(existing, incoming) {
14665
- const window = coalesceWindow(existing, incoming);
15309
+ async function loadOrGenerateRoster(storage, generate, path4 = OPENTEAM_ROSTER_PATH) {
15310
+ const existing = await loadRosterFile(storage, path4);
15311
+ if (existing.status === "loaded") {
15312
+ return { roster: existing.roster, regenerated: false };
15313
+ }
15314
+ const roster = enforceGuaranteedRoles(await generate());
15315
+ if (existing.status === "absent") {
15316
+ await persistRoster(storage, roster, path4);
15317
+ return { roster, regenerated: true };
15318
+ }
14666
15319
  return {
14667
- ...existing,
14668
- validFrom: window.validFrom,
14669
- validUntil: window.validUntil,
14670
- confidence: Math.max(existing.confidence, incoming.confidence)
15320
+ roster,
15321
+ regenerated: true,
15322
+ recoveredFromInvalidPersisted: true,
15323
+ invalidPersistedRosterError: existing.error.message
14671
15324
  };
14672
15325
  }
14673
- function applyFact(state, incoming, now) {
14674
- const incomingSlot = factSlotKey(incoming);
14675
- const incomingValue = factValueKey(incoming);
14676
- const superseded = [];
14677
- const records = [];
14678
- let reaffirmed = false;
14679
- for (const fact of state) {
14680
- if (!isActiveAt(fact, now)) {
14681
- records.push(fact);
14682
- continue;
14683
- }
14684
- if (factValueKey(fact) === incomingValue) {
14685
- records.push(coalesceFact(fact, incoming));
14686
- reaffirmed = true;
14687
- continue;
14688
- }
14689
- if (factSlotKey(fact) === incomingSlot) {
14690
- const dead = {
14691
- ...fact,
14692
- invalidatedAt: now,
14693
- supersededBy: incoming.id
14694
- };
14695
- superseded.push(dead);
14696
- records.push(dead);
14697
- continue;
14698
- }
14699
- records.push(fact);
15326
+
15327
+ // src/memory/types.ts
15328
+ import { z as z21 } from "zod";
15329
+ var SHARED_OWNER_KEY = "*";
15330
+ var OwnerKeySchema = z21.string().min(1);
15331
+ var MemoryKindSchema = z21.enum(["fact", "preference", "entity"]);
15332
+ var MemoryBaseSchema = z21.object({
15333
+ id: z21.string().min(1),
15334
+ ownerKey: OwnerKeySchema,
15335
+ confidence: z21.number().min(0).max(1),
15336
+ validFrom: z21.number().finite(),
15337
+ validUntil: z21.number().finite().nullable().default(null),
15338
+ createdAt: z21.number().finite(),
15339
+ invalidatedAt: z21.number().finite().nullable().default(null),
15340
+ sourceHash: z21.string().min(1),
15341
+ supersededBy: z21.string().min(1).nullable().default(null)
15342
+ }).strict();
15343
+ var FactSchema = MemoryBaseSchema.extend({
15344
+ kind: z21.literal("fact"),
15345
+ subject: z21.string().min(1),
15346
+ predicate: z21.string().min(1),
15347
+ object: z21.string().min(1),
15348
+ category: z21.string().min(1).nullable().default(null)
15349
+ });
15350
+ var PreferenceSchema = MemoryBaseSchema.extend({
15351
+ kind: z21.literal("preference"),
15352
+ category: z21.string().min(1),
15353
+ preference: z21.string().min(1),
15354
+ context: z21.string().min(1).nullable().default(null),
15355
+ lastAccessedAt: z21.number().finite().nullable().default(null),
15356
+ accessCount: z21.number().int().min(0).default(0)
15357
+ });
15358
+ var EntitySchema = MemoryBaseSchema.extend({
15359
+ kind: z21.literal("entity"),
15360
+ canonicalName: z21.string().min(1),
15361
+ type: z21.string().min(1),
15362
+ aliases: z21.array(z21.string().min(1)).default([])
15363
+ });
15364
+ var RelationSchema = MemoryBaseSchema.extend({
15365
+ kind: z21.literal("relation"),
15366
+ from: z21.string().min(1),
15367
+ to: z21.string().min(1),
15368
+ predicate: z21.string().min(1),
15369
+ annotation: z21.string().min(1).nullable().default(null)
15370
+ });
15371
+ var MemoryRecordSchema = z21.discriminatedUnion("kind", [
15372
+ FactSchema,
15373
+ PreferenceSchema,
15374
+ EntitySchema,
15375
+ RelationSchema
15376
+ ]);
15377
+ var RecallQuerySchema = z21.object({
15378
+ ownerKey: OwnerKeySchema,
15379
+ kinds: z21.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
15380
+ asOf: z21.number().finite().nullable().default(null),
15381
+ limit: z21.number().int().positive().default(8),
15382
+ minSimilarity: z21.number().min(0).max(1).default(0.2),
15383
+ includeShared: z21.boolean().default(true)
15384
+ }).strict();
15385
+
15386
+ // src/memory/rank.ts
15387
+ var DAY_MS = 86400000;
15388
+ var DEFAULT_RANK_SIGNALS = {
15389
+ similarityWeight: 1,
15390
+ recencyWeight: 0.2,
15391
+ accessWeight: 0.1,
15392
+ recencyHalfLifeMs: 14 * DAY_MS
15393
+ };
15394
+ function recencyTimestamp(record) {
15395
+ if (record.kind === "preference" && record.lastAccessedAt !== null) {
15396
+ return record.lastAccessedAt;
14700
15397
  }
14701
- if (!reaffirmed) {
14702
- records.push(incoming);
15398
+ return record.createdAt;
15399
+ }
15400
+ function recencyScore(record, signals) {
15401
+ const ageMs = Math.max(0, signals.now - recencyTimestamp(record));
15402
+ return 0.5 ** (ageMs / signals.recencyHalfLifeMs);
15403
+ }
15404
+ function accessScore(record) {
15405
+ if (record.kind !== "preference") {
15406
+ return 0;
14703
15407
  }
14704
- return { records, superseded };
15408
+ return 1 - 1 / (1 + record.accessCount);
14705
15409
  }
14706
- function applyPreference(state, incoming, now) {
14707
- const incomingSlot = preferenceSlotKey(incoming);
14708
- const incomingValue = preferenceValueKey(incoming);
14709
- const superseded = [];
14710
- const records = [];
14711
- let reaffirmed = false;
14712
- for (const preference of state) {
14713
- if (!isActiveAt(preference, now)) {
14714
- records.push(preference);
14715
- continue;
14716
- }
14717
- if (preferenceValueKey(preference) === incomingValue) {
15410
+ function scoreCandidate(candidate, signals) {
15411
+ return signals.similarityWeight * candidate.similarity + signals.recencyWeight * recencyScore(candidate.record, signals) + signals.accessWeight * accessScore(candidate.record);
15412
+ }
15413
+ function rankRecall(candidates, signals) {
15414
+ return candidates.map((candidate) => ({
15415
+ ...candidate,
15416
+ score: scoreCandidate(candidate, signals)
15417
+ })).sort((a, b) => {
15418
+ if (b.score !== a.score) {
15419
+ return b.score - a.score;
15420
+ }
15421
+ return a.record.id < b.record.id ? -1 : a.record.id > b.record.id ? 1 : 0;
15422
+ });
15423
+ }
15424
+
15425
+ // src/memory/temporal.ts
15426
+ function isTransactionActiveAt(record, asOf) {
15427
+ return record.invalidatedAt === null || record.invalidatedAt > asOf;
15428
+ }
15429
+ function isValidAt(record, asOf) {
15430
+ if (record.validFrom > asOf) {
15431
+ return false;
15432
+ }
15433
+ return record.validUntil === null || record.validUntil > asOf;
15434
+ }
15435
+ function isActiveAt(record, asOf) {
15436
+ return isTransactionActiveAt(record, asOf) && isValidAt(record, asOf);
15437
+ }
15438
+ function filterActive(records, asOf) {
15439
+ return records.filter((record) => isActiveAt(record, asOf));
15440
+ }
15441
+ function coalesceWindow(a, b) {
15442
+ const validFrom = Math.min(a.validFrom, b.validFrom);
15443
+ const validUntil = a.validUntil === null || b.validUntil === null ? null : Math.max(a.validUntil, b.validUntil);
15444
+ return { validFrom, validUntil };
15445
+ }
15446
+
15447
+ // src/memory/vector.ts
15448
+ function dot(a, b) {
15449
+ if (a.length !== b.length) {
15450
+ throw new Error(`dimension mismatch: ${a.length} vs ${b.length}`);
15451
+ }
15452
+ let sum = 0;
15453
+ for (let i = 0;i < a.length; i += 1) {
15454
+ sum += a[i] * b[i];
15455
+ }
15456
+ return sum;
15457
+ }
15458
+ function norm(a) {
15459
+ let sum = 0;
15460
+ for (let i = 0;i < a.length; i += 1) {
15461
+ const value = a[i];
15462
+ sum += value * value;
15463
+ }
15464
+ return Math.sqrt(sum);
15465
+ }
15466
+ function cosineSimilarity(a, b) {
15467
+ const denom = norm(a) * norm(b);
15468
+ if (denom === 0) {
15469
+ return 0;
15470
+ }
15471
+ return dot(a, b) / denom;
15472
+ }
15473
+
15474
+ // src/memory/recall.ts
15475
+ function matchesScope(record, query) {
15476
+ if (record.ownerKey === query.ownerKey) {
15477
+ return true;
15478
+ }
15479
+ return query.includeShared && record.ownerKey === SHARED_OWNER_KEY;
15480
+ }
15481
+ function recall(records, queryEmbedding, query, deps) {
15482
+ const asOf = query.asOf ?? deps.now;
15483
+ const kinds = new Set(query.kinds);
15484
+ const candidates = [];
15485
+ for (const record of filterActive(records, asOf)) {
15486
+ if (!kinds.has(record.kind) || !matchesScope(record, query)) {
15487
+ continue;
15488
+ }
15489
+ const embedding = deps.embeddings.get(record.id);
15490
+ if (embedding === undefined) {
15491
+ continue;
15492
+ }
15493
+ const similarity = cosineSimilarity(queryEmbedding, embedding);
15494
+ if (similarity < query.minSimilarity) {
15495
+ continue;
15496
+ }
15497
+ candidates.push({ record, similarity });
15498
+ }
15499
+ const signals = {
15500
+ now: deps.now,
15501
+ ...deps.signals ?? DEFAULT_RANK_SIGNALS
15502
+ };
15503
+ return rankRecall(candidates, signals).slice(0, query.limit);
15504
+ }
15505
+
15506
+ // src/memory/merge.ts
15507
+ var SEP = "\x00";
15508
+ function factSlotKey(fact) {
15509
+ return [fact.subject, fact.predicate, fact.ownerKey].join(SEP);
15510
+ }
15511
+ function factValueKey(fact) {
15512
+ return [factSlotKey(fact), fact.object].join(SEP);
15513
+ }
15514
+ function preferenceSlotKey(preference) {
15515
+ return [preference.category, preference.ownerKey].join(SEP);
15516
+ }
15517
+ function preferenceValueKey(preference) {
15518
+ return [preferenceSlotKey(preference), preference.preference].join(SEP);
15519
+ }
15520
+ function entitySlotKey(entity) {
15521
+ return [entity.canonicalName, entity.type, entity.ownerKey].join(SEP);
15522
+ }
15523
+ function relationSlotKey(relation) {
15524
+ return [relation.from, relation.predicate, relation.ownerKey].join(SEP);
15525
+ }
15526
+ function relationValueKey(relation) {
15527
+ return [relationSlotKey(relation), relation.to].join(SEP);
15528
+ }
15529
+ function coalesceFact(existing, incoming) {
15530
+ const window = coalesceWindow(existing, incoming);
15531
+ return {
15532
+ ...existing,
15533
+ validFrom: window.validFrom,
15534
+ validUntil: window.validUntil,
15535
+ confidence: Math.max(existing.confidence, incoming.confidence)
15536
+ };
15537
+ }
15538
+ function applyFact(state, incoming, now) {
15539
+ const incomingSlot = factSlotKey(incoming);
15540
+ const incomingValue = factValueKey(incoming);
15541
+ const superseded = [];
15542
+ const records = [];
15543
+ let reaffirmed = false;
15544
+ for (const fact of state) {
15545
+ if (!isActiveAt(fact, now)) {
15546
+ records.push(fact);
15547
+ continue;
15548
+ }
15549
+ if (factValueKey(fact) === incomingValue) {
15550
+ records.push(coalesceFact(fact, incoming));
15551
+ reaffirmed = true;
15552
+ continue;
15553
+ }
15554
+ if (factSlotKey(fact) === incomingSlot) {
15555
+ const dead = {
15556
+ ...fact,
15557
+ invalidatedAt: now,
15558
+ supersededBy: incoming.id
15559
+ };
15560
+ superseded.push(dead);
15561
+ records.push(dead);
15562
+ continue;
15563
+ }
15564
+ records.push(fact);
15565
+ }
15566
+ if (!reaffirmed) {
15567
+ records.push(incoming);
15568
+ }
15569
+ return { records, superseded };
15570
+ }
15571
+ function applyPreference(state, incoming, now) {
15572
+ const incomingSlot = preferenceSlotKey(incoming);
15573
+ const incomingValue = preferenceValueKey(incoming);
15574
+ const superseded = [];
15575
+ const records = [];
15576
+ let reaffirmed = false;
15577
+ for (const preference of state) {
15578
+ if (!isActiveAt(preference, now)) {
15579
+ records.push(preference);
15580
+ continue;
15581
+ }
15582
+ if (preferenceValueKey(preference) === incomingValue) {
14718
15583
  records.push({
14719
15584
  ...preference,
14720
15585
  confidence: Math.max(preference.confidence, incoming.confidence)
@@ -15070,47 +15935,520 @@ async function recallMemory(deps, queryText, query) {
15070
15935
  return recall(recordRead.records, queryEmbedding, query, recallDeps);
15071
15936
  }
15072
15937
 
15073
- // src/mcp/semanticRecall.ts
15074
- function buildSemanticRecall(deps, policy) {
15075
- return async ({ queryText, kinds, limit }) => {
15076
- const query = RecallQuerySchema.parse({
15077
- ownerKey: policy.ownerKey,
15078
- kinds,
15079
- asOf: policy.asOf ?? null,
15080
- limit,
15081
- minSimilarity: policy.minSimilarity,
15082
- includeShared: policy.includeShared
15083
- });
15084
- const recallDeps = deps.signals === undefined ? { index: deps.index, embedder: deps.embedder, now: deps.now() } : {
15085
- index: deps.index,
15086
- embedder: deps.embedder,
15087
- now: deps.now(),
15088
- signals: deps.signals
15089
- };
15090
- return recallMemory(recallDeps, queryText, query);
15938
+ // src/plugin/orchestrateTool.ts
15939
+ import { tool } from "@opencode-ai/plugin";
15940
+ import { z as z22 } from "zod";
15941
+ var _pluginWarnedRoles = new Set;
15942
+ var RoleAssignmentSchema = z22.object({
15943
+ roleID: z22.string().min(1).describe("Role identifier from the roster"),
15944
+ prompt: z22.string().min(1).describe("Work prompt for this role"),
15945
+ title: z22.string().min(1).optional().describe("Human-readable title"),
15946
+ dependsOn: z22.array(z22.string().min(1)).optional().describe("Role IDs this assignment depends on (DAG edges)")
15947
+ }).strict();
15948
+ var OrchestratePayloadSchema = z22.object({
15949
+ assignments: z22.array(RoleAssignmentSchema).min(1).describe("Role assignments to distribute"),
15950
+ parentSessionID: z22.string().min(1).optional().describe("Parent session for subsessions"),
15951
+ directory: z22.string().min(1).optional().describe("Working directory override")
15952
+ }).strict();
15953
+ function adaptSdkClient(sdk) {
15954
+ return {
15955
+ session: {
15956
+ create: async (args) => {
15957
+ const result = await sdk.session.create(args);
15958
+ const out = {};
15959
+ if (result.data !== undefined) {
15960
+ out.data = result.data;
15961
+ }
15962
+ if (result.error !== undefined) {
15963
+ out.error = result.error;
15964
+ }
15965
+ return out;
15966
+ },
15967
+ prompt: async (args) => {
15968
+ const result = await sdk.session.prompt(args);
15969
+ const out = {};
15970
+ if (result.data !== undefined) {
15971
+ out.data = result.data;
15972
+ }
15973
+ if (result.error !== undefined) {
15974
+ out.error = result.error;
15975
+ }
15976
+ return out;
15977
+ }
15978
+ }
15091
15979
  };
15092
15980
  }
15093
- var DEFAULT_TRAVERSAL_DEPTH = 3;
15094
- function buildRelationTraversal(deps, policy) {
15095
- return async ({ seed, maxDepth }) => {
15096
- const asOf = policy.asOf ?? deps.now();
15097
- return traverseRelations(deps.index.allRecords(), {
15098
- seed,
15099
- ownerKey: policy.ownerKey,
15100
- asOf,
15101
- maxDepth: maxDepth ?? DEFAULT_TRAVERSAL_DEPTH,
15102
- includeShared: policy.includeShared
15103
- });
15104
- };
15981
+
15982
+ // src/local/embeddings.ts
15983
+ function parseEmbeddingsResponse(payload) {
15984
+ if (typeof payload !== "object" || payload === null || !Array.isArray(payload.data)) {
15985
+ throw new Error("Malformed /embeddings response: missing 'data'");
15986
+ }
15987
+ const data = payload.data;
15988
+ return data.map((entry, index) => {
15989
+ const embedding = typeof entry === "object" && entry !== null ? entry.embedding : undefined;
15990
+ if (!Array.isArray(embedding) || !embedding.every((n) => typeof n === "number" && Number.isFinite(n))) {
15991
+ throw new Error(`Malformed /embeddings response: invalid vector at index ${index}`);
15992
+ }
15993
+ return embedding;
15994
+ });
15105
15995
  }
15106
- function buildGraphRecall(deps, policy) {
15107
- return async ({ queryText, limit, maxDepth }) => {
15108
- const query = RecallQuerySchema.parse({
15109
- ownerKey: policy.ownerKey,
15110
- kinds: ["fact", "preference", "entity"],
15111
- asOf: policy.asOf ?? null,
15112
- limit,
15113
- minSimilarity: policy.minSimilarity,
15996
+ async function embedTexts(request, deps) {
15997
+ if (request.texts.length === 0) {
15998
+ return [];
15999
+ }
16000
+ let response;
16001
+ try {
16002
+ response = await deps.fetch(embeddingsEndpoint(request.baseURL), {
16003
+ method: "POST",
16004
+ headers: {
16005
+ "content-type": "application/json",
16006
+ accept: "application/json"
16007
+ },
16008
+ body: JSON.stringify({
16009
+ model: request.modelID,
16010
+ input: [...request.texts]
16011
+ })
16012
+ });
16013
+ } catch (error) {
16014
+ throw new Error(`Local embeddings failed: ${errorMessage(error)}`);
16015
+ }
16016
+ if (!response.ok) {
16017
+ throw new Error(`Local embeddings failed: HTTP ${response.status} on /embeddings`);
16018
+ }
16019
+ let payload;
16020
+ try {
16021
+ payload = await response.json();
16022
+ } catch (error) {
16023
+ throw new Error(`Local embeddings failed: ${errorMessage(error)}`);
16024
+ }
16025
+ const vectors = parseEmbeddingsResponse(payload);
16026
+ if (vectors.length !== request.texts.length) {
16027
+ throw new Error(`Local embeddings returned ${vectors.length} vectors for ${request.texts.length} texts`);
16028
+ }
16029
+ return vectors;
16030
+ }
16031
+ function createLocalEmbedder(request, deps) {
16032
+ return (texts) => embedTexts({ ...request, texts }, deps);
16033
+ }
16034
+
16035
+ // src/memory/embedText.ts
16036
+ function normalize(parts) {
16037
+ return parts.map((part) => part.trim()).filter((part) => part.length > 0).join(" ");
16038
+ }
16039
+ function recordEmbeddingText(record) {
16040
+ if (record.kind === "fact") {
16041
+ return normalize([
16042
+ record.subject,
16043
+ record.predicate,
16044
+ record.object,
16045
+ record.category ?? ""
16046
+ ]);
16047
+ }
16048
+ if (record.kind === "preference") {
16049
+ return normalize([
16050
+ record.category,
16051
+ record.preference,
16052
+ record.context ?? ""
16053
+ ]);
16054
+ }
16055
+ if (record.kind === "relation") {
16056
+ return normalize([
16057
+ record.from,
16058
+ record.predicate,
16059
+ record.to,
16060
+ record.annotation ?? ""
16061
+ ]);
16062
+ }
16063
+ return normalize([record.canonicalName, record.type, ...record.aliases]);
16064
+ }
16065
+
16066
+ // src/storage/index/memoryEmbeddings.ts
16067
+ async function ensureMemoryEmbeddings(deps) {
16068
+ const { index, embedder, model } = deps;
16069
+ const warn = deps.warn ?? console.warn;
16070
+ const storedModel = index.embeddingModel();
16071
+ const modelChanged = storedModel !== null && storedModel !== model;
16072
+ if (modelChanged) {
16073
+ index.clearEmbeddings();
16074
+ }
16075
+ index.setEmbeddingModel(model);
16076
+ const records = index.allRecords();
16077
+ index.pruneEmbeddings();
16078
+ const have = modelChanged ? new Set : new Set(index.embeddingMap().keys());
16079
+ const todo = records.filter((record) => !have.has(record.id));
16080
+ if (todo.length === 0) {
16081
+ return { embedded: 0, failed: 0, modelChanged };
16082
+ }
16083
+ let vectors;
16084
+ try {
16085
+ vectors = await embedder(todo.map(recordEmbeddingText));
16086
+ } catch (error) {
16087
+ const reason = error instanceof Error ? error.message : String(error);
16088
+ warn(`[openteam] could not compute embeddings for ${todo.length} memory record(s): ${reason}. ` + "Recall over them is unavailable until the local runtime is reachable.");
16089
+ return { embedded: 0, failed: todo.length, modelChanged };
16090
+ }
16091
+ if (vectors.length !== todo.length) {
16092
+ warn(`[openteam] could not compute embeddings for ${todo.length} memory record(s): ` + `the embedder returned ${vectors.length} vector(s). Recall over them is unavailable.`);
16093
+ return { embedded: 0, failed: todo.length, modelChanged };
16094
+ }
16095
+ const entries = todo.map((record, position) => ({
16096
+ recordId: record.id,
16097
+ vector: vectors[position]
16098
+ }));
16099
+ index.putEmbeddings(entries);
16100
+ return { embedded: entries.length, failed: 0, modelChanged };
16101
+ }
16102
+
16103
+ // src/storage/index/memoryRuntime.ts
16104
+ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
16105
+ const semantic = resolveMemorySemantic(config);
16106
+ if (!semantic.enabled || semantic.embeddings.provider === "none") {
16107
+ return;
16108
+ }
16109
+ if (options.requireInjectionEnabled === true && !semantic.injection.enabled) {
16110
+ return;
16111
+ }
16112
+ const runtime = config.local.runtimes.find((rt) => rt.enabled && rt.baseURL !== undefined);
16113
+ if (runtime?.baseURL === undefined) {
16114
+ return;
16115
+ }
16116
+ const modelID = semantic.embeddings.model?.modelID ?? runtime.defaultModel.modelID;
16117
+ const openDatabase = deps.openDatabase ?? createBunSqliteDatabase;
16118
+ const makeEmbedder = deps.createEmbedder ?? createLocalEmbedder;
16119
+ const fetchImpl = deps.fetch ?? globalThis.fetch;
16120
+ const now = deps.now ?? (() => Date.now());
16121
+ const warn = deps.warn ?? console.warn;
16122
+ try {
16123
+ const db = await openDatabase(semantic.indexPath);
16124
+ const index = createMemoryIndex(db, warn);
16125
+ const rebuildResult = await rebuildMemoryIndexFromStorage(index, semantic.logPath, { storage: deps.storage });
16126
+ if (rebuildResult.rejectedLines > 0) {
16127
+ warn(`[openteam] memory log rejected ${rebuildResult.rejectedLines} line(s); semantic memory may be incomplete.`);
16128
+ }
16129
+ const embedder = makeEmbedder({ baseURL: runtime.baseURL, modelID }, { fetch: fetchImpl });
16130
+ await ensureMemoryEmbeddings({ index, embedder, model: modelID, warn });
16131
+ return {
16132
+ index,
16133
+ embedder,
16134
+ now,
16135
+ ownerKey: semantic.scope === "user" ? "user" : "project",
16136
+ semantic
16137
+ };
16138
+ } catch {
16139
+ return;
16140
+ }
16141
+ }
16142
+ // package.json
16143
+ var package_default = {
16144
+ name: "@jmanuelcorral/openteam",
16145
+ version: "0.16.1",
16146
+ packageManager: "bun@1.3.14",
16147
+ description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
16148
+ license: "MIT",
16149
+ author: "Jose Manuel Corral",
16150
+ repository: {
16151
+ type: "git",
16152
+ url: "git+https://github.com/jmanuelcorral/openteam.git"
16153
+ },
16154
+ homepage: "https://github.com/jmanuelcorral/openteam#readme",
16155
+ bugs: {
16156
+ url: "https://github.com/jmanuelcorral/openteam/issues"
16157
+ },
16158
+ keywords: [
16159
+ "opencode",
16160
+ "opencode-plugin",
16161
+ "llm",
16162
+ "routing",
16163
+ "local-llm",
16164
+ "ollama",
16165
+ "lm-studio",
16166
+ "foundry-local",
16167
+ "cost-optimization",
16168
+ "multi-agent"
16169
+ ],
16170
+ engines: {
16171
+ bun: ">=1.3",
16172
+ node: "^22.22.2 || ^24.15.0 || >=26.0.0"
16173
+ },
16174
+ type: "module",
16175
+ main: "./dist/index.js",
16176
+ module: "./dist/index.js",
16177
+ types: "./dist/index.d.ts",
16178
+ bin: {
16179
+ openteam: "./dist/cli.js"
16180
+ },
16181
+ exports: {
16182
+ ".": {
16183
+ types: "./dist/index.d.ts",
16184
+ import: "./dist/index.js"
16185
+ },
16186
+ "./package.json": "./package.json"
16187
+ },
16188
+ files: [
16189
+ "dist",
16190
+ "README.md",
16191
+ "LICENSE",
16192
+ "AGENTS.md",
16193
+ ".opencode/openteam.example.json",
16194
+ ".opencode/command/openteam.md"
16195
+ ],
16196
+ publishConfig: {
16197
+ access: "public",
16198
+ registry: "https://registry.npmjs.org/"
16199
+ },
16200
+ trustedDependencies: [],
16201
+ sideEffects: false,
16202
+ scripts: {
16203
+ prebuild: "bun run clean",
16204
+ build: "bun run build:js && bun run build:cli && bun run build:types",
16205
+ "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",
16206
+ "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',
16207
+ "build:types": "tsc -p tsconfig.build.json",
16208
+ clean: `node -e "require('node:fs').rmSync('dist', { recursive: true, force: true })"`,
16209
+ test: "bun test",
16210
+ "test:cov": "bun test --coverage",
16211
+ "certify:shadow": "bun run scripts/certify-artifact.ts shadow",
16212
+ "certify:release": "bun run scripts/certify-artifact.ts release",
16213
+ "coverage:check": "node scripts/check-coverage.mjs",
16214
+ typecheck: "tsc --noEmit",
16215
+ lint: "biome check .",
16216
+ "format:check": "biome format .",
16217
+ "docs:install": "cd docs && bun install --frozen-lockfile --ignore-scripts",
16218
+ "docs:dev": "cd docs && bun run docs:dev",
16219
+ "docs:build": "cd docs && bun run docs:build",
16220
+ "docs:preview": "cd docs && bun run docs:preview",
16221
+ prepublishOnly: "bun run build",
16222
+ "link:local": "bun run build && npm link",
16223
+ "hooks:install": "git config core.hooksPath .githooks"
16224
+ },
16225
+ dependencies: {
16226
+ "@clack/prompts": "1.7.0",
16227
+ "@langchain/core": "1.2.9",
16228
+ "@langchain/langgraph": "1.4.12",
16229
+ "@opencode-ai/plugin": "1.18.19",
16230
+ "@opencode-ai/sdk": "1.18.19",
16231
+ "@opentelemetry/api": "1.9.1",
16232
+ "@opentelemetry/exporter-trace-otlp-http": "0.221.0",
16233
+ "@opentelemetry/resources": "2.10.0",
16234
+ "@opentelemetry/sdk-trace-base": "2.10.0",
16235
+ zod: "4.4.3"
16236
+ },
16237
+ devDependencies: {
16238
+ "@biomejs/biome": "2.5.9",
16239
+ "@types/bun": "1.3.14",
16240
+ typescript: "7.0.2"
16241
+ }
16242
+ };
16243
+
16244
+ // src/version.ts
16245
+ var PACKAGE_VERSION = package_default.version;
16246
+
16247
+ // src/index.ts
16248
+ function warnSoakEvidenceRejections(result, warn) {
16249
+ if (result.rejectedLines === 0) {
16250
+ return;
16251
+ }
16252
+ (warn ?? console.warn)(`[openteam] soak evidence reader rejected ${result.rejectedLines} observation line(s); cutover gate evidence may be incomplete.`);
16253
+ for (const rejection of result.rejections) {
16254
+ (warn ?? console.warn)(`[openteam] • ${rejection.file}:${rejection.lineNumber}: ${rejection.detail}`);
16255
+ }
16256
+ }
16257
+ var SHADOW_CERT_PATH = "artifacts/graph-shadow-certificate.json";
16258
+ var RELEASE_CERT_PATH = "artifacts/graph-release-certificate.json";
16259
+ var SOAK_CERT_PATH = "artifacts/graph-soak-certificate.json";
16260
+ async function readAndParseCertificate(storage, path4, parse2, digestExtractor) {
16261
+ try {
16262
+ const content = await storage.read(path4);
16263
+ if (content === undefined) {
16264
+ return { status: "absent" };
16265
+ }
16266
+ const raw = JSON.parse(content);
16267
+ const cert = parse2(raw);
16268
+ return { status: "valid", digest: digestExtractor(cert) };
16269
+ } catch (error) {
16270
+ if (isMissingFile(error)) {
16271
+ return { status: "absent" };
16272
+ }
16273
+ if (error instanceof ShadowCertificateError || error instanceof ReleaseCertificateError || error instanceof SoakCertificateError) {
16274
+ return { status: "invalid", code: error.code, detail: error.detail };
16275
+ }
16276
+ const msg = error instanceof Error ? error.message : String(error);
16277
+ return { status: "invalid", code: "read-error", detail: msg };
16278
+ }
16279
+ }
16280
+ async function buildGateInput(config, storage, opencodeVersion, warn) {
16281
+ const shadow = await readAndParseCertificate(storage, SHADOW_CERT_PATH, (raw) => parseShadowCertificate(raw, opencodeVersion), (cert) => shadowCertificateBindingDigest(cert.evidence));
16282
+ const shadowDigest = shadow.status === "valid" ? shadow.digest : "0".repeat(64);
16283
+ const release = await readAndParseCertificate(storage, RELEASE_CERT_PATH, (raw) => parseReleaseCertificate(raw, opencodeVersion, shadowDigest), (cert) => cert.digest);
16284
+ let soakLedgerDigest = "0".repeat(64);
16285
+ try {
16286
+ const recorderPorts = recorderPortsFromStorage(storage);
16287
+ const chainRead = await readAllChains(recorderPorts, SOAK_EVIDENCE_DIR);
16288
+ warnSoakEvidenceRejections(chainRead, warn);
16289
+ if (chainRead.chains.length > 0) {
16290
+ soakLedgerDigest = canonicalLedger(chainRead.chains);
16291
+ }
16292
+ } catch {}
16293
+ const soak = await readAndParseCertificate(storage, SOAK_CERT_PATH, (raw) => parseSoakCertificate(raw, DEFAULT_SOAK_POLICY, soakLedgerDigest), (cert) => cert.digest);
16294
+ const migration = "complete";
16295
+ const operatorApproval = config.graph?.operatorApproval ?? false;
16296
+ return { shadow, release, soak, migration, operatorApproval };
16297
+ }
16298
+ 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";
16299
+ async function evaluateCutoverGateForBoundary(config, storage, version) {
16300
+ if (version === undefined) {
16301
+ return { kind: "unevaluable", reason: OPENCODE_VERSION_UNRESOLVED_REASON };
16302
+ }
16303
+ const gateInput = await buildGateInput(config, storage, version, () => {});
16304
+ return { kind: "evaluated", gate: evaluateGraphGate(gateInput) };
16305
+ }
16306
+ function recorderPortsFromStorage(storage) {
16307
+ return {
16308
+ fs: {
16309
+ readFile: async (path4) => {
16310
+ const content = await storage.read(path4);
16311
+ if (content === undefined) {
16312
+ throw Object.assign(new Error(`ENOENT: ${path4}`), { code: "ENOENT" });
16313
+ }
16314
+ return content;
16315
+ },
16316
+ writeFile: (path4, content) => storage.write(path4, content),
16317
+ exists: (path4) => storage.exists(path4),
16318
+ readDir: (path4) => storage.list(path4),
16319
+ mkdir: (path4) => storage.mkdir(path4)
16320
+ },
16321
+ clock: { now: () => new Date().toISOString() },
16322
+ id: { randomUUID: () => crypto.randomUUID() }
16323
+ };
16324
+ }
16325
+ function isMissingFile(error) {
16326
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
16327
+ }
16328
+
16329
+ // src/memory/traverse.ts
16330
+ function inOwnerScope(record, ownerKey, includeShared) {
16331
+ if (record.ownerKey === ownerKey) {
16332
+ return true;
16333
+ }
16334
+ return includeShared && record.ownerKey === SHARED_OWNER_KEY;
16335
+ }
16336
+ function traverseRelations(records, query) {
16337
+ const includeShared = query.includeShared ?? true;
16338
+ const adjacency = new Map;
16339
+ for (const record of records) {
16340
+ if (record.kind !== "relation") {
16341
+ continue;
16342
+ }
16343
+ if (!isActiveAt(record, query.asOf)) {
16344
+ continue;
16345
+ }
16346
+ if (!inOwnerScope(record, query.ownerKey, includeShared)) {
16347
+ continue;
16348
+ }
16349
+ const list = adjacency.get(record.from);
16350
+ if (list === undefined) {
16351
+ adjacency.set(record.from, [record]);
16352
+ } else {
16353
+ list.push(record);
16354
+ }
16355
+ }
16356
+ const visited = new Set([query.seed]);
16357
+ const nodes = [{ id: query.seed, depth: 0 }];
16358
+ const edges = [];
16359
+ let frontier = [query.seed];
16360
+ for (let depth = 0;depth < query.maxDepth; depth += 1) {
16361
+ const nextFrontier = [];
16362
+ for (const nodeId of frontier) {
16363
+ const outgoing = adjacency.get(nodeId) ?? [];
16364
+ for (const edge of outgoing) {
16365
+ if (visited.has(edge.to)) {
16366
+ continue;
16367
+ }
16368
+ visited.add(edge.to);
16369
+ const childDepth = depth + 1;
16370
+ nodes.push({ id: edge.to, depth: childDepth });
16371
+ edges.push({
16372
+ from: edge.from,
16373
+ to: edge.to,
16374
+ predicate: edge.predicate,
16375
+ depth: childDepth
16376
+ });
16377
+ nextFrontier.push(edge.to);
16378
+ }
16379
+ }
16380
+ frontier = nextFrontier;
16381
+ }
16382
+ return { seed: query.seed, nodes, edges };
16383
+ }
16384
+ function seedNodeId(record) {
16385
+ return record.kind === "entity" ? record.canonicalName : undefined;
16386
+ }
16387
+ function recallGraph(records, queryEmbedding, query, options, deps) {
16388
+ const seeds = recall(records, queryEmbedding, query, deps);
16389
+ const asOf = query.asOf ?? deps.now;
16390
+ const includeShared = options.includeShared ?? query.includeShared;
16391
+ const traversals = [];
16392
+ for (const result of seeds) {
16393
+ const seed = seedNodeId(result.record);
16394
+ if (seed === undefined) {
16395
+ continue;
16396
+ }
16397
+ traversals.push({
16398
+ seedRecordId: result.record.id,
16399
+ traversal: traverseRelations(records, {
16400
+ seed,
16401
+ ownerKey: query.ownerKey,
16402
+ asOf,
16403
+ maxDepth: options.maxDepth,
16404
+ includeShared
16405
+ })
16406
+ });
16407
+ }
16408
+ return { recall: seeds, traversals };
16409
+ }
16410
+
16411
+ // src/mcp/semanticRecall.ts
16412
+ function buildSemanticRecall(deps, policy) {
16413
+ return async ({ queryText, kinds, limit }) => {
16414
+ const query = RecallQuerySchema.parse({
16415
+ ownerKey: policy.ownerKey,
16416
+ kinds,
16417
+ asOf: policy.asOf ?? null,
16418
+ limit,
16419
+ minSimilarity: policy.minSimilarity,
16420
+ includeShared: policy.includeShared
16421
+ });
16422
+ const recallDeps = deps.signals === undefined ? { index: deps.index, embedder: deps.embedder, now: deps.now() } : {
16423
+ index: deps.index,
16424
+ embedder: deps.embedder,
16425
+ now: deps.now(),
16426
+ signals: deps.signals
16427
+ };
16428
+ return recallMemory(recallDeps, queryText, query);
16429
+ };
16430
+ }
16431
+ var DEFAULT_TRAVERSAL_DEPTH = 3;
16432
+ function buildRelationTraversal(deps, policy) {
16433
+ return async ({ seed, maxDepth }) => {
16434
+ const asOf = policy.asOf ?? deps.now();
16435
+ return traverseRelations(deps.index.allRecords(), {
16436
+ seed,
16437
+ ownerKey: policy.ownerKey,
16438
+ asOf,
16439
+ maxDepth: maxDepth ?? DEFAULT_TRAVERSAL_DEPTH,
16440
+ includeShared: policy.includeShared
16441
+ });
16442
+ };
16443
+ }
16444
+ function buildGraphRecall(deps, policy) {
16445
+ return async ({ queryText, limit, maxDepth }) => {
16446
+ const query = RecallQuerySchema.parse({
16447
+ ownerKey: policy.ownerKey,
16448
+ kinds: ["fact", "preference", "entity"],
16449
+ asOf: policy.asOf ?? null,
16450
+ limit,
16451
+ minSimilarity: policy.minSimilarity,
15114
16452
  includeShared: policy.includeShared
15115
16453
  });
15116
16454
  const [queryEmbedding] = await deps.embedder([queryText]);
@@ -15362,584 +16700,223 @@ function projectRecall(results) {
15362
16700
  return {
15363
16701
  ...base,
15364
16702
  category: record.category,
15365
- preference: record.preference
15366
- };
15367
- }
15368
- const entity = record;
15369
- return { ...base, canonicalName: entity.canonicalName, type: entity.type };
15370
- });
15371
- }
15372
- function projectGraphRecall(graph) {
15373
- return {
15374
- recall: projectRecall(graph.recall),
15375
- traversals: graph.traversals.map((entry) => ({
15376
- seedRecordId: entry.seedRecordId,
15377
- traversal: projectRelationGraph(entry.traversal)
15378
- }))
15379
- };
15380
- }
15381
- async function callSemanticRecall(recall2, kinds, args) {
15382
- const query = optionalString(args.query);
15383
- if (query === undefined) {
15384
- return textContent({ error: "requiere 'query'" });
15385
- }
15386
- const limit = optionalLimit(args.limit) ?? DEFAULT_RECALL_LIMIT;
15387
- const results = await recall2({ queryText: query, kinds, limit });
15388
- return textContent(projectRecall(results));
15389
- }
15390
- async function callRelationTraversal(traverse, args) {
15391
- const entity = optionalString(args.entity);
15392
- if (entity === undefined) {
15393
- return textContent({ error: "requiere 'entity'" });
15394
- }
15395
- const depth = optionalLimit(args.depth) ?? DEFAULT_TRAVERSAL_DEPTH2;
15396
- const graph = await traverse({ seed: entity, maxDepth: depth });
15397
- return textContent(projectRelationGraph(graph));
15398
- }
15399
- async function callGraphRecall(recall2, args) {
15400
- const query = optionalString(args.query);
15401
- if (query === undefined) {
15402
- return textContent({ error: "requiere 'query'" });
15403
- }
15404
- const limit = optionalLimit(args.limit) ?? DEFAULT_RECALL_LIMIT;
15405
- const depth = optionalLimit(args.depth) ?? DEFAULT_TRAVERSAL_DEPTH2;
15406
- const graph = await recall2({ queryText: query, limit, maxDepth: depth });
15407
- return textContent(projectGraphRecall(graph));
15408
- }
15409
- async function callTool(name, args, deps) {
15410
- if (deps.recallSemantic !== undefined) {
15411
- switch (name) {
15412
- case "recall_facts":
15413
- return callSemanticRecall(deps.recallSemantic, ["fact"], args);
15414
- case "recall_preferences":
15415
- return callSemanticRecall(deps.recallSemantic, ["preference"], args);
15416
- case "similar_tasks":
15417
- return callSemanticRecall(deps.recallSemantic, ["fact", "preference", "entity"], args);
15418
- default:
15419
- break;
15420
- }
15421
- }
15422
- if (deps.recallRelations !== undefined && name === "recall_relations") {
15423
- return callRelationTraversal(deps.recallRelations, args);
15424
- }
15425
- if (deps.recallGraph !== undefined && name === "recall_graph") {
15426
- return callGraphRecall(deps.recallGraph, args);
15427
- }
15428
- const memory = await deps.readMemory();
15429
- switch (name) {
15430
- case "list_decisions": {
15431
- const query = {};
15432
- const agent = optionalString(args.agent);
15433
- const tag = optionalString(args.tag);
15434
- const limit = optionalLimit(args.limit);
15435
- if (agent !== undefined) {
15436
- query.agent = agent;
15437
- }
15438
- if (tag !== undefined) {
15439
- query.tag = tag;
15440
- }
15441
- if (limit !== undefined) {
15442
- query.limit = limit;
15443
- }
15444
- return textContent(queryDecisions(memory, query));
15445
- }
15446
- case "list_meetings":
15447
- return textContent(queryMeetings(memory, { limit: optionalLimit(args.limit) ?? 0 }));
15448
- case "list_sessions":
15449
- return textContent(querySessions(memory, { limit: optionalLimit(args.limit) ?? 0 }));
15450
- case "cost_summary":
15451
- return textContent(costSummary(memory));
15452
- default:
15453
- return;
15454
- }
15455
- }
15456
- async function handleMcpMessage(message, deps) {
15457
- const isNotification = message.id === undefined;
15458
- const id = message.id ?? null;
15459
- const serverInfo = deps.serverInfo ?? DEFAULT_SERVER_INFO;
15460
- switch (message.method) {
15461
- case "initialize":
15462
- return response(id, {
15463
- protocolVersion: MCP_PROTOCOL_VERSION,
15464
- capabilities: { tools: {} },
15465
- serverInfo
15466
- });
15467
- case "tools/list":
15468
- return response(id, { tools: availableTools(deps) });
15469
- case "tools/call": {
15470
- const params = message.params ?? {};
15471
- const name = optionalString(params.name);
15472
- if (name === undefined) {
15473
- return errorResponse(id, -32602, "tools/call requiere 'name'");
15474
- }
15475
- const args = typeof params.arguments === "object" && params.arguments !== null ? params.arguments : {};
15476
- const result = await callTool(name, args, deps);
15477
- if (result === undefined) {
15478
- return errorResponse(id, -32602, `unknown tool: ${name}`);
15479
- }
15480
- return response(id, result);
15481
- }
15482
- default:
15483
- if (isNotification) {
15484
- return null;
15485
- }
15486
- return errorResponse(id, -32601, `unsupported method: ${message.method}`);
15487
- }
15488
- }
15489
-
15490
- // src/mcp/stdio.ts
15491
- function parseErrorResponse() {
15492
- return JSON.stringify({
15493
- jsonrpc: "2.0",
15494
- id: null,
15495
- error: { code: -32700, message: "Invalid JSON" }
15496
- });
15497
- }
15498
- async function runMcpStdio(deps, io) {
15499
- for await (const rawLine of io.lines) {
15500
- const line = rawLine.trim();
15501
- if (line.length === 0) {
15502
- continue;
15503
- }
15504
- let message;
15505
- try {
15506
- message = JSON.parse(line);
15507
- } catch {
15508
- io.write(`${parseErrorResponse()}
15509
- `);
15510
- continue;
15511
- }
15512
- const reply = await handleMcpMessage(message, deps);
15513
- if (reply !== null) {
15514
- io.write(`${JSON.stringify(reply)}
15515
- `);
15516
- }
15517
- }
15518
- }
15519
- async function* linesFromReadable(readable) {
15520
- let buffer = "";
15521
- for await (const chunk of readable) {
15522
- buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
15523
- let newlineIndex = buffer.indexOf(`
15524
- `);
15525
- while (newlineIndex >= 0) {
15526
- yield buffer.slice(0, newlineIndex);
15527
- buffer = buffer.slice(newlineIndex + 1);
15528
- newlineIndex = buffer.indexOf(`
15529
- `);
15530
- }
15531
- }
15532
- if (buffer.length > 0) {
15533
- yield buffer;
15534
- }
15535
- }
15536
-
15537
- // src/orchestrator/rosterPersistence.ts
15538
- function rosterFence(roster) {
15539
- const data = { universe: roster.universe, entries: roster.entries };
15540
- return `\`\`\`json
15541
- ${JSON.stringify(data, null, 2)}
15542
- \`\`\``;
15543
- }
15544
- function serializeRoster(roster) {
15545
- return `# openteam roster
15546
-
15547
- Universe: ${roster.universe}
15548
-
15549
- ${rosterFence(roster)}
15550
- `;
15551
- }
15552
- async function persistRoster(storage, roster, path4 = OPENTEAM_ROSTER_PATH) {
15553
- await storage.write(path4, serializeRoster(roster));
15554
- return path4;
15555
- }
15556
- async function loadRosterFile(storage, path4 = OPENTEAM_ROSTER_PATH) {
15557
- const content = await storage.read(path4);
15558
- if (content === undefined) {
15559
- return { status: "absent", path: path4 };
15560
- }
15561
- const parsed = parseRosterResult(content);
15562
- if (!parsed.ok) {
15563
- return { status: "invalid", path: path4, error: parsed.error };
15564
- }
15565
- return {
15566
- status: "loaded",
15567
- path: path4,
15568
- roster: enforceGuaranteedRoles(parsed.roster)
15569
- };
15570
- }
15571
- async function loadOrGenerateRoster(storage, generate, path4 = OPENTEAM_ROSTER_PATH) {
15572
- const existing = await loadRosterFile(storage, path4);
15573
- if (existing.status === "loaded") {
15574
- return { roster: existing.roster, regenerated: false };
15575
- }
15576
- const roster = enforceGuaranteedRoles(await generate());
15577
- if (existing.status === "absent") {
15578
- await persistRoster(storage, roster, path4);
15579
- return { roster, regenerated: true };
15580
- }
15581
- return {
15582
- roster,
15583
- regenerated: true,
15584
- recoveredFromInvalidPersisted: true,
15585
- invalidPersistedRosterError: existing.error.message
15586
- };
16703
+ preference: record.preference
16704
+ };
16705
+ }
16706
+ const entity = record;
16707
+ return { ...base, canonicalName: entity.canonicalName, type: entity.type };
16708
+ });
15587
16709
  }
15588
-
15589
- // src/plugin/orchestrateTool.ts
15590
- import { tool } from "@opencode-ai/plugin";
15591
- import { z as z20 } from "zod";
15592
- var _pluginWarnedRoles = new Set;
15593
- var RoleAssignmentSchema = z20.object({
15594
- roleID: z20.string().min(1).describe("Role identifier from the roster"),
15595
- prompt: z20.string().min(1).describe("Work prompt for this role"),
15596
- title: z20.string().min(1).optional().describe("Human-readable title"),
15597
- dependsOn: z20.array(z20.string().min(1)).optional().describe("Role IDs this assignment depends on (DAG edges)")
15598
- }).strict();
15599
- var OrchestratePayloadSchema = z20.object({
15600
- assignments: z20.array(RoleAssignmentSchema).min(1).describe("Role assignments to distribute"),
15601
- parentSessionID: z20.string().min(1).optional().describe("Parent session for subsessions"),
15602
- directory: z20.string().min(1).optional().describe("Working directory override")
15603
- }).strict();
15604
- function adaptSdkClient(sdk) {
16710
+ function projectGraphRecall(graph) {
15605
16711
  return {
15606
- session: {
15607
- create: async (args) => {
15608
- const result = await sdk.session.create(args);
15609
- const out = {};
15610
- if (result.data !== undefined) {
15611
- out.data = result.data;
15612
- }
15613
- if (result.error !== undefined) {
15614
- out.error = result.error;
15615
- }
15616
- return out;
15617
- },
15618
- prompt: async (args) => {
15619
- const result = await sdk.session.prompt(args);
15620
- const out = {};
15621
- if (result.data !== undefined) {
15622
- out.data = result.data;
15623
- }
15624
- if (result.error !== undefined) {
15625
- out.error = result.error;
15626
- }
15627
- return out;
15628
- }
15629
- }
16712
+ recall: projectRecall(graph.recall),
16713
+ traversals: graph.traversals.map((entry) => ({
16714
+ seedRecordId: entry.seedRecordId,
16715
+ traversal: projectRelationGraph(entry.traversal)
16716
+ }))
15630
16717
  };
15631
16718
  }
15632
-
15633
- // src/memory/consolidate.ts
15634
- var DAY_MS2 = 86400000;
15635
- var DEFAULT_DECAY_PARAMS = {
15636
- halfLifeMs: 90 * DAY_MS2,
15637
- forgetThreshold: 0.05
15638
- };
15639
- function lastReinforcedAt(record) {
15640
- if (record.kind === "preference" && record.lastAccessedAt !== null) {
15641
- return record.lastAccessedAt;
16719
+ async function callSemanticRecall(recall2, kinds, args) {
16720
+ const query = optionalString(args.query);
16721
+ if (query === undefined) {
16722
+ return textContent({ error: "requiere 'query'" });
15642
16723
  }
15643
- return record.createdAt;
15644
- }
15645
- function decayedConfidence(record, now, params = DEFAULT_DECAY_PARAMS) {
15646
- const ageMs = Math.max(0, now - lastReinforcedAt(record));
15647
- return record.confidence * 0.5 ** (ageMs / params.halfLifeMs);
16724
+ const limit = optionalLimit(args.limit) ?? DEFAULT_RECALL_LIMIT;
16725
+ const results = await recall2({ queryText: query, kinds, limit });
16726
+ return textContent(projectRecall(results));
15648
16727
  }
15649
- function consolidate(records, now, params = DEFAULT_DECAY_PARAMS) {
15650
- const tombstones = [];
15651
- for (const record of records) {
15652
- if (!isTransactionActiveAt(record, now)) {
15653
- continue;
15654
- }
15655
- if (decayedConfidence(record, now, params) < params.forgetThreshold) {
15656
- tombstones.push({ ...record, invalidatedAt: now, supersededBy: null });
15657
- }
16728
+ async function callRelationTraversal(traverse, args) {
16729
+ const entity = optionalString(args.entity);
16730
+ if (entity === undefined) {
16731
+ return textContent({ error: "requiere 'entity'" });
15658
16732
  }
15659
- return tombstones;
16733
+ const depth = optionalLimit(args.depth) ?? DEFAULT_TRAVERSAL_DEPTH2;
16734
+ const graph = await traverse({ seed: entity, maxDepth: depth });
16735
+ return textContent(projectRelationGraph(graph));
15660
16736
  }
15661
-
15662
- // src/storage/index/memoryConsolidate.ts
15663
- async function runConsolidation(logPath, deps) {
15664
- const params = deps.params ?? DEFAULT_DECAY_PARAMS;
15665
- const text2 = await deps.storage.read(logPath) ?? "";
15666
- const { records, rejectedLines } = parseMemoryLog(text2);
15667
- const state = foldAssertions(records);
15668
- const tombstones = consolidate(allRecords(state), deps.now, params);
15669
- if (tombstones.length === 0) {
15670
- return { forgotten: 0, rejectedLines };
16737
+ async function callGraphRecall(recall2, args) {
16738
+ const query = optionalString(args.query);
16739
+ if (query === undefined) {
16740
+ return textContent({ error: "requiere 'query'" });
15671
16741
  }
15672
- const appended = `${tombstones.map(serializeAssertion).join(`
15673
- `)}
15674
- `;
15675
- await deps.storage.append(logPath, appended);
15676
- return { forgotten: tombstones.length, rejectedLines };
16742
+ const limit = optionalLimit(args.limit) ?? DEFAULT_RECALL_LIMIT;
16743
+ const depth = optionalLimit(args.depth) ?? DEFAULT_TRAVERSAL_DEPTH2;
16744
+ const graph = await recall2({ queryText: query, limit, maxDepth: depth });
16745
+ return textContent(projectGraphRecall(graph));
15677
16746
  }
15678
-
15679
- // src/local/embeddings.ts
15680
- function parseEmbeddingsResponse(payload) {
15681
- if (typeof payload !== "object" || payload === null || !Array.isArray(payload.data)) {
15682
- throw new Error("Malformed /embeddings response: missing 'data'");
15683
- }
15684
- const data = payload.data;
15685
- return data.map((entry, index) => {
15686
- const embedding = typeof entry === "object" && entry !== null ? entry.embedding : undefined;
15687
- if (!Array.isArray(embedding) || !embedding.every((n) => typeof n === "number" && Number.isFinite(n))) {
15688
- throw new Error(`Malformed /embeddings response: invalid vector at index ${index}`);
16747
+ async function callTool(name, args, deps) {
16748
+ if (deps.recallSemantic !== undefined) {
16749
+ switch (name) {
16750
+ case "recall_facts":
16751
+ return callSemanticRecall(deps.recallSemantic, ["fact"], args);
16752
+ case "recall_preferences":
16753
+ return callSemanticRecall(deps.recallSemantic, ["preference"], args);
16754
+ case "similar_tasks":
16755
+ return callSemanticRecall(deps.recallSemantic, ["fact", "preference", "entity"], args);
16756
+ default:
16757
+ break;
15689
16758
  }
15690
- return embedding;
15691
- });
15692
- }
15693
- async function embedTexts(request, deps) {
15694
- if (request.texts.length === 0) {
15695
- return [];
15696
- }
15697
- let response2;
15698
- try {
15699
- response2 = await deps.fetch(embeddingsEndpoint(request.baseURL), {
15700
- method: "POST",
15701
- headers: {
15702
- "content-type": "application/json",
15703
- accept: "application/json"
15704
- },
15705
- body: JSON.stringify({
15706
- model: request.modelID,
15707
- input: [...request.texts]
15708
- })
15709
- });
15710
- } catch (error) {
15711
- throw new Error(`Local embeddings failed: ${errorMessage(error)}`);
15712
- }
15713
- if (!response2.ok) {
15714
- throw new Error(`Local embeddings failed: HTTP ${response2.status} on /embeddings`);
15715
- }
15716
- let payload;
15717
- try {
15718
- payload = await response2.json();
15719
- } catch (error) {
15720
- throw new Error(`Local embeddings failed: ${errorMessage(error)}`);
15721
- }
15722
- const vectors = parseEmbeddingsResponse(payload);
15723
- if (vectors.length !== request.texts.length) {
15724
- throw new Error(`Local embeddings returned ${vectors.length} vectors for ${request.texts.length} texts`);
15725
16759
  }
15726
- return vectors;
15727
- }
15728
- function createLocalEmbedder(request, deps) {
15729
- return (texts) => embedTexts({ ...request, texts }, deps);
15730
- }
15731
-
15732
- // src/memory/embedText.ts
15733
- function normalize(parts) {
15734
- return parts.map((part) => part.trim()).filter((part) => part.length > 0).join(" ");
15735
- }
15736
- function recordEmbeddingText(record) {
15737
- if (record.kind === "fact") {
15738
- return normalize([
15739
- record.subject,
15740
- record.predicate,
15741
- record.object,
15742
- record.category ?? ""
15743
- ]);
16760
+ if (deps.recallRelations !== undefined && name === "recall_relations") {
16761
+ return callRelationTraversal(deps.recallRelations, args);
15744
16762
  }
15745
- if (record.kind === "preference") {
15746
- return normalize([
15747
- record.category,
15748
- record.preference,
15749
- record.context ?? ""
15750
- ]);
16763
+ if (deps.recallGraph !== undefined && name === "recall_graph") {
16764
+ return callGraphRecall(deps.recallGraph, args);
15751
16765
  }
15752
- if (record.kind === "relation") {
15753
- return normalize([
15754
- record.from,
15755
- record.predicate,
15756
- record.to,
15757
- record.annotation ?? ""
15758
- ]);
16766
+ const memory = await deps.readMemory();
16767
+ switch (name) {
16768
+ case "list_decisions": {
16769
+ const query = {};
16770
+ const agent = optionalString(args.agent);
16771
+ const tag = optionalString(args.tag);
16772
+ const limit = optionalLimit(args.limit);
16773
+ if (agent !== undefined) {
16774
+ query.agent = agent;
16775
+ }
16776
+ if (tag !== undefined) {
16777
+ query.tag = tag;
16778
+ }
16779
+ if (limit !== undefined) {
16780
+ query.limit = limit;
16781
+ }
16782
+ return textContent(queryDecisions(memory, query));
16783
+ }
16784
+ case "list_meetings":
16785
+ return textContent(queryMeetings(memory, { limit: optionalLimit(args.limit) ?? 0 }));
16786
+ case "list_sessions":
16787
+ return textContent(querySessions(memory, { limit: optionalLimit(args.limit) ?? 0 }));
16788
+ case "cost_summary":
16789
+ return textContent(costSummary(memory));
16790
+ default:
16791
+ return;
15759
16792
  }
15760
- return normalize([record.canonicalName, record.type, ...record.aliases]);
15761
16793
  }
15762
-
15763
- // src/storage/index/memoryEmbeddings.ts
15764
- async function ensureMemoryEmbeddings(deps) {
15765
- const { index, embedder, model } = deps;
15766
- const warn = deps.warn ?? console.warn;
15767
- const storedModel = index.embeddingModel();
15768
- const modelChanged = storedModel !== null && storedModel !== model;
15769
- if (modelChanged) {
15770
- index.clearEmbeddings();
16794
+ async function handleMcpMessage(message, deps) {
16795
+ const isNotification = message.id === undefined;
16796
+ const id = message.id ?? null;
16797
+ const serverInfo = deps.serverInfo ?? DEFAULT_SERVER_INFO;
16798
+ switch (message.method) {
16799
+ case "initialize":
16800
+ return response(id, {
16801
+ protocolVersion: MCP_PROTOCOL_VERSION,
16802
+ capabilities: { tools: {} },
16803
+ serverInfo
16804
+ });
16805
+ case "tools/list":
16806
+ return response(id, { tools: availableTools(deps) });
16807
+ case "tools/call": {
16808
+ const params = message.params ?? {};
16809
+ const name = optionalString(params.name);
16810
+ if (name === undefined) {
16811
+ return errorResponse(id, -32602, "tools/call requiere 'name'");
16812
+ }
16813
+ const args = typeof params.arguments === "object" && params.arguments !== null ? params.arguments : {};
16814
+ const result = await callTool(name, args, deps);
16815
+ if (result === undefined) {
16816
+ return errorResponse(id, -32602, `unknown tool: ${name}`);
16817
+ }
16818
+ return response(id, result);
16819
+ }
16820
+ default:
16821
+ if (isNotification) {
16822
+ return null;
16823
+ }
16824
+ return errorResponse(id, -32601, `unsupported method: ${message.method}`);
15771
16825
  }
15772
- index.setEmbeddingModel(model);
15773
- const records = index.allRecords();
15774
- index.pruneEmbeddings();
15775
- const have = modelChanged ? new Set : new Set(index.embeddingMap().keys());
15776
- const todo = records.filter((record) => !have.has(record.id));
15777
- if (todo.length === 0) {
15778
- return { embedded: 0, failed: 0, modelChanged };
16826
+ }
16827
+
16828
+ // src/mcp/stdio.ts
16829
+ function parseErrorResponse() {
16830
+ return JSON.stringify({
16831
+ jsonrpc: "2.0",
16832
+ id: null,
16833
+ error: { code: -32700, message: "Invalid JSON" }
16834
+ });
16835
+ }
16836
+ async function runMcpStdio(deps, io) {
16837
+ for await (const rawLine of io.lines) {
16838
+ const line = rawLine.trim();
16839
+ if (line.length === 0) {
16840
+ continue;
16841
+ }
16842
+ let message;
16843
+ try {
16844
+ message = JSON.parse(line);
16845
+ } catch {
16846
+ io.write(`${parseErrorResponse()}
16847
+ `);
16848
+ continue;
16849
+ }
16850
+ const reply = await handleMcpMessage(message, deps);
16851
+ if (reply !== null) {
16852
+ io.write(`${JSON.stringify(reply)}
16853
+ `);
16854
+ }
15779
16855
  }
15780
- let vectors;
15781
- try {
15782
- vectors = await embedder(todo.map(recordEmbeddingText));
15783
- } catch (error) {
15784
- const reason = error instanceof Error ? error.message : String(error);
15785
- warn(`[openteam] could not compute embeddings for ${todo.length} memory record(s): ${reason}. ` + "Recall over them is unavailable until the local runtime is reachable.");
15786
- return { embedded: 0, failed: todo.length, modelChanged };
16856
+ }
16857
+ async function* linesFromReadable(readable) {
16858
+ let buffer = "";
16859
+ for await (const chunk of readable) {
16860
+ buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
16861
+ let newlineIndex = buffer.indexOf(`
16862
+ `);
16863
+ while (newlineIndex >= 0) {
16864
+ yield buffer.slice(0, newlineIndex);
16865
+ buffer = buffer.slice(newlineIndex + 1);
16866
+ newlineIndex = buffer.indexOf(`
16867
+ `);
16868
+ }
15787
16869
  }
15788
- if (vectors.length !== todo.length) {
15789
- warn(`[openteam] could not compute embeddings for ${todo.length} memory record(s): ` + `the embedder returned ${vectors.length} vector(s). Recall over them is unavailable.`);
15790
- return { embedded: 0, failed: todo.length, modelChanged };
16870
+ if (buffer.length > 0) {
16871
+ yield buffer;
15791
16872
  }
15792
- const entries = todo.map((record, position) => ({
15793
- recordId: record.id,
15794
- vector: vectors[position]
15795
- }));
15796
- index.putEmbeddings(entries);
15797
- return { embedded: entries.length, failed: 0, modelChanged };
15798
16873
  }
15799
16874
 
15800
- // src/storage/index/memoryRuntime.ts
15801
- async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
15802
- const semantic = resolveMemorySemantic(config);
15803
- if (!semantic.enabled || semantic.embeddings.provider === "none") {
15804
- return;
15805
- }
15806
- if (options.requireInjectionEnabled === true && !semantic.injection.enabled) {
15807
- return;
15808
- }
15809
- const runtime = config.local.runtimes.find((rt) => rt.enabled && rt.baseURL !== undefined);
15810
- if (runtime?.baseURL === undefined) {
15811
- return;
16875
+ // src/memory/consolidate.ts
16876
+ var DAY_MS2 = 86400000;
16877
+ var DEFAULT_DECAY_PARAMS = {
16878
+ halfLifeMs: 90 * DAY_MS2,
16879
+ forgetThreshold: 0.05
16880
+ };
16881
+ function lastReinforcedAt(record) {
16882
+ if (record.kind === "preference" && record.lastAccessedAt !== null) {
16883
+ return record.lastAccessedAt;
15812
16884
  }
15813
- const modelID = semantic.embeddings.model?.modelID ?? runtime.defaultModel.modelID;
15814
- const openDatabase = deps.openDatabase ?? createBunSqliteDatabase;
15815
- const makeEmbedder = deps.createEmbedder ?? createLocalEmbedder;
15816
- const fetchImpl = deps.fetch ?? globalThis.fetch;
15817
- const now = deps.now ?? (() => Date.now());
15818
- const warn = deps.warn ?? console.warn;
15819
- try {
15820
- const db = await openDatabase(semantic.indexPath);
15821
- const index = createMemoryIndex(db, warn);
15822
- const rebuildResult = await rebuildMemoryIndexFromStorage(index, semantic.logPath, { storage: deps.storage });
15823
- if (rebuildResult.rejectedLines > 0) {
15824
- warn(`[openteam] memory log rejected ${rebuildResult.rejectedLines} line(s); semantic memory may be incomplete.`);
16885
+ return record.createdAt;
16886
+ }
16887
+ function decayedConfidence(record, now, params = DEFAULT_DECAY_PARAMS) {
16888
+ const ageMs = Math.max(0, now - lastReinforcedAt(record));
16889
+ return record.confidence * 0.5 ** (ageMs / params.halfLifeMs);
16890
+ }
16891
+ function consolidate(records, now, params = DEFAULT_DECAY_PARAMS) {
16892
+ const tombstones = [];
16893
+ for (const record of records) {
16894
+ if (!isTransactionActiveAt(record, now)) {
16895
+ continue;
16896
+ }
16897
+ if (decayedConfidence(record, now, params) < params.forgetThreshold) {
16898
+ tombstones.push({ ...record, invalidatedAt: now, supersededBy: null });
15825
16899
  }
15826
- const embedder = makeEmbedder({ baseURL: runtime.baseURL, modelID }, { fetch: fetchImpl });
15827
- await ensureMemoryEmbeddings({ index, embedder, model: modelID, warn });
15828
- return {
15829
- index,
15830
- embedder,
15831
- now,
15832
- ownerKey: semantic.scope === "user" ? "user" : "project",
15833
- semantic
15834
- };
15835
- } catch {
15836
- return;
15837
16900
  }
16901
+ return tombstones;
15838
16902
  }
15839
- // package.json
15840
- var package_default = {
15841
- name: "@jmanuelcorral/openteam",
15842
- version: "0.15.1",
15843
- packageManager: "bun@1.3.14",
15844
- description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
15845
- license: "MIT",
15846
- author: "Jose Manuel Corral",
15847
- repository: {
15848
- type: "git",
15849
- url: "git+https://github.com/jmanuelcorral/openteam.git"
15850
- },
15851
- homepage: "https://github.com/jmanuelcorral/openteam#readme",
15852
- bugs: {
15853
- url: "https://github.com/jmanuelcorral/openteam/issues"
15854
- },
15855
- keywords: [
15856
- "opencode",
15857
- "opencode-plugin",
15858
- "llm",
15859
- "routing",
15860
- "local-llm",
15861
- "ollama",
15862
- "lm-studio",
15863
- "foundry-local",
15864
- "cost-optimization",
15865
- "multi-agent"
15866
- ],
15867
- engines: {
15868
- bun: ">=1.3",
15869
- node: "^22.22.2 || ^24.15.0 || >=26.0.0"
15870
- },
15871
- type: "module",
15872
- main: "./dist/index.js",
15873
- module: "./dist/index.js",
15874
- types: "./dist/index.d.ts",
15875
- bin: {
15876
- openteam: "./dist/cli.js"
15877
- },
15878
- exports: {
15879
- ".": {
15880
- types: "./dist/index.d.ts",
15881
- import: "./dist/index.js"
15882
- },
15883
- "./package.json": "./package.json"
15884
- },
15885
- files: [
15886
- "dist",
15887
- "README.md",
15888
- "LICENSE",
15889
- "AGENTS.md",
15890
- ".opencode/openteam.example.json",
15891
- ".opencode/command/openteam.md"
15892
- ],
15893
- publishConfig: {
15894
- access: "public",
15895
- registry: "https://registry.npmjs.org/"
15896
- },
15897
- trustedDependencies: [],
15898
- sideEffects: false,
15899
- scripts: {
15900
- prebuild: "bun run clean",
15901
- build: "bun run build:js && bun run build:cli && bun run build:types",
15902
- "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",
15903
- "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',
15904
- "build:types": "tsc -p tsconfig.build.json",
15905
- clean: `node -e "require('node:fs').rmSync('dist', { recursive: true, force: true })"`,
15906
- test: "bun test",
15907
- "test:cov": "bun test --coverage",
15908
- "certify:shadow": "bun run scripts/certify-artifact.ts shadow",
15909
- "certify:release": "bun run scripts/certify-artifact.ts release",
15910
- "coverage:check": "node scripts/check-coverage.mjs",
15911
- typecheck: "tsc --noEmit",
15912
- lint: "biome check .",
15913
- "format:check": "biome format .",
15914
- "docs:install": "cd docs && bun install --frozen-lockfile --ignore-scripts",
15915
- "docs:dev": "cd docs && bun run docs:dev",
15916
- "docs:build": "cd docs && bun run docs:build",
15917
- "docs:preview": "cd docs && bun run docs:preview",
15918
- prepublishOnly: "bun run build",
15919
- "link:local": "bun run build && npm link",
15920
- "hooks:install": "git config core.hooksPath .githooks"
15921
- },
15922
- dependencies: {
15923
- "@clack/prompts": "1.7.0",
15924
- "@langchain/core": "1.2.9",
15925
- "@langchain/langgraph": "1.4.12",
15926
- "@opencode-ai/plugin": "1.18.19",
15927
- "@opencode-ai/sdk": "1.18.19",
15928
- "@opentelemetry/api": "1.9.1",
15929
- "@opentelemetry/exporter-trace-otlp-http": "0.221.0",
15930
- "@opentelemetry/resources": "2.10.0",
15931
- "@opentelemetry/sdk-trace-base": "2.10.0",
15932
- zod: "4.4.3"
15933
- },
15934
- devDependencies: {
15935
- "@biomejs/biome": "2.5.9",
15936
- "@types/bun": "1.3.14",
15937
- typescript: "7.0.2"
15938
- }
15939
- };
15940
16903
 
15941
- // src/version.ts
15942
- var PACKAGE_VERSION = package_default.version;
16904
+ // src/storage/index/memoryConsolidate.ts
16905
+ async function runConsolidation(logPath, deps) {
16906
+ const params = deps.params ?? DEFAULT_DECAY_PARAMS;
16907
+ const text2 = await deps.storage.read(logPath) ?? "";
16908
+ const { records, rejectedLines } = parseMemoryLog(text2);
16909
+ const state = foldAssertions(records);
16910
+ const tombstones = consolidate(allRecords(state), deps.now, params);
16911
+ if (tombstones.length === 0) {
16912
+ return { forgotten: 0, rejectedLines };
16913
+ }
16914
+ const appended = `${tombstones.map(serializeAssertion).join(`
16915
+ `)}
16916
+ `;
16917
+ await deps.storage.append(logPath, appended);
16918
+ return { forgotten: tombstones.length, rejectedLines };
16919
+ }
15943
16920
 
15944
16921
  // src/web/git.ts
15945
16922
  function createGitLastCommit(exec) {
@@ -15962,6 +16939,7 @@ function createGitLastCommit(exec) {
15962
16939
 
15963
16940
  // src/cli.ts
15964
16941
  var execFileAsync2 = promisify2(execFile2);
16942
+ var execAsync = promisify2(exec);
15965
16943
  var AGENT_DIR2 = dirname5(ORCHESTRATOR_AGENT_PATH);
15966
16944
  var storage = createFsStorageProvider(process.cwd());
15967
16945
  async function readStoredRosterForDoctor() {
@@ -15990,6 +16968,22 @@ var nodeExec2 = async (command, args) => {
15990
16968
  };
15991
16969
  }
15992
16970
  };
16971
+ var requireFromCli = createRequire2(import.meta.url);
16972
+ function readPackageVersion(packageName) {
16973
+ try {
16974
+ const pkg = requireFromCli(`${packageName}/package.json`);
16975
+ return typeof pkg.version === "string" ? pkg.version : undefined;
16976
+ } catch {
16977
+ return;
16978
+ }
16979
+ }
16980
+ async function runOpencodeVersionCommand() {
16981
+ const { stdout } = await execAsync("opencode --version", {
16982
+ timeout: 4000,
16983
+ windowsHide: true
16984
+ });
16985
+ return stdout;
16986
+ }
15993
16987
  function describeFileReadError(error) {
15994
16988
  const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
15995
16989
  if (code === "ENOENT") {
@@ -16147,7 +17141,11 @@ var deps = {
16147
17141
  storage,
16148
17142
  holder: "openteam-cli",
16149
17143
  exec: nodeExec2,
16150
- repoRoot: process.cwd()
17144
+ repoRoot: process.cwd(),
17145
+ evaluateCutoverGate: async (gateConfig) => evaluateCutoverGateForBoundary(gateConfig, storage, await resolveOpencodeVersionBestEffort({
17146
+ readPackageVersion,
17147
+ runVersionCommand: runOpencodeVersionCommand
17148
+ }))
16151
17149
  }),
16152
17150
  configPath: DEFAULT_CONFIG_PATH,
16153
17151
  telemetryPath: DEFAULT_TELEMETRY_PATH,