@jmanuelcorral/openteam 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -46,8 +46,11 @@ function buildOrchestratorAgent(frontier, options = {}) {
46
46
  "con `mode: subagent` (usa `read`/`glob`/`list`).",
47
47
  "",
48
48
  "- **Si el equipo YA existe** → actúa como coordinador: entiende la tarea,",
49
- " repártela entre los subagentes adecuados con la herramienta `task` y",
50
- " reutiliza el reparto existente. No vuelvas a castear ni recrear agentes.",
49
+ " identifica qué roles del roster la cubren y **delega la distribución a la",
50
+ " herramienta `openteam-orchestrate`** con las asignaciones correspondientes.",
51
+ " openteam se encarga del routing de modelo, la creación de subsesiones y la",
52
+ " telemetría — tú no necesitas usar `task` directamente para roles del roster.",
53
+ " Reutiliza el reparto existente. No vuelvas a castear ni recrear agentes.",
51
54
  "- **Si NO existe equipo** (sin `openteam-roster.md` ni subagentes): **no pidas",
52
55
  " permiso para crearlo**. Entiende primero las tareas que implica la",
53
56
  " petición, diseña el equipo mínimo necesario, **créalo a demanda** y luego",
@@ -148,6 +151,39 @@ function buildOrchestratorAgent(frontier, options = {}) {
148
151
  "Explica brevemente al usuario tu plan (qué va en paralelo y qué en serie, y",
149
152
  "por qué) antes o mientras lanzas las tareas.",
150
153
  "",
154
+ "## Distribución de trabajo con `openteam-orchestrate`",
155
+ "",
156
+ "Cuando el roster existe, **usa la herramienta `openteam-orchestrate`** para",
157
+ "distribuir el trabajo entre los roles del equipo. Esta herramienta delega a",
158
+ "nuestro código el routing de modelo por rol, la creación de subsesiones y la",
159
+ "telemetría — no improvises estos pasos manualmente con `task`.",
160
+ "",
161
+ "Payload (las claves son estrictas — claves desconocidas se rechazan):",
162
+ "",
163
+ "```json",
164
+ "{",
165
+ ' "assignments": [',
166
+ ' { "roleID": "<rol-del-roster>", "prompt": "<tarea accionable>", "title": "<título opcional>" },',
167
+ ' { "roleID": "<otro-rol>", "prompt": "<otra tarea>" }',
168
+ " ],",
169
+ ' "parentSessionID": "<sesión actual, opcional>",',
170
+ ' "directory": "<directorio de trabajo, opcional>"',
171
+ "}",
172
+ "```",
173
+ "",
174
+ "- `assignments` (obligatorio): lista de asignaciones. Cada una tiene un",
175
+ " `roleID` (debe coincidir con un rol del roster) y un `prompt` accionable.",
176
+ " `title` es opcional y solo para legibilidad.",
177
+ "- `parentSessionID` (opcional): la sesión actual, para que las subsesiones",
178
+ " se vinculen como hijas.",
179
+ "- `directory` (opcional): directorio de trabajo si difiere del actual.",
180
+ "",
181
+ "La herramienta devuelve un resumen por rol: modelo seleccionado, éxito o",
182
+ "fallo, y sesión creada. Si un rol falla, el resto sigue ejecutándose.",
183
+ "",
184
+ "**Usa `task` directamente** solo para subagentes ad-hoc que no estén en el",
185
+ "roster (p. ej. un agente creado a demanda para un trabajo puntual).",
186
+ "",
151
187
  "## Lista de tareas con responsable visible",
152
188
  "",
153
189
  "Mantén la lista de tareas de la sesión con `todowrite` y **haz visible quién",
@@ -169,8 +205,9 @@ function buildOrchestratorAgent(frontier, options = {}) {
169
205
  "",
170
206
  "- **Empieza siempre comprobando el equipo** (roster + subagentes) antes de",
171
207
  " delegar; si falta, créalo antes de repartir el trabajo.",
172
- "- Delega el trabajo con la herramienta `task` al subagente adecuado; tú te",
173
- " mantienes como coordinador y no implementas lo que puede hacer un subagente.",
208
+ "- Delega el trabajo del roster con `openteam-orchestrate`; usa `task` solo",
209
+ " para subagentes ad-hoc que no tengan rol en el roster. te mantienes como",
210
+ " coordinador y no implementas lo que puede hacer un subagente.",
174
211
  "- **Lanza en un solo turno las tareas independientes** para que corran en",
175
212
  " paralelo; secuencia solo las que dependen de un resultado previo.",
176
213
  "- Crea subagentes **justo cuando el trabajo lo pide**, nunca “por si acaso”.",
@@ -337,9 +374,11 @@ var PrivacyModeSchema = z.enum([
337
374
  ]);
338
375
  var BaselineModeSchema = z.enum(["auto", "pinned"]);
339
376
  var GraphModeSchema = z.enum(["off", "shadow", "active"]);
377
+ var DEFAULT_GRAPH_JOURNAL_ROOT = ".opencode/openteam/graph/runs";
340
378
  var GraphConfigSchema = z.object({
341
379
  mode: GraphModeSchema.default("off"),
342
380
  killSwitch: z.boolean().default(false),
381
+ journalRoot: z.string().min(1).default(DEFAULT_GRAPH_JOURNAL_ROOT),
343
382
  operatorApproval: z.boolean().default(false),
344
383
  worktrees: z.object({
345
384
  enabled: z.boolean().default(false)
@@ -1883,6 +1922,17 @@ var SessionEndpointEventSchema = EventBaseSchema.extend({
1883
1922
  worktree: z5.string().min(1).optional(),
1884
1923
  title: z5.string().min(1).optional()
1885
1924
  });
1925
+ var ShadowDiagnosticEventSchema = EventBaseSchema.extend({
1926
+ type: z5.literal("shadow-diagnostic"),
1927
+ batchID: z5.string().min(1),
1928
+ failureClass: z5.enum([
1929
+ "invalid-node-id",
1930
+ "journal-create-failed",
1931
+ "journal-append-failed",
1932
+ "observation-failed",
1933
+ "unknown"
1934
+ ])
1935
+ });
1886
1936
  var OpenTeamEventSchema = z5.discriminatedUnion("type", [
1887
1937
  RouteEventSchema,
1888
1938
  MessageEventSchema,
@@ -1890,7 +1940,8 @@ var OpenTeamEventSchema = z5.discriminatedUnion("type", [
1890
1940
  MeetingEventSchema,
1891
1941
  DecisionEventSchema,
1892
1942
  ActivityEventSchema,
1893
- SessionEndpointEventSchema
1943
+ SessionEndpointEventSchema,
1944
+ ShadowDiagnosticEventSchema
1894
1945
  ]);
1895
1946
 
1896
1947
  // src/plugin/capture.ts
@@ -2669,7 +2720,10 @@ async function runCli(argv, deps) {
2669
2720
  const config = await deps.loadConfig(configPath);
2670
2721
  return { exitCode: 0, stdout: renderConsoleStatus(config.console) };
2671
2722
  }
2672
- if (command === undefined || command === "help" || command === "--help") {
2723
+ if (command === "--version" || command === "-v") {
2724
+ return { exitCode: 0, stdout: deps.version };
2725
+ }
2726
+ if (command === undefined || command === "help" || command === "--help" || command === "-h") {
2673
2727
  return { exitCode: 0, stdout: HELP };
2674
2728
  }
2675
2729
  return {
@@ -2835,6 +2889,85 @@ var GraphEventV1Schema = z7.discriminatedUnion("type", [
2835
2889
  RunCancelledEventSchema
2836
2890
  ]);
2837
2891
 
2892
+ class GraphSchemaError extends Error {
2893
+ code;
2894
+ issues;
2895
+ constructor(code, error) {
2896
+ super(`${code}: ${error.issues.map((issue) => issue.message).join("; ")}`);
2897
+ this.name = "GraphSchemaError";
2898
+ this.code = code;
2899
+ this.issues = error.issues.map((issue) => issue.message);
2900
+ }
2901
+ }
2902
+ function parseGraphSpec(input) {
2903
+ const result = GraphSpecV1Schema.safeParse(input);
2904
+ if (!result.success) {
2905
+ throw new GraphSchemaError("invalid-spec", result.error);
2906
+ }
2907
+ return result.data;
2908
+ }
2909
+ function parseGraphEvent(input) {
2910
+ const result = GraphEventV1Schema.safeParse(input);
2911
+ if (!result.success) {
2912
+ throw new GraphSchemaError("invalid-event", result.error);
2913
+ }
2914
+ return result.data;
2915
+ }
2916
+
2917
+ // src/storage/graph/provider.ts
2918
+ class JournalError extends Error {
2919
+ code;
2920
+ detail;
2921
+ constructor(code, detail) {
2922
+ super(`${code}: ${detail}`);
2923
+ this.name = "JournalError";
2924
+ this.code = code;
2925
+ this.detail = detail;
2926
+ }
2927
+ }
2928
+
2929
+ // src/storage/graph/writer.ts
2930
+ import {
2931
+ closeSync,
2932
+ existsSync,
2933
+ fsyncSync,
2934
+ mkdirSync,
2935
+ openSync,
2936
+ readFileSync,
2937
+ realpathSync,
2938
+ writeSync
2939
+ } from "node:fs";
2940
+ function ensureDir(dir) {
2941
+ mkdirSync(dir, { recursive: true });
2942
+ }
2943
+ function writeDurable(file, content) {
2944
+ const fd = openSync(file, "w");
2945
+ try {
2946
+ writeSync(fd, content);
2947
+ fsyncSync(fd);
2948
+ } finally {
2949
+ closeSync(fd);
2950
+ }
2951
+ }
2952
+ function appendDurable(file, content) {
2953
+ const fd = openSync(file, "a");
2954
+ try {
2955
+ writeSync(fd, content);
2956
+ fsyncSync(fd);
2957
+ } finally {
2958
+ closeSync(fd);
2959
+ }
2960
+ }
2961
+ function readText(file) {
2962
+ if (!existsSync(file)) {
2963
+ return;
2964
+ }
2965
+ return readFileSync(file, "utf8");
2966
+ }
2967
+ function pathExists(path) {
2968
+ return existsSync(path);
2969
+ }
2970
+
2838
2971
  // src/graph/ids.ts
2839
2972
  var NUL = "\x00";
2840
2973
  function operationID(runID, nodeID, revision = 0) {
@@ -4891,6 +5024,893 @@ function buildMemoryInjector(deps, policy) {
4891
5024
  };
4892
5025
  }
4893
5026
 
5027
+ // src/plugin/orchestrateTool.ts
5028
+ import { tool as tool3 } from "@opencode-ai/plugin";
5029
+ import { z as z13 } from "zod";
5030
+
5031
+ // src/orchestrator/permissions.ts
5032
+ var ROLE_PERMISSIONS = {
5033
+ rusty: ["read", "edit", "multiFileEdit", "destructive", "network", "shell"],
5034
+ livingston: ["read", "edit", "multiFileEdit", "network", "shell"],
5035
+ yen: ["read", "edit", "network", "shell"],
5036
+ basher: ["read", "edit"],
5037
+ scribe: ["read", "edit"],
5038
+ linus: ["read", "edit", "shell"],
5039
+ reviewer: ["read"]
5040
+ };
5041
+ var elevatedTier = {
5042
+ trivial: "simple",
5043
+ simple: "moderate",
5044
+ moderate: "hard",
5045
+ hard: "hard"
5046
+ };
5047
+ function elevateTierForPermissions(baseTier, perms) {
5048
+ const needsElevation = perms.includes("destructive") || perms.includes("multiFileEdit");
5049
+ return needsElevation ? elevatedTier[baseTier] : baseTier;
5050
+ }
5051
+
5052
+ // src/orchestrator/roles.ts
5053
+ import { z as z12 } from "zod";
5054
+ var modelSelectionSchema = z12.object({
5055
+ providerID: z12.string().min(1),
5056
+ modelID: z12.string().min(1)
5057
+ });
5058
+ var roleCapabilityTierSchema = z12.union([
5059
+ z12.literal(1),
5060
+ z12.literal(2),
5061
+ z12.literal(3),
5062
+ z12.literal(4),
5063
+ z12.literal(5)
5064
+ ]);
5065
+ var AgentRoleProfileSchema = z12.object({
5066
+ roleID: z12.string().min(1),
5067
+ opencodeAgent: z12.string().min(1).optional(),
5068
+ defaultTier: z12.enum(["trivial", "simple", "moderate", "hard"]),
5069
+ minReasoningTier: roleCapabilityTierSchema,
5070
+ minCodeQualityTier: roleCapabilityTierSchema,
5071
+ needsToolCallingByDefault: z12.boolean(),
5072
+ preferredLocalModels: z12.array(modelSelectionSchema),
5073
+ preferredFrontierModels: z12.array(z12.union([modelSelectionSchema, z12.literal("auto")])),
5074
+ localFirst: z12.boolean()
5075
+ });
5076
+ var TEAM_ROLES = {
5077
+ rusty: {
5078
+ roleID: "rusty",
5079
+ opencodeAgent: "architect",
5080
+ defaultTier: "hard",
5081
+ minReasoningTier: 5,
5082
+ minCodeQualityTier: 5,
5083
+ needsToolCallingByDefault: true,
5084
+ preferredLocalModels: [],
5085
+ preferredFrontierModels: ["auto"],
5086
+ localFirst: false
5087
+ },
5088
+ livingston: {
5089
+ roleID: "livingston",
5090
+ opencodeAgent: "integration",
5091
+ defaultTier: "moderate",
5092
+ minReasoningTier: 4,
5093
+ minCodeQualityTier: 4,
5094
+ needsToolCallingByDefault: true,
5095
+ preferredLocalModels: [{ providerID: "ollama", modelID: "qwen3:8b" }],
5096
+ preferredFrontierModels: ["auto"],
5097
+ localFirst: false
5098
+ },
5099
+ yen: {
5100
+ roleID: "yen",
5101
+ opencodeAgent: "local-runtime",
5102
+ defaultTier: "moderate",
5103
+ minReasoningTier: 3,
5104
+ minCodeQualityTier: 4,
5105
+ needsToolCallingByDefault: true,
5106
+ preferredLocalModels: [
5107
+ { providerID: "ollama", modelID: "qwen3-coder:latest" }
5108
+ ],
5109
+ preferredFrontierModels: ["auto"],
5110
+ localFirst: true
5111
+ },
5112
+ basher: {
5113
+ roleID: "basher",
5114
+ opencodeAgent: "routing-cost",
5115
+ defaultTier: "moderate",
5116
+ minReasoningTier: 3,
5117
+ minCodeQualityTier: 4,
5118
+ needsToolCallingByDefault: false,
5119
+ preferredLocalModels: [{ providerID: "ollama", modelID: "qwen3:8b" }],
5120
+ preferredFrontierModels: ["auto"],
5121
+ localFirst: true
5122
+ },
5123
+ scribe: {
5124
+ roleID: "scribe",
5125
+ opencodeAgent: "scribe",
5126
+ defaultTier: "trivial",
5127
+ minReasoningTier: 1,
5128
+ minCodeQualityTier: 1,
5129
+ needsToolCallingByDefault: false,
5130
+ preferredLocalModels: [{ providerID: "ollama", modelID: "qwen3:8b" }],
5131
+ preferredFrontierModels: [],
5132
+ localFirst: true
5133
+ },
5134
+ linus: {
5135
+ roleID: "linus",
5136
+ opencodeAgent: "tester",
5137
+ defaultTier: "simple",
5138
+ minReasoningTier: 2,
5139
+ minCodeQualityTier: 3,
5140
+ needsToolCallingByDefault: true,
5141
+ preferredLocalModels: [{ providerID: "lmstudio", modelID: "local-coder" }],
5142
+ preferredFrontierModels: ["auto"],
5143
+ localFirst: true
5144
+ },
5145
+ reviewer: {
5146
+ roleID: "reviewer",
5147
+ opencodeAgent: "reviewer",
5148
+ defaultTier: "moderate",
5149
+ minReasoningTier: 4,
5150
+ minCodeQualityTier: 5,
5151
+ needsToolCallingByDefault: false,
5152
+ preferredLocalModels: [{ providerID: "ollama", modelID: "qwen3:8b" }],
5153
+ preferredFrontierModels: ["auto"],
5154
+ localFirst: true
5155
+ }
5156
+ };
5157
+ function getRoleProfile(roleID) {
5158
+ return TEAM_ROLES[roleID];
5159
+ }
5160
+ function roleToRequirement(role, task, tier) {
5161
+ const baseRequirement = deriveRequirement(tier ?? role.defaultTier, task);
5162
+ return {
5163
+ ...baseRequirement,
5164
+ minReasoningTier: Math.max(baseRequirement.minReasoningTier, role.minReasoningTier),
5165
+ minCodeQualityTier: Math.max(baseRequirement.minCodeQualityTier, role.minCodeQualityTier),
5166
+ needsTools: baseRequirement.needsTools || role.needsToolCallingByDefault
5167
+ };
5168
+ }
5169
+
5170
+ // src/orchestrator/subsessions.ts
5171
+ function errorReason(error) {
5172
+ if (error instanceof Error) {
5173
+ return error.message;
5174
+ }
5175
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
5176
+ return error.message;
5177
+ }
5178
+ return String(error);
5179
+ }
5180
+ function createArgs(req) {
5181
+ const body = {};
5182
+ const args = { body };
5183
+ if (req.parentSessionID !== undefined) {
5184
+ body.parentID = req.parentSessionID;
5185
+ }
5186
+ if (req.title !== undefined) {
5187
+ body.title = req.title;
5188
+ }
5189
+ if (req.directory !== undefined) {
5190
+ args.query = { directory: req.directory };
5191
+ }
5192
+ return args;
5193
+ }
5194
+ function promptArgs(sessionID, req) {
5195
+ const body = {
5196
+ model: req.model,
5197
+ parts: [{ type: "text", text: req.prompt }]
5198
+ };
5199
+ if (req.agent !== undefined) {
5200
+ body.agent = req.agent;
5201
+ }
5202
+ if (req.systemPrompt !== undefined) {
5203
+ body.system = req.systemPrompt;
5204
+ }
5205
+ return {
5206
+ path: { id: sessionID },
5207
+ body
5208
+ };
5209
+ }
5210
+ async function withTimeout(promise, timeoutMs) {
5211
+ if (timeoutMs === undefined) {
5212
+ return promise;
5213
+ }
5214
+ let timeoutID;
5215
+ const timeout = new Promise((_, reject) => {
5216
+ timeoutID = setTimeout(() => {
5217
+ reject(new Error(`prompt timed out after ${timeoutMs}ms`));
5218
+ }, timeoutMs);
5219
+ });
5220
+ try {
5221
+ return await Promise.race([promise, timeout]);
5222
+ } finally {
5223
+ clearTimeout(timeoutID);
5224
+ }
5225
+ }
5226
+ async function runSubsession(client, req, deps = {}) {
5227
+ let sessionID;
5228
+ try {
5229
+ const created = await client.session.create(createArgs(req));
5230
+ if (created.error !== undefined) {
5231
+ return {
5232
+ success: false,
5233
+ roleID: req.roleID,
5234
+ model: req.model,
5235
+ failureStage: "create",
5236
+ failureReason: errorReason(created.error)
5237
+ };
5238
+ }
5239
+ sessionID = created.data?.id;
5240
+ if (sessionID === undefined || sessionID.length === 0) {
5241
+ return {
5242
+ success: false,
5243
+ roleID: req.roleID,
5244
+ model: req.model,
5245
+ failureStage: "create",
5246
+ failureReason: "create response did not include a session id"
5247
+ };
5248
+ }
5249
+ } catch (error) {
5250
+ return {
5251
+ success: false,
5252
+ roleID: req.roleID,
5253
+ model: req.model,
5254
+ failureStage: "create",
5255
+ failureReason: errorReason(error)
5256
+ };
5257
+ }
5258
+ try {
5259
+ const prompted = await withTimeout(client.session.prompt(promptArgs(sessionID, req)), deps.timeoutMs);
5260
+ if (prompted.error !== undefined) {
5261
+ return {
5262
+ success: false,
5263
+ sessionID,
5264
+ roleID: req.roleID,
5265
+ model: req.model,
5266
+ failureStage: "prompt",
5267
+ failureReason: errorReason(prompted.error)
5268
+ };
5269
+ }
5270
+ return {
5271
+ success: true,
5272
+ sessionID,
5273
+ roleID: req.roleID,
5274
+ model: req.model,
5275
+ response: prompted.data
5276
+ };
5277
+ } catch (error) {
5278
+ return {
5279
+ success: false,
5280
+ sessionID,
5281
+ roleID: req.roleID,
5282
+ model: req.model,
5283
+ failureStage: "prompt",
5284
+ failureReason: errorReason(error)
5285
+ };
5286
+ }
5287
+ }
5288
+
5289
+ // src/orchestrator/coordinator.ts
5290
+ function defaultRole() {
5291
+ return TEAM_ROLES.scribe;
5292
+ }
5293
+ function roleFor(roleID) {
5294
+ return getRoleProfile(roleID) ?? defaultRole();
5295
+ }
5296
+ function permissionsFor(roleID) {
5297
+ return ROLE_PERMISSIONS[roleID] ?? [];
5298
+ }
5299
+ function roleRequiresFrontier(role, task) {
5300
+ return task.explicitOverride === undefined && role.localFirst === false && role.defaultTier === "hard" && role.preferredFrontierModels.length > 0;
5301
+ }
5302
+ function roleAdjustedTask(role, input, requirement) {
5303
+ const task = {
5304
+ ...input.task,
5305
+ prompt: input.task.prompt ?? input.prompt
5306
+ };
5307
+ if (requirement.needsTools) {
5308
+ task.requiresTools = true;
5309
+ }
5310
+ if (roleRequiresFrontier(role, task)) {
5311
+ task.explicitOverride = "alwaysFrontier";
5312
+ }
5313
+ return task;
5314
+ }
5315
+ function satisfiesRoleRequirement(requirement, profile) {
5316
+ return profile.kind === "frontier" && profile.availability !== "unavailable" && profile.contextWindow >= requirement.minContextWindow && profile.reasoningTier >= requirement.minReasoningTier && profile.codeQualityTier >= requirement.minCodeQualityTier && (!requirement.needsTools || profile.supportsToolCalling) && (!requirement.needsVision || profile.supportsVision);
5317
+ }
5318
+ function roleCapableProfiles(profiles, requirement) {
5319
+ if (profiles === undefined) {
5320
+ return;
5321
+ }
5322
+ return profiles.filter((profile) => satisfiesRoleRequirement(requirement, profile));
5323
+ }
5324
+ function routingInput(input, role) {
5325
+ const classifiedTier = classifyHeuristic({
5326
+ ...input.task,
5327
+ prompt: input.task.prompt ?? input.prompt
5328
+ }).tier;
5329
+ const permissionTier = elevateTierForPermissions(classifiedTier, permissionsFor(input.roleID));
5330
+ const tier = permissionTier;
5331
+ const requirement = roleToRequirement(role, input.task, tier);
5332
+ const task = roleAdjustedTask(role, input, requirement);
5333
+ const baseInput = {
5334
+ config: input.config,
5335
+ task,
5336
+ tier
5337
+ };
5338
+ const profiles = roleCapableProfiles(input.profiles, requirement);
5339
+ if (profiles !== undefined) {
5340
+ baseInput.profiles = profiles;
5341
+ }
5342
+ if (input.availableModels !== undefined) {
5343
+ baseInput.availableModels = input.availableModels;
5344
+ }
5345
+ if (input.budgetState !== undefined) {
5346
+ baseInput.budgetState = input.budgetState;
5347
+ }
5348
+ return baseInput;
5349
+ }
5350
+ function recordSessionID(input, subsession) {
5351
+ return subsession.sessionID ?? input.parentSessionID;
5352
+ }
5353
+ function buildRecord(input, decision2, subsession, deps, decisionID, batchID) {
5354
+ const context = {
5355
+ ts: deps.now(),
5356
+ promptHash: hashPrompt(input.prompt),
5357
+ promptChars: input.task.promptChars
5358
+ };
5359
+ const sessionID = recordSessionID(input, subsession);
5360
+ if (sessionID !== undefined) {
5361
+ context.sessionID = sessionID;
5362
+ }
5363
+ if (input.task.estimatedInputTokens !== undefined) {
5364
+ context.tokensIn = input.task.estimatedInputTokens;
5365
+ }
5366
+ if (input.task.estimatedOutputTokens !== undefined) {
5367
+ context.tokensOut = input.task.estimatedOutputTokens;
5368
+ }
5369
+ const base = toCostRecord(decision2, context);
5370
+ const record = {
5371
+ ...base,
5372
+ decisionID,
5373
+ agent: input.roleID,
5374
+ success: subsession.success
5375
+ };
5376
+ if (batchID !== undefined) {
5377
+ record.batchID = batchID;
5378
+ }
5379
+ if (input.taskID !== undefined) {
5380
+ record.taskID = input.taskID;
5381
+ }
5382
+ if (subsession.failureReason !== undefined) {
5383
+ record.failureReason = subsession.failureReason;
5384
+ }
5385
+ if (subsession.failureStage !== undefined) {
5386
+ record.failureStage = subsession.failureStage;
5387
+ }
5388
+ return record;
5389
+ }
5390
+ function classifyFailureForTelemetry(failureReason, failureStage) {
5391
+ const lower = failureReason.toLowerCase();
5392
+ if (lower.includes("timed out") || lower.includes("timeout")) {
5393
+ return "prompt-timeout";
5394
+ }
5395
+ if (lower.includes("did not include a session id") || lower.includes("missing session")) {
5396
+ return "create-no-id";
5397
+ }
5398
+ if (failureStage === "create") {
5399
+ return "create-rejected";
5400
+ }
5401
+ if (failureStage === "prompt") {
5402
+ return "prompt-rejected";
5403
+ }
5404
+ return "unknown";
5405
+ }
5406
+ function correlatedCostRecordToRouteEvent(record) {
5407
+ const event = {
5408
+ ...costRecordToRouteEvent(record),
5409
+ decisionID: record.decisionID,
5410
+ agent: record.agent,
5411
+ success: record.success
5412
+ };
5413
+ if (record.batchID !== undefined) {
5414
+ event.batchID = record.batchID;
5415
+ }
5416
+ if (record.taskID !== undefined) {
5417
+ event.taskID = record.taskID;
5418
+ }
5419
+ if (record.failureReason !== undefined) {
5420
+ event.failureReason = classifyFailureForTelemetry(record.failureReason, record.failureStage);
5421
+ }
5422
+ if (record.failureStage !== undefined) {
5423
+ event.failureStage = record.failureStage;
5424
+ }
5425
+ return event;
5426
+ }
5427
+ function buildMeetingEvent(inputs, batchID, decisionIDs, ts) {
5428
+ const sessionID = inputs.find((input) => input.parentSessionID !== undefined)?.parentSessionID;
5429
+ if (sessionID === undefined) {
5430
+ return;
5431
+ }
5432
+ return {
5433
+ v: EVENT_SCHEMA_VERSION,
5434
+ type: "meeting",
5435
+ ts,
5436
+ sessionID,
5437
+ batchID,
5438
+ roles: inputs.map((input) => input.roleID),
5439
+ decisionIDs: [...decisionIDs]
5440
+ };
5441
+ }
5442
+ async function safeEmit(sink, event) {
5443
+ try {
5444
+ await sink.emit(event);
5445
+ } catch {}
5446
+ }
5447
+ async function runRoleTaskWithCorrelation(input, deps, correlation) {
5448
+ const role = roleFor(input.roleID);
5449
+ const decision2 = chooseModel(routingInput(input, role));
5450
+ const request = {
5451
+ roleID: input.roleID,
5452
+ model: decision2.selected,
5453
+ prompt: input.prompt
5454
+ };
5455
+ if (input.parentSessionID !== undefined) {
5456
+ request.parentSessionID = input.parentSessionID;
5457
+ }
5458
+ if (input.title !== undefined) {
5459
+ request.title = input.title;
5460
+ }
5461
+ if (input.systemPrompt !== undefined) {
5462
+ request.systemPrompt = input.systemPrompt;
5463
+ }
5464
+ if (role.opencodeAgent !== undefined) {
5465
+ request.agent = role.opencodeAgent;
5466
+ }
5467
+ if (input.directory !== undefined) {
5468
+ request.directory = input.directory;
5469
+ }
5470
+ const subsessionDeps = deps.timeoutMs === undefined ? {} : { timeoutMs: deps.timeoutMs };
5471
+ const subsession = await runSubsession(deps.client, request, subsessionDeps);
5472
+ const record = buildRecord(input, decision2, subsession, deps, correlation.decisionID, correlation.batchID);
5473
+ await safeEmit(deps.sink, correlatedCostRecordToRouteEvent(record));
5474
+ return { decision: decision2, subsession, record };
5475
+ }
5476
+ function topologicalWaves(inputs) {
5477
+ const waves = [];
5478
+ const placed = new Set;
5479
+ while (placed.size < inputs.length) {
5480
+ const wave = [];
5481
+ for (const input of inputs) {
5482
+ if (placed.has(input.roleID))
5483
+ continue;
5484
+ const deps = input.dependsOn ?? [];
5485
+ if (deps.every((dep) => placed.has(dep))) {
5486
+ wave.push(input);
5487
+ }
5488
+ }
5489
+ if (wave.length === 0) {
5490
+ const unplaceable = inputs.filter((i) => !placed.has(i.roleID));
5491
+ waves.push(unplaceable);
5492
+ break;
5493
+ }
5494
+ for (const input of wave) {
5495
+ placed.add(input.roleID);
5496
+ }
5497
+ waves.push(wave);
5498
+ }
5499
+ return waves;
5500
+ }
5501
+ async function runRoleTasks(inputs, deps) {
5502
+ const batchID = deps.newDecisionID();
5503
+ const planned = inputs.map((input) => ({
5504
+ input,
5505
+ correlation: { decisionID: deps.newDecisionID(), batchID }
5506
+ }));
5507
+ const meeting = buildMeetingEvent(inputs, batchID, planned.map((entry) => entry.correlation.decisionID), deps.now());
5508
+ if (meeting !== undefined) {
5509
+ await safeEmit(deps.sink, meeting);
5510
+ }
5511
+ const correlationByRole = new Map(planned.map((entry) => [entry.input.roleID, entry.correlation]));
5512
+ const waves = topologicalWaves(inputs);
5513
+ const allResults = [];
5514
+ const succeeded = new Set;
5515
+ const cancelled = [];
5516
+ for (const wave of waves) {
5517
+ const runnable = [];
5518
+ for (const input of wave) {
5519
+ const deps_ = input.dependsOn ?? [];
5520
+ if (deps_.every((dep) => succeeded.has(dep))) {
5521
+ runnable.push(input);
5522
+ } else {
5523
+ cancelled.push(input.roleID);
5524
+ }
5525
+ }
5526
+ const waveResults = await Promise.all(runnable.map((input) => {
5527
+ const correlation = correlationByRole.get(input.roleID);
5528
+ if (correlation === undefined) {
5529
+ throw new Error(`missing correlation for ${input.roleID}`);
5530
+ }
5531
+ return runRoleTaskWithCorrelation(input, deps, correlation);
5532
+ }));
5533
+ for (const result of waveResults) {
5534
+ allResults.push(result);
5535
+ if (result.subsession.success) {
5536
+ succeeded.add(result.subsession.roleID);
5537
+ }
5538
+ }
5539
+ }
5540
+ return { batchID, results: allResults, cancelled };
5541
+ }
5542
+
5543
+ // src/plugin/orchestrateTool.ts
5544
+ var RoleAssignmentSchema = z13.object({
5545
+ roleID: z13.string().min(1).describe("Role identifier from the roster"),
5546
+ prompt: z13.string().min(1).describe("Work prompt for this role"),
5547
+ title: z13.string().min(1).optional().describe("Human-readable title"),
5548
+ dependsOn: z13.array(z13.string().min(1)).optional().describe("Role IDs this assignment depends on (DAG edges)")
5549
+ }).strict();
5550
+ var OrchestratePayloadSchema = z13.object({
5551
+ assignments: z13.array(RoleAssignmentSchema).min(1).describe("Role assignments to distribute"),
5552
+ parentSessionID: z13.string().min(1).optional().describe("Parent session for subsessions"),
5553
+ directory: z13.string().min(1).optional().describe("Working directory override")
5554
+ }).strict();
5555
+ function validateAssignmentDependencies(assignments) {
5556
+ const ids = new Set;
5557
+ for (const a of assignments) {
5558
+ if (ids.has(a.roleID)) {
5559
+ return { code: "duplicate-role", detail: a.roleID };
5560
+ }
5561
+ ids.add(a.roleID);
5562
+ }
5563
+ for (const a of assignments) {
5564
+ for (const dep of a.dependsOn ?? []) {
5565
+ if (!ids.has(dep)) {
5566
+ return {
5567
+ code: "dangling-reference",
5568
+ detail: `${a.roleID} depends on unknown role "${dep}"`
5569
+ };
5570
+ }
5571
+ }
5572
+ }
5573
+ const state = new Map;
5574
+ const deps = new Map;
5575
+ for (const a of assignments) {
5576
+ deps.set(a.roleID, a.dependsOn ?? []);
5577
+ }
5578
+ const visit = (id) => {
5579
+ const s = state.get(id);
5580
+ if (s === "done")
5581
+ return;
5582
+ if (s === "visiting")
5583
+ return id;
5584
+ state.set(id, "visiting");
5585
+ for (const dep of deps.get(id) ?? []) {
5586
+ const cycle = visit(dep);
5587
+ if (cycle !== undefined)
5588
+ return cycle;
5589
+ }
5590
+ state.set(id, "done");
5591
+ return;
5592
+ };
5593
+ for (const a of assignments) {
5594
+ const cycle = visit(a.roleID);
5595
+ if (cycle !== undefined) {
5596
+ return { code: "cycle", detail: cycle };
5597
+ }
5598
+ }
5599
+ return;
5600
+ }
5601
+ function adaptSdkClient(sdk) {
5602
+ return {
5603
+ session: {
5604
+ create: async (args) => {
5605
+ const result = await sdk.session.create(args);
5606
+ const out = {};
5607
+ if (result.data !== undefined) {
5608
+ out.data = result.data;
5609
+ }
5610
+ if (result.error !== undefined) {
5611
+ out.error = result.error;
5612
+ }
5613
+ return out;
5614
+ },
5615
+ prompt: async (args) => {
5616
+ const result = await sdk.session.prompt(args);
5617
+ const out = {};
5618
+ if (result.data !== undefined) {
5619
+ out.data = result.data;
5620
+ }
5621
+ if (result.error !== undefined) {
5622
+ out.error = result.error;
5623
+ }
5624
+ return out;
5625
+ }
5626
+ }
5627
+ };
5628
+ }
5629
+ function deriveTaskSignals2(prompt) {
5630
+ return {
5631
+ promptChars: prompt.length,
5632
+ hasCode: /```/.test(prompt),
5633
+ estimatedInputTokens: Math.ceil(prompt.length / 4),
5634
+ estimatedOutputTokens: 0
5635
+ };
5636
+ }
5637
+ function projectResults(results, cancelled = []) {
5638
+ const roles = results.map((r) => {
5639
+ const summary = {
5640
+ roleID: r.subsession.roleID,
5641
+ model: r.decision.selected,
5642
+ success: r.subsession.success
5643
+ };
5644
+ if (r.subsession.sessionID !== undefined) {
5645
+ summary.sessionID = r.subsession.sessionID;
5646
+ }
5647
+ if (r.subsession.failureStage !== undefined) {
5648
+ summary.failureStage = r.subsession.failureStage;
5649
+ }
5650
+ if (r.subsession.failureReason !== undefined) {
5651
+ summary.failureReason = r.subsession.failureReason;
5652
+ }
5653
+ return summary;
5654
+ });
5655
+ const result = {
5656
+ kind: "completed",
5657
+ roles,
5658
+ ...cancelled.length > 0 ? { cancelled } : {}
5659
+ };
5660
+ assertReferenceOnly(result);
5661
+ return result;
5662
+ }
5663
+ function buildRoleTaskInputs(payload, config) {
5664
+ return payload.assignments.map((a) => ({
5665
+ roleID: a.roleID,
5666
+ prompt: a.prompt,
5667
+ task: deriveTaskSignals2(a.prompt),
5668
+ config,
5669
+ ...a.title !== undefined ? { title: a.title } : {},
5670
+ ...a.dependsOn !== undefined && a.dependsOn.length > 0 ? { dependsOn: a.dependsOn } : {},
5671
+ ...payload.parentSessionID !== undefined ? { parentSessionID: payload.parentSessionID } : {},
5672
+ ...payload.directory !== undefined ? { directory: payload.directory } : {}
5673
+ }));
5674
+ }
5675
+ function formatResult(result) {
5676
+ if (result.kind === "error") {
5677
+ return `orchestration error: ${result.reason}`;
5678
+ }
5679
+ const lines = result.roles.map((r) => `${r.roleID}: ${r.success ? "ok" : "failed"} → ${r.model.providerID}/${r.model.modelID}${r.sessionID !== undefined ? ` (session ${r.sessionID})` : ""}${r.failureReason !== undefined ? ` — ${r.failureReason}` : ""}`);
5680
+ const cancelled = result.cancelled ?? [];
5681
+ if (cancelled.length > 0) {
5682
+ lines.push(`⚠ cancelled (dependency failed): ${cancelled.join(", ")}`);
5683
+ }
5684
+ return `orchestration completed (${result.roles.length} role(s)):
5685
+ ${lines.join(`
5686
+ `)}`;
5687
+ }
5688
+ function shadowArtifact2(runID, nodeID) {
5689
+ return {
5690
+ uri: `shadow://${runID}/${nodeID}`,
5691
+ sha256: sha256Hex(`shadow-artifact\x00${runID}\x00${nodeID}`),
5692
+ bytes: 0
5693
+ };
5694
+ }
5695
+ function buildGraphSpecFromAssignments(assignments, batchID) {
5696
+ const anchorDigest = sha256Hex(JSON.stringify({
5697
+ batchID,
5698
+ roles: assignments.map((a) => a.roleID)
5699
+ }));
5700
+ const anchor = { taskID: batchID, specDigest: anchorDigest };
5701
+ const nodes = assignments.map((a) => ({
5702
+ id: a.roleID,
5703
+ role: "implementer",
5704
+ dependsOn: a.dependsOn ?? [],
5705
+ anchor
5706
+ }));
5707
+ return { version: 1, runID: batchID, nodes };
5708
+ }
5709
+ function synthesizeJournalEvents(spec, results, cancelled = []) {
5710
+ const runID = spec.runID;
5711
+ const events = [
5712
+ {
5713
+ v: 1,
5714
+ seq: 0,
5715
+ runID,
5716
+ type: "run.started",
5717
+ specDigest: graphSpecDigest(spec)
5718
+ }
5719
+ ];
5720
+ const resultByRole = new Map;
5721
+ for (const r of results) {
5722
+ resultByRole.set(r.subsession.roleID, r);
5723
+ }
5724
+ const cancelledSet = new Set(cancelled);
5725
+ for (const node of spec.nodes) {
5726
+ if (cancelledSet.has(node.id)) {
5727
+ events.push({
5728
+ v: 1,
5729
+ seq: events.length,
5730
+ runID,
5731
+ type: "node.cancelled",
5732
+ nodeID: node.id
5733
+ });
5734
+ continue;
5735
+ }
5736
+ const result = resultByRole.get(node.id);
5737
+ const operation = operationID(runID, node.id);
5738
+ const attempt = attemptID(operation, 0);
5739
+ events.push({
5740
+ v: 1,
5741
+ seq: events.length,
5742
+ runID,
5743
+ type: "node.dispatched",
5744
+ nodeID: node.id,
5745
+ operationID: operation,
5746
+ attemptID: attempt
5747
+ });
5748
+ if (result === undefined || !result.subsession.success) {
5749
+ events.push({
5750
+ v: 1,
5751
+ seq: events.length,
5752
+ runID,
5753
+ type: "node.failed",
5754
+ nodeID: node.id,
5755
+ operationID: operation,
5756
+ attemptID: attempt,
5757
+ errorClass: "unknown"
5758
+ });
5759
+ } else {
5760
+ events.push({
5761
+ v: 1,
5762
+ seq: events.length,
5763
+ runID,
5764
+ type: "node.succeeded",
5765
+ nodeID: node.id,
5766
+ operationID: operation,
5767
+ attemptID: attempt,
5768
+ artifact: shadowArtifact2(runID, node.id)
5769
+ });
5770
+ }
5771
+ }
5772
+ const allSucceeded = cancelled.length === 0 && results.every((r) => r.subsession.success);
5773
+ events.push({
5774
+ v: 1,
5775
+ seq: events.length,
5776
+ runID,
5777
+ type: allSucceeded ? "run.completed" : "run.failed"
5778
+ });
5779
+ return events;
5780
+ }
5781
+ function buildObservation(batchID, _assignments, results, parentSessionID, cancelled = []) {
5782
+ const allSucceeded = cancelled.length === 0 && results.every((r) => r.subsession.success);
5783
+ return {
5784
+ runID: batchID,
5785
+ executed: true,
5786
+ status: allSucceeded ? "completed" : "failed",
5787
+ fixes: 0,
5788
+ nodes: results.map((r) => ({
5789
+ id: r.subsession.roleID,
5790
+ role: "implementer",
5791
+ model: `${r.decision.selected.providerID}/${r.decision.selected.modelID}`,
5792
+ ok: r.subsession.success,
5793
+ ...r.subsession.sessionID !== undefined ? { sessionRef: r.subsession.sessionID } : {}
5794
+ })),
5795
+ ...parentSessionID !== undefined ? { parentSessionRef: parentSessionID } : {}
5796
+ };
5797
+ }
5798
+ function classifyShadowFailure(_error, phase) {
5799
+ if (phase === "create")
5800
+ return "journal-create-failed";
5801
+ if (phase === "append")
5802
+ return "journal-append-failed";
5803
+ if (phase === "observe")
5804
+ return "observation-failed";
5805
+ return "unknown";
5806
+ }
5807
+ async function emitShadowDiagnostic(sink, batchID, failureClass, now) {
5808
+ const event = {
5809
+ v: EVENT_SCHEMA_VERSION,
5810
+ type: "shadow-diagnostic",
5811
+ ts: now(),
5812
+ sessionID: batchID,
5813
+ batchID,
5814
+ failureClass
5815
+ };
5816
+ try {
5817
+ await sink.emit(event);
5818
+ } catch {}
5819
+ }
5820
+ async function runShadowProducer(deps, payload, batch) {
5821
+ if (deps.graphMode === "off")
5822
+ return { ok: true };
5823
+ if (deps.journal === undefined || deps.observeLegacyExecution === undefined) {
5824
+ return { ok: true };
5825
+ }
5826
+ const spec = buildGraphSpecFromAssignments(payload.assignments, batch.batchID);
5827
+ for (const node of spec.nodes) {
5828
+ const parsed = NodeIDSchema.safeParse(node.id);
5829
+ if (!parsed.success) {
5830
+ const failureClass = "invalid-node-id";
5831
+ await emitShadowDiagnostic(deps.sink, batch.batchID, failureClass, deps.now);
5832
+ return { ok: false, failureClass };
5833
+ }
5834
+ }
5835
+ let handle;
5836
+ try {
5837
+ handle = await deps.journal.create(spec);
5838
+ } catch {
5839
+ const failureClass = classifyShadowFailure(undefined, "create");
5840
+ await emitShadowDiagnostic(deps.sink, batch.batchID, failureClass, deps.now);
5841
+ return { ok: false, failureClass };
5842
+ }
5843
+ const events = synthesizeJournalEvents(spec, batch.results, batch.cancelled);
5844
+ for (let i = 0;i < events.length; i++) {
5845
+ const event = events[i];
5846
+ if (event !== undefined) {
5847
+ try {
5848
+ await deps.journal.append(handle, event, i);
5849
+ } catch {
5850
+ const failureClass = classifyShadowFailure(undefined, "append");
5851
+ await emitShadowDiagnostic(deps.sink, batch.batchID, failureClass, deps.now);
5852
+ return { ok: false, failureClass };
5853
+ }
5854
+ }
5855
+ }
5856
+ const observation = buildObservation(batch.batchID, payload.assignments, batch.results, payload.parentSessionID, batch.cancelled);
5857
+ try {
5858
+ await deps.observeLegacyExecution(observation);
5859
+ } catch {
5860
+ const failureClass = classifyShadowFailure(undefined, "observe");
5861
+ await emitShadowDiagnostic(deps.sink, batch.batchID, failureClass, deps.now);
5862
+ return { ok: false, failureClass };
5863
+ }
5864
+ return { ok: true };
5865
+ }
5866
+ function createOrchestrateTool(deps) {
5867
+ return tool3({
5868
+ description: "Distributes work across roster roles via openteam's coordinator. " + "Each assignment spawns a subsession with model selection driven by " + "the role profile and routing config. Use instead of manually spawning " + "tasks when a roster exists.",
5869
+ args: {
5870
+ assignments: tool3.schema.array(tool3.schema.object({
5871
+ roleID: tool3.schema.string().describe("Role identifier from the roster"),
5872
+ prompt: tool3.schema.string().describe("Work prompt for this role"),
5873
+ title: tool3.schema.string().optional().describe("Human-readable title"),
5874
+ dependsOn: tool3.schema.array(tool3.schema.string()).optional().describe("Role IDs this assignment depends on")
5875
+ })).describe("Role assignments to distribute"),
5876
+ parentSessionID: tool3.schema.string().optional().describe("Parent session for subsessions"),
5877
+ directory: tool3.schema.string().optional().describe("Working directory override")
5878
+ },
5879
+ async execute(args) {
5880
+ const parsed = OrchestratePayloadSchema.safeParse(args);
5881
+ if (!parsed.success) {
5882
+ return `validation error: ${parsed.error.message}`;
5883
+ }
5884
+ const depError = validateAssignmentDependencies(parsed.data.assignments);
5885
+ if (depError !== undefined) {
5886
+ return `dependency validation error: ${depError.code} — ${depError.detail}`;
5887
+ }
5888
+ try {
5889
+ const inputs = buildRoleTaskInputs(parsed.data, deps.config);
5890
+ const coordinatorDeps = {
5891
+ client: deps.client,
5892
+ sink: deps.sink,
5893
+ now: deps.now,
5894
+ newDecisionID: deps.newDecisionID
5895
+ };
5896
+ const batch = await runRoleTasks(inputs, coordinatorDeps);
5897
+ const projected = projectResults(batch.results, batch.cancelled);
5898
+ const shadowResult = await runShadowProducer(deps, parsed.data, batch);
5899
+ const base = formatResult(projected);
5900
+ if (!shadowResult.ok) {
5901
+ return `${base}
5902
+ ⚠ shadow observation failed: ${shadowResult.failureClass}`;
5903
+ }
5904
+ return base;
5905
+ } catch (error) {
5906
+ const reason = error instanceof Error ? error.message : String(error);
5907
+ const result = { kind: "error", reason };
5908
+ return formatResult(result);
5909
+ }
5910
+ }
5911
+ });
5912
+ }
5913
+
4894
5914
  // src/plugin/sessionTracker.ts
4895
5915
  function createSessionTracker() {
4896
5916
  const knownSessions = new Set;
@@ -4950,6 +5970,213 @@ function createToolcallTracker(deps) {
4950
5970
  };
4951
5971
  }
4952
5972
 
5973
+ // src/storage/graph/fsGraphJournal.ts
5974
+ import { join as join2 } from "node:path";
5975
+
5976
+ // src/graph/replay.ts
5977
+ class ReplayError extends Error {
5978
+ code;
5979
+ detail;
5980
+ constructor(detail) {
5981
+ super(`sequence-gap: ${detail}`);
5982
+ this.name = "ReplayError";
5983
+ this.code = "sequence-gap";
5984
+ this.detail = detail;
5985
+ }
5986
+ }
5987
+ function replay(spec, events) {
5988
+ let state = initialState(spec);
5989
+ events.forEach((event, index) => {
5990
+ if (event.seq !== index) {
5991
+ throw new ReplayError(`expected ${index}, got ${event.seq}`);
5992
+ }
5993
+ state = applyEvent(spec, state, event);
5994
+ });
5995
+ return state;
5996
+ }
5997
+
5998
+ // src/storage/graph/codec.ts
5999
+ var CODEC_VERSION = 1;
6000
+ var GENESIS_DIGEST = sha256Hex("openteam/graph-journal/genesis/v1");
6001
+ var NUL2 = "\x00";
6002
+ var NEWLINE = `
6003
+ `;
6004
+ function canonical(value) {
6005
+ if (value === null || typeof value !== "object") {
6006
+ return JSON.stringify(value);
6007
+ }
6008
+ const record = value;
6009
+ const entries = Object.keys(record).sort().map((key2) => `${JSON.stringify(key2)}:${canonical(record[key2])}`);
6010
+ return `{${entries.join(",")}}`;
6011
+ }
6012
+ function frameDigest(prev, event) {
6013
+ return sha256Hex(`${prev}${NUL2}${canonical(event)}`);
6014
+ }
6015
+
6016
+ class JournalCodecError extends Error {
6017
+ code;
6018
+ detail;
6019
+ constructor(code, detail) {
6020
+ super(`${code}: ${detail}`);
6021
+ this.name = "JournalCodecError";
6022
+ this.code = code;
6023
+ this.detail = detail;
6024
+ }
6025
+ }
6026
+ function encodeFrame(prev, seq, event) {
6027
+ const sum = frameDigest(prev, event);
6028
+ const line = canonical({ v: CODEC_VERSION, seq, prev, sum, event });
6029
+ return { line, digest: sum };
6030
+ }
6031
+ function decodeFrame(raw, index, expectedPrev) {
6032
+ let parsed;
6033
+ try {
6034
+ parsed = JSON.parse(raw);
6035
+ } catch {
6036
+ throw new JournalCodecError("bad-frame", `unparseable frame ${index}`);
6037
+ }
6038
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
6039
+ throw new JournalCodecError("bad-frame", `frame ${index} is not an object`);
6040
+ }
6041
+ const frame = parsed;
6042
+ if (frame.v !== CODEC_VERSION) {
6043
+ throw new JournalCodecError("bad-version", `frame ${index}`);
6044
+ }
6045
+ if (typeof frame.seq !== "number" || typeof frame.prev !== "string" || typeof frame.sum !== "string") {
6046
+ throw new JournalCodecError("bad-frame", `frame ${index} header`);
6047
+ }
6048
+ let event;
6049
+ try {
6050
+ event = parseGraphEvent(frame.event);
6051
+ } catch {
6052
+ throw new JournalCodecError("bad-frame", `frame ${index} event`);
6053
+ }
6054
+ if (frame.seq !== index) {
6055
+ throw new JournalCodecError("bad-sequence", `expected ${index}, got ${frame.seq}`);
6056
+ }
6057
+ if (frame.prev !== expectedPrev) {
6058
+ throw new JournalCodecError("chain-break", `frame ${index}`);
6059
+ }
6060
+ if (frame.sum !== frameDigest(frame.prev, event)) {
6061
+ throw new JournalCodecError("bad-checksum", `frame ${index}`);
6062
+ }
6063
+ return { event, digest: frame.sum };
6064
+ }
6065
+ function decodeJournal(text) {
6066
+ const segments = text.split(NEWLINE);
6067
+ const trailing = segments.pop() ?? "";
6068
+ const partialTail = trailing !== "";
6069
+ const events = [];
6070
+ let prev = GENESIS_DIGEST;
6071
+ segments.forEach((raw, index) => {
6072
+ const { event, digest } = decodeFrame(raw, index, prev);
6073
+ events.push(event);
6074
+ prev = digest;
6075
+ });
6076
+ return { events, digest: prev, partialTail };
6077
+ }
6078
+
6079
+ // src/storage/graph/fsGraphJournal.ts
6080
+ var NUL3 = "\x00";
6081
+ var JOURNAL_FILE = "journal.ndjson";
6082
+ var OWNER_FILE = "owner";
6083
+ var SPEC_FILE = "spec.json";
6084
+ function createFsGraphJournal(root) {
6085
+ let counter = 0;
6086
+ const runDir = (runID) => join2(root, runID);
6087
+ const journalPath = (runID) => join2(runDir(runID), JOURNAL_FILE);
6088
+ const ownerPath = (runID) => join2(runDir(runID), OWNER_FILE);
6089
+ const specPath = (runID) => join2(runDir(runID), SPEC_FILE);
6090
+ const issueToken = (runID, prev) => {
6091
+ const token = sha256Hex(`writer${NUL3}${runID}${NUL3}${prev}${NUL3}${counter}`).slice(0, 32);
6092
+ counter += 1;
6093
+ return token;
6094
+ };
6095
+ const readDecoded = (runID) => {
6096
+ const text = readText(journalPath(runID)) ?? "";
6097
+ try {
6098
+ return decodeJournal(text);
6099
+ } catch (error) {
6100
+ throw new JournalError("corrupt", error.message);
6101
+ }
6102
+ };
6103
+ return {
6104
+ async create(spec) {
6105
+ const parsed = parseGraphSpec(structuredClone(spec));
6106
+ if (pathExists(runDir(parsed.runID))) {
6107
+ throw new JournalError("already-exists", parsed.runID);
6108
+ }
6109
+ ensureDir(runDir(parsed.runID));
6110
+ writeDurable(specPath(parsed.runID), JSON.stringify(parsed));
6111
+ writeDurable(journalPath(parsed.runID), "");
6112
+ const token = issueToken(parsed.runID, GENESIS_DIGEST);
6113
+ writeDurable(ownerPath(parsed.runID), token);
6114
+ return { runID: parsed.runID, writerToken: token };
6115
+ },
6116
+ async takeOver(runID) {
6117
+ if (!pathExists(runDir(runID))) {
6118
+ throw new JournalError("not-found", runID);
6119
+ }
6120
+ const prev = readText(ownerPath(runID)) ?? GENESIS_DIGEST;
6121
+ const token = issueToken(runID, prev);
6122
+ writeDurable(ownerPath(runID), token);
6123
+ return { runID, writerToken: token };
6124
+ },
6125
+ async append(handle, event, expectedSeq) {
6126
+ if (!pathExists(runDir(handle.runID))) {
6127
+ throw new JournalError("not-found", handle.runID);
6128
+ }
6129
+ if (readText(ownerPath(handle.runID)) !== handle.writerToken) {
6130
+ throw new JournalError("writer-conflict", handle.runID);
6131
+ }
6132
+ const decoded = readDecoded(handle.runID);
6133
+ if (decoded.partialTail) {
6134
+ throw new JournalError("corrupt", "partial tail; recovery required");
6135
+ }
6136
+ const head = decoded.events.length;
6137
+ if (event.seq < head) {
6138
+ throw new JournalError("duplicate-event", `seq ${event.seq}`);
6139
+ }
6140
+ if (event.seq !== head || expectedSeq !== head) {
6141
+ throw new JournalError("sequence-mismatch", `head=${head} expected=${expectedSeq} event=${event.seq}`);
6142
+ }
6143
+ const frame = encodeFrame(decoded.digest, head, event);
6144
+ appendDurable(journalPath(handle.runID), `${frame.line}
6145
+ `);
6146
+ },
6147
+ async load(runID) {
6148
+ if (!pathExists(runDir(runID))) {
6149
+ throw new JournalError("not-found", runID);
6150
+ }
6151
+ const decoded = readDecoded(runID);
6152
+ const specText = readText(specPath(runID));
6153
+ if (specText === undefined) {
6154
+ throw new JournalError("corrupt", "missing spec");
6155
+ }
6156
+ let spec;
6157
+ try {
6158
+ spec = parseGraphSpec(JSON.parse(specText));
6159
+ } catch (error) {
6160
+ throw new JournalError("corrupt", error.message);
6161
+ }
6162
+ let loaded;
6163
+ try {
6164
+ loaded = {
6165
+ spec,
6166
+ events: decoded.events,
6167
+ state: replay(spec, decoded.events)
6168
+ };
6169
+ } catch (error) {
6170
+ throw new JournalError("corrupt", error.message);
6171
+ }
6172
+ return loaded;
6173
+ },
6174
+ async exists(runID) {
6175
+ return pathExists(runDir(runID));
6176
+ }
6177
+ };
6178
+ }
6179
+
4953
6180
  // src/local/embeddings.ts
4954
6181
  function parseEmbeddingsResponse(payload) {
4955
6182
  if (typeof payload !== "object" || payload === null || !Array.isArray(payload.data)) {
@@ -5357,6 +6584,104 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
5357
6584
  return;
5358
6585
  }
5359
6586
  }
6587
+ // package.json
6588
+ var package_default = {
6589
+ name: "@jmanuelcorral/openteam",
6590
+ version: "0.2.2",
6591
+ packageManager: "bun@1.3.14",
6592
+ description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
6593
+ license: "MIT",
6594
+ author: "Jose Manuel Corral",
6595
+ repository: {
6596
+ type: "git",
6597
+ url: "git+https://github.com/jmanuelcorral/openteam.git"
6598
+ },
6599
+ homepage: "https://github.com/jmanuelcorral/openteam#readme",
6600
+ bugs: {
6601
+ url: "https://github.com/jmanuelcorral/openteam/issues"
6602
+ },
6603
+ keywords: [
6604
+ "opencode",
6605
+ "opencode-plugin",
6606
+ "llm",
6607
+ "routing",
6608
+ "local-llm",
6609
+ "ollama",
6610
+ "lm-studio",
6611
+ "foundry-local",
6612
+ "cost-optimization",
6613
+ "multi-agent"
6614
+ ],
6615
+ engines: {
6616
+ bun: ">=1.3",
6617
+ node: "^22.22.2 || ^24.15.0 || >=26.0.0"
6618
+ },
6619
+ type: "module",
6620
+ main: "./dist/index.js",
6621
+ module: "./dist/index.js",
6622
+ types: "./dist/index.d.ts",
6623
+ bin: {
6624
+ openteam: "./dist/cli.js"
6625
+ },
6626
+ exports: {
6627
+ ".": {
6628
+ types: "./dist/index.d.ts",
6629
+ import: "./dist/index.js"
6630
+ },
6631
+ "./package.json": "./package.json"
6632
+ },
6633
+ files: [
6634
+ "dist",
6635
+ "README.md",
6636
+ "LICENSE",
6637
+ "AGENTS.md",
6638
+ ".opencode/openteam.example.json",
6639
+ ".opencode/command/openteam.md"
6640
+ ],
6641
+ publishConfig: {
6642
+ access: "public",
6643
+ registry: "https://registry.npmjs.org/"
6644
+ },
6645
+ trustedDependencies: [],
6646
+ sideEffects: false,
6647
+ scripts: {
6648
+ prebuild: "bun run clean",
6649
+ build: "bun run build:js && bun run build:cli && bun run build:types",
6650
+ "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",
6651
+ "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',
6652
+ "build:types": "tsc -p tsconfig.build.json",
6653
+ clean: `node -e "require('node:fs').rmSync('dist', { recursive: true, force: true })"`,
6654
+ test: "bun test",
6655
+ "test:cov": "bun test --coverage",
6656
+ "certify:shadow": "bun test tests/certification/graph-shadow.test.ts",
6657
+ "certify:release": "bun test tests/certification/graph-release.test.ts",
6658
+ "coverage:check": "node scripts/check-coverage.mjs",
6659
+ typecheck: "tsc --noEmit",
6660
+ lint: "biome check .",
6661
+ "format:check": "biome format .",
6662
+ "docs:install": "cd docs && bun install --frozen-lockfile --ignore-scripts",
6663
+ "docs:dev": "cd docs && bun run docs:dev",
6664
+ "docs:build": "cd docs && bun run docs:build",
6665
+ "docs:preview": "cd docs && bun run docs:preview",
6666
+ prepublishOnly: "bun run build",
6667
+ "link:local": "bun run build && npm link",
6668
+ "hooks:install": "git config core.hooksPath .githooks"
6669
+ },
6670
+ dependencies: {
6671
+ "@clack/prompts": "1.7.0",
6672
+ "@opencode-ai/plugin": "1.18.18",
6673
+ "@opencode-ai/sdk": "1.18.18",
6674
+ zod: "4.4.3"
6675
+ },
6676
+ devDependencies: {
6677
+ "@biomejs/biome": "2.5.9",
6678
+ "@types/bun": "1.3.14",
6679
+ typescript: "7.0.2"
6680
+ }
6681
+ };
6682
+
6683
+ // src/version.ts
6684
+ var PACKAGE_VERSION = package_default.version;
5360
6685
 
5361
6686
  // src/index.ts
5362
6687
  function createShellExec($) {
@@ -5531,7 +6856,8 @@ function createCliDeps(config, registry, telemetryPath, sessionsDir = DEFAULT_SE
5531
6856
  telemetryPath,
5532
6857
  opencodeConfigPath: OPENCODE_CONFIG_PATH,
5533
6858
  orchestratorAgentPath: ORCHESTRATOR_AGENT_PATH,
5534
- agentDir: dirname2(ORCHESTRATOR_AGENT_PATH)
6859
+ agentDir: dirname2(ORCHESTRATOR_AGENT_PATH),
6860
+ version: PACKAGE_VERSION
5535
6861
  };
5536
6862
  }
5537
6863
  var server = async (ctx, rawOptions) => {
@@ -5621,11 +6947,26 @@ var server = async (ctx, rawOptions) => {
5621
6947
  shadow: shadowDeps,
5622
6948
  tracker: sessionTracker
5623
6949
  });
6950
+ const orchestrateClient = adaptSdkClient(ctx.client);
6951
+ const effectiveGraphMode = graphConfig.effectiveMode;
6952
+ const journalRoot = config.graph?.journalRoot ?? DEFAULT_GRAPH_JOURNAL_ROOT;
6953
+ const journal = effectiveGraphMode !== "off" ? createFsGraphJournal(journalRoot) : undefined;
6954
+ const orchestrateTool = createOrchestrateTool({
6955
+ client: orchestrateClient,
6956
+ sink,
6957
+ config,
6958
+ now,
6959
+ newDecisionID: () => crypto.randomUUID(),
6960
+ graphMode: effectiveGraphMode,
6961
+ journal,
6962
+ observeLegacyExecution: graphSurface.observeLegacyExecution
6963
+ });
5624
6964
  return {
5625
6965
  ...hooks,
5626
6966
  tool: {
5627
6967
  openteam: createCommandTool(cliDeps),
5628
- "openteam-graph": graphSurface.definition
6968
+ "openteam-graph": graphSurface.definition,
6969
+ "openteam-orchestrate": orchestrateTool
5629
6970
  },
5630
6971
  "tool.execute.before": async (input) => {
5631
6972
  toolcalls.before(input);
@@ -5670,5 +7011,6 @@ export {
5670
7011
  src_default as default,
5671
7012
  createEventSink,
5672
7013
  createCliDeps,
5673
- buildGateInput
7014
+ buildGateInput,
7015
+ PACKAGE_VERSION
5674
7016
  };