@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/README.es.md +1 -1
- package/README.md +1 -1
- package/dist/cli/consoleServe.d.ts +9 -0
- package/dist/cli/consoleServe.d.ts.map +1 -1
- package/dist/cli/graphViewProvider.d.ts +30 -0
- package/dist/cli/graphViewProvider.d.ts.map +1 -0
- package/dist/cli.js +1271 -240
- package/dist/commands/dispatch.d.ts +1 -0
- package/dist/commands/dispatch.d.ts.map +1 -1
- package/dist/commands/orchestratorAgent.d.ts.map +1 -1
- package/dist/config/schema.d.ts +5 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/context/redaction.d.ts +8 -0
- package/dist/context/redaction.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1351 -9
- package/dist/orchestrator/coordinator.d.ts +37 -2
- package/dist/orchestrator/coordinator.d.ts.map +1 -1
- package/dist/plugin/orchestrateTool.d.ts +197 -0
- package/dist/plugin/orchestrateTool.d.ts.map +1 -0
- package/dist/telemetry/events.d.ts +37 -0
- package/dist/telemetry/events.d.ts.map +1 -1
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/storage/graph/migrateLegacyBriefs.d.ts +0 -32
- package/dist/storage/graph/migrateLegacyBriefs.d.ts.map +0 -1
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
|
-
"
|
|
50
|
-
"
|
|
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
|
|
173
|
-
"
|
|
208
|
+
"- Delega el trabajo del roster con `openteam-orchestrate`; usa `task` solo",
|
|
209
|
+
" para subagentes ad-hoc que no tengan rol en el roster. Tú 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 ===
|
|
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
|
};
|