@jmanuelcorral/openteam 0.18.0 → 0.19.0
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/.opencode/openteam.example.json +23 -14
- package/AGENTS.md +16 -2
- package/README.es.md +291 -208
- package/README.md +292 -138
- package/dist/capabilities/classify.d.ts.map +1 -1
- package/dist/capabilities/normalize.d.ts.map +1 -1
- package/dist/certificates/graph-release-certificate.json +2 -2
- package/dist/certificates/graph-shadow-certificate.json +2 -2
- package/dist/cli.js +1354 -594
- package/dist/commands/agents.d.ts +16 -4
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/dispatch.d.ts.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/local.d.ts +4 -5
- package/dist/commands/local.d.ts.map +1 -1
- package/dist/commands/report.d.ts +6 -6
- package/dist/commands/report.d.ts.map +1 -1
- package/dist/commands/rolesInit.d.ts +10 -12
- package/dist/commands/rolesInit.d.ts.map +1 -1
- package/dist/commands/setup.d.ts +5 -13
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/yolo.d.ts +2 -4
- package/dist/commands/yolo.d.ts.map +1 -1
- package/dist/config/errors.d.ts.map +1 -1
- package/dist/config/executionPolicyDomains.d.ts +18 -0
- package/dist/config/executionPolicyDomains.d.ts.map +1 -0
- package/dist/config/migrations.d.ts +9 -2
- package/dist/config/migrations.d.ts.map +1 -1
- package/dist/config/persist.d.ts.map +1 -1
- package/dist/config/runtime.d.ts +2 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/schema.d.ts +97 -12
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/console/configView.d.ts +2 -7
- package/dist/console/configView.d.ts.map +1 -1
- package/dist/console/render.d.ts.map +1 -1
- package/dist/console/types.d.ts +2 -2
- package/dist/console/types.d.ts.map +1 -1
- package/dist/context/schema.d.ts +13 -7
- package/dist/context/schema.d.ts.map +1 -1
- package/dist/graph/certificate.d.ts.map +1 -1
- package/dist/graph/langgraph/adapter.d.ts +7 -8
- package/dist/graph/langgraph/adapter.d.ts.map +1 -1
- package/dist/graph/langgraph/certification.d.ts +5 -0
- package/dist/graph/langgraph/certification.d.ts.map +1 -1
- package/dist/graph/langgraph/checkpointReplay.d.ts.map +1 -1
- package/dist/graph/langgraph/engine.d.ts +2 -2
- package/dist/graph/langgraph/engine.d.ts.map +1 -1
- package/dist/graph/langgraph/types.d.ts +11 -6
- package/dist/graph/langgraph/types.d.ts.map +1 -1
- package/dist/graph/privacyPolicy.d.ts +6 -30
- package/dist/graph/privacyPolicy.d.ts.map +1 -1
- package/dist/index.d.ts +1 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3164 -1102
- package/dist/mcp/teamMemory.d.ts +2 -2
- package/dist/mcp/teamMemory.d.ts.map +1 -1
- package/dist/messages/commands.d.ts +22 -35
- package/dist/messages/commands.d.ts.map +1 -1
- package/dist/messages/executionSetup.d.ts +116 -0
- package/dist/messages/executionSetup.d.ts.map +1 -0
- package/dist/messages/index.d.ts +1 -0
- package/dist/messages/index.d.ts.map +1 -1
- package/dist/messages/memoryTool.d.ts +17 -0
- package/dist/messages/memoryTool.d.ts.map +1 -0
- package/dist/messages/orchestration.d.ts +16 -0
- package/dist/messages/orchestration.d.ts.map +1 -0
- package/dist/messages/routing.d.ts +9 -0
- package/dist/messages/routing.d.ts.map +1 -0
- package/dist/opencodeArtifacts/orchestratorAgent.d.ts +3 -5
- package/dist/opencodeArtifacts/orchestratorAgent.d.ts.map +1 -1
- package/dist/orchestrator/coordinator.d.ts +28 -9
- package/dist/orchestrator/coordinator.d.ts.map +1 -1
- package/dist/orchestrator/graphEffects.d.ts +10 -11
- package/dist/orchestrator/graphEffects.d.ts.map +1 -1
- package/dist/orchestrator/outputContract.d.ts +25 -0
- package/dist/orchestrator/outputContract.d.ts.map +1 -1
- package/dist/orchestrator/roles.d.ts +25 -2
- package/dist/orchestrator/roles.d.ts.map +1 -1
- package/dist/orchestrator/runtimeSelection.d.ts.map +1 -1
- package/dist/orchestrator/subsessions.d.ts +49 -1
- package/dist/orchestrator/subsessions.d.ts.map +1 -1
- package/dist/plugin/availability.d.ts +15 -2
- package/dist/plugin/availability.d.ts.map +1 -1
- package/dist/plugin/diagnostics.d.ts +1 -0
- package/dist/plugin/diagnostics.d.ts.map +1 -1
- package/dist/plugin/hooks.d.ts +21 -2
- package/dist/plugin/hooks.d.ts.map +1 -1
- package/dist/plugin/memoryTool.d.ts +3 -3
- package/dist/plugin/memoryTool.d.ts.map +1 -1
- package/dist/plugin/modelOwnership.d.ts +15 -0
- package/dist/plugin/modelOwnership.d.ts.map +1 -0
- package/dist/plugin/orchestrateTool.d.ts +68 -3
- package/dist/plugin/orchestrateTool.d.ts.map +1 -1
- package/dist/router/chooseModel.d.ts.map +1 -1
- package/dist/router/cost.d.ts +1 -1
- package/dist/router/cost.d.ts.map +1 -1
- package/dist/router/executionPolicy.d.ts +44 -0
- package/dist/router/executionPolicy.d.ts.map +1 -0
- package/dist/router/frontierBaseline.d.ts.map +1 -1
- package/dist/router/types.d.ts +10 -4
- package/dist/router/types.d.ts.map +1 -1
- package/dist/telemetry/aggregate.d.ts +4 -4
- package/dist/telemetry/aggregate.d.ts.map +1 -1
- package/dist/telemetry/diagnostics.d.ts +1 -1
- package/dist/telemetry/diagnostics.d.ts.map +1 -1
- package/dist/telemetry/events.d.ts +20 -6
- package/dist/telemetry/events.d.ts.map +1 -1
- package/dist/telemetry/otel.d.ts.map +1 -1
- package/dist/telemetry/types.d.ts +3 -3
- package/dist/web/configApi.d.ts +3 -1
- package/dist/web/configApi.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -47,6 +47,18 @@ import { z as z2 } from "zod";
|
|
|
47
47
|
|
|
48
48
|
// src/orchestrator/roles.ts
|
|
49
49
|
import { z } from "zod";
|
|
50
|
+
|
|
51
|
+
// src/messages/routing.ts
|
|
52
|
+
var routingMessages = {
|
|
53
|
+
conflictingLegacyModes: "router.localOnly and router.frontierOnly cannot both be true",
|
|
54
|
+
conflictingRouterModes: (executionMode, legacyMode) => `router.executionMode "${executionMode}" conflicts with legacy router mode "${legacyMode}"`,
|
|
55
|
+
agentModeBroadensGlobal: (agentMode, routerMode) => `agent execution mode "${agentMode}" cannot broaden global router execution mode "${routerMode}"`,
|
|
56
|
+
requiredLocalModeConflict: "requiresLocalRuntime=true conflicts with a non-local agent executionMode",
|
|
57
|
+
privacyModeDeprecated: 'privacyMode is deprecated and ignored; it no longer protects prompt content from frontier egress. Set router.executionMode to "local" to prohibit frontier routing.',
|
|
58
|
+
unknownCost: "unknown"
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// src/orchestrator/roles.ts
|
|
50
62
|
var modelSelectionSchema = z.object({
|
|
51
63
|
providerID: z.string().min(1),
|
|
52
64
|
modelID: z.string().min(1)
|
|
@@ -70,8 +82,19 @@ var AgentRoleProfileSchema = z.object({
|
|
|
70
82
|
requiresLocalRuntime: z.boolean().optional(),
|
|
71
83
|
localRuntimes: z.array(z.string().min(1)).optional(),
|
|
72
84
|
localModels: z.record(z.string().min(1), z.string().min(1)).optional(),
|
|
73
|
-
localModelClass: z.enum(["thinking", "coder"]).optional()
|
|
74
|
-
|
|
85
|
+
localModelClass: z.enum(["thinking", "coder"]).optional(),
|
|
86
|
+
executionMode: z.enum(["local", "frontier", "mixed"]).optional(),
|
|
87
|
+
model: z.union([modelSelectionSchema, z.literal("auto")]).optional(),
|
|
88
|
+
fallbacks: z.array(modelSelectionSchema).optional()
|
|
89
|
+
}).strict().superRefine((profile, ctx) => {
|
|
90
|
+
if (profile.requiresLocalRuntime === true && profile.executionMode !== undefined && profile.executionMode !== "local") {
|
|
91
|
+
ctx.addIssue({
|
|
92
|
+
code: "custom",
|
|
93
|
+
path: ["executionMode"],
|
|
94
|
+
message: routingMessages.requiredLocalModeConflict
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
});
|
|
75
98
|
var TEAM_ROLES = {
|
|
76
99
|
architect: {
|
|
77
100
|
roleID: "architect",
|
|
@@ -125,8 +148,7 @@ var TEAM_ROLES = {
|
|
|
125
148
|
minCodeQualityTier: 1,
|
|
126
149
|
needsToolCallingByDefault: false,
|
|
127
150
|
preferredFrontierModels: [],
|
|
128
|
-
localFirst: true
|
|
129
|
-
requiresLocalRuntime: true
|
|
151
|
+
localFirst: true
|
|
130
152
|
},
|
|
131
153
|
ralph: {
|
|
132
154
|
roleID: "ralph",
|
|
@@ -136,8 +158,7 @@ var TEAM_ROLES = {
|
|
|
136
158
|
minCodeQualityTier: 2,
|
|
137
159
|
needsToolCallingByDefault: true,
|
|
138
160
|
preferredFrontierModels: [],
|
|
139
|
-
localFirst: true
|
|
140
|
-
requiresLocalRuntime: true
|
|
161
|
+
localFirst: true
|
|
141
162
|
},
|
|
142
163
|
guardian: {
|
|
143
164
|
roleID: "guardian",
|
|
@@ -148,7 +169,6 @@ var TEAM_ROLES = {
|
|
|
148
169
|
needsToolCallingByDefault: false,
|
|
149
170
|
preferredFrontierModels: [],
|
|
150
171
|
localFirst: true,
|
|
151
|
-
requiresLocalRuntime: true,
|
|
152
172
|
localModelClass: "thinking"
|
|
153
173
|
},
|
|
154
174
|
tester: {
|
|
@@ -216,9 +236,112 @@ function synthesiseFallbackProfile(roleID) {
|
|
|
216
236
|
localFirst: true
|
|
217
237
|
};
|
|
218
238
|
}
|
|
239
|
+
function concreteFrontierPreferences(role) {
|
|
240
|
+
return role.preferredFrontierModels.filter((model) => model !== "auto");
|
|
241
|
+
}
|
|
242
|
+
function agentExecutionPolicyForRole(role) {
|
|
243
|
+
const policy = {};
|
|
244
|
+
if (role.executionMode !== undefined) {
|
|
245
|
+
policy.executionMode = role.executionMode;
|
|
246
|
+
} else if (role.requiresLocalRuntime === true) {
|
|
247
|
+
policy.executionMode = "local";
|
|
248
|
+
}
|
|
249
|
+
if (role.model !== undefined) {
|
|
250
|
+
policy.model = role.model;
|
|
251
|
+
}
|
|
252
|
+
if (role.fallbacks !== undefined) {
|
|
253
|
+
policy.fallbacks = [...role.fallbacks];
|
|
254
|
+
}
|
|
255
|
+
if (policy.model === undefined && policy.executionMode === undefined && policy.fallbacks === undefined) {
|
|
256
|
+
const preferences = concreteFrontierPreferences(role);
|
|
257
|
+
const primary = preferences[0];
|
|
258
|
+
if (primary === undefined) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
policy.executionMode = "frontier";
|
|
262
|
+
policy.model = primary;
|
|
263
|
+
if (preferences.length > 1) {
|
|
264
|
+
policy.fallbacks = preferences.slice(1);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return policy;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/router/executionPolicy.ts
|
|
271
|
+
class ExecutionPolicyError extends Error {
|
|
272
|
+
code;
|
|
273
|
+
constructor(code, message) {
|
|
274
|
+
super(message);
|
|
275
|
+
this.name = "ExecutionPolicyError";
|
|
276
|
+
this.code = code;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function legacyExecutionMode(localOnly, frontierOnly) {
|
|
280
|
+
if (localOnly === true) {
|
|
281
|
+
return "local";
|
|
282
|
+
}
|
|
283
|
+
if (frontierOnly === true) {
|
|
284
|
+
return "frontier";
|
|
285
|
+
}
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
function resolveExecutionModeResult(input) {
|
|
289
|
+
if (input.legacyLocalOnly === true && input.legacyFrontierOnly === true) {
|
|
290
|
+
return {
|
|
291
|
+
ok: false,
|
|
292
|
+
error: new ExecutionPolicyError("legacy-modes-conflict", routingMessages.conflictingLegacyModes)
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const legacyMode = legacyExecutionMode(input.legacyLocalOnly, input.legacyFrontierOnly);
|
|
296
|
+
if (input.routerMode !== undefined && legacyMode !== undefined && input.routerMode !== legacyMode) {
|
|
297
|
+
return {
|
|
298
|
+
ok: false,
|
|
299
|
+
error: new ExecutionPolicyError("router-mode-conflict", routingMessages.conflictingRouterModes(input.routerMode, legacyMode))
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
const routerMode = input.routerMode ?? legacyMode ?? "mixed";
|
|
303
|
+
if (input.agentMode !== undefined && routerMode !== "mixed" && input.agentMode !== routerMode) {
|
|
304
|
+
return {
|
|
305
|
+
ok: false,
|
|
306
|
+
error: new ExecutionPolicyError("agent-mode-broadens-global", routingMessages.agentModeBroadensGlobal(input.agentMode, routerMode))
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
return { ok: true, mode: input.agentMode ?? routerMode };
|
|
310
|
+
}
|
|
311
|
+
function resolveExecutionMode(input) {
|
|
312
|
+
const resolution = resolveExecutionModeResult(input);
|
|
313
|
+
if (!resolution.ok) {
|
|
314
|
+
throw resolution.error;
|
|
315
|
+
}
|
|
316
|
+
return resolution.mode;
|
|
317
|
+
}
|
|
318
|
+
function resolveAgentExecutionPolicyResult(routerMode, override) {
|
|
319
|
+
const executionModeResolution = resolveExecutionModeResult({
|
|
320
|
+
agentMode: override?.executionMode,
|
|
321
|
+
routerMode
|
|
322
|
+
});
|
|
323
|
+
if (!executionModeResolution.ok) {
|
|
324
|
+
return executionModeResolution;
|
|
325
|
+
}
|
|
326
|
+
const policy = {
|
|
327
|
+
executionMode: executionModeResolution.mode,
|
|
328
|
+
model: override?.model ?? "auto"
|
|
329
|
+
};
|
|
330
|
+
if (override?.fallbacks !== undefined) {
|
|
331
|
+
policy.fallbacks = [...override.fallbacks];
|
|
332
|
+
}
|
|
333
|
+
return { ok: true, policy };
|
|
334
|
+
}
|
|
335
|
+
function resolveAgentExecutionPolicy(routerMode, override) {
|
|
336
|
+
const resolution = resolveAgentExecutionPolicyResult(routerMode, override);
|
|
337
|
+
if (!resolution.ok) {
|
|
338
|
+
throw resolution.error;
|
|
339
|
+
}
|
|
340
|
+
return resolution.policy;
|
|
341
|
+
}
|
|
219
342
|
|
|
220
343
|
// src/config/migrations.ts
|
|
221
|
-
var CURRENT_CONFIG_VERSION =
|
|
344
|
+
var CURRENT_CONFIG_VERSION = 2;
|
|
222
345
|
|
|
223
346
|
class ConfigMigrationError extends Error {
|
|
224
347
|
code;
|
|
@@ -230,22 +353,69 @@ class ConfigMigrationError extends Error {
|
|
|
230
353
|
this.detail = detail;
|
|
231
354
|
}
|
|
232
355
|
}
|
|
356
|
+
function isRecord(value) {
|
|
357
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
358
|
+
}
|
|
359
|
+
function detectConfigDeprecations(raw) {
|
|
360
|
+
if (!isRecord(raw) || raw.privacyMode === undefined) {
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
return [
|
|
364
|
+
{
|
|
365
|
+
code: "privacy-mode-ignored",
|
|
366
|
+
path: "privacyMode",
|
|
367
|
+
message: routingMessages.privacyModeDeprecated
|
|
368
|
+
}
|
|
369
|
+
];
|
|
370
|
+
}
|
|
371
|
+
function migrateRouter(raw, fromLegacyVersion) {
|
|
372
|
+
if (!isRecord(raw)) {
|
|
373
|
+
return raw;
|
|
374
|
+
}
|
|
375
|
+
const validExecutionMode = raw.executionMode === "local" || raw.executionMode === "frontier" || raw.executionMode === "mixed" ? raw.executionMode : undefined;
|
|
376
|
+
let executionMode;
|
|
377
|
+
try {
|
|
378
|
+
executionMode = resolveExecutionMode({
|
|
379
|
+
routerMode: validExecutionMode,
|
|
380
|
+
legacyLocalOnly: typeof raw.localOnly === "boolean" ? raw.localOnly : undefined,
|
|
381
|
+
legacyFrontierOnly: typeof raw.frontierOnly === "boolean" ? raw.frontierOnly : undefined
|
|
382
|
+
});
|
|
383
|
+
} catch (error) {
|
|
384
|
+
if (!(error instanceof ExecutionPolicyError)) {
|
|
385
|
+
throw error;
|
|
386
|
+
}
|
|
387
|
+
throw new ConfigMigrationError("conflicting-execution-mode", error.message);
|
|
388
|
+
}
|
|
389
|
+
const { localOnly: _localOnly, frontierOnly: _frontierOnly, ...router } = raw;
|
|
390
|
+
if (raw.executionMode !== undefined && validExecutionMode === undefined) {
|
|
391
|
+
return raw;
|
|
392
|
+
}
|
|
393
|
+
if (fromLegacyVersion || raw.localOnly !== undefined || raw.frontierOnly !== undefined) {
|
|
394
|
+
return { ...router, executionMode };
|
|
395
|
+
}
|
|
396
|
+
return raw;
|
|
397
|
+
}
|
|
233
398
|
function migrateConfig(raw) {
|
|
234
|
-
if (
|
|
399
|
+
if (!isRecord(raw)) {
|
|
235
400
|
return raw;
|
|
236
401
|
}
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
if (
|
|
240
|
-
|
|
402
|
+
const version = raw.configVersion;
|
|
403
|
+
const sourceVersion = version ?? 1;
|
|
404
|
+
if (typeof sourceVersion !== "number" || !Number.isInteger(sourceVersion) || sourceVersion < 1) {
|
|
405
|
+
throw new ConfigMigrationError("unknown-version", String(sourceVersion));
|
|
241
406
|
}
|
|
242
|
-
if (
|
|
243
|
-
throw new ConfigMigrationError("
|
|
407
|
+
if (sourceVersion > CURRENT_CONFIG_VERSION) {
|
|
408
|
+
throw new ConfigMigrationError("unsupported-version", String(sourceVersion));
|
|
244
409
|
}
|
|
245
|
-
|
|
246
|
-
|
|
410
|
+
const migratedRouter = migrateRouter(raw.router, sourceVersion < 2);
|
|
411
|
+
if (sourceVersion === CURRENT_CONFIG_VERSION && migratedRouter === raw.router) {
|
|
412
|
+
return raw;
|
|
247
413
|
}
|
|
248
|
-
return
|
|
414
|
+
return {
|
|
415
|
+
...raw,
|
|
416
|
+
configVersion: CURRENT_CONFIG_VERSION,
|
|
417
|
+
...migratedRouter === undefined ? {} : { router: migratedRouter }
|
|
418
|
+
};
|
|
249
419
|
}
|
|
250
420
|
|
|
251
421
|
// src/config/schema.ts
|
|
@@ -254,6 +424,7 @@ var ModelRefSchema = z2.object({
|
|
|
254
424
|
modelID: z2.string().min(1)
|
|
255
425
|
}).strict();
|
|
256
426
|
var RouterModeSchema = z2.enum(["economy", "balanced", "quality"]);
|
|
427
|
+
var ExecutionModeSchema = z2.enum(["local", "frontier", "mixed"]);
|
|
257
428
|
var PrivacyModeSchema = z2.enum([
|
|
258
429
|
"forceLocalOnSensitive",
|
|
259
430
|
"consentBeforeFrontier",
|
|
@@ -332,7 +503,12 @@ var OrchestratorRolesSchema = z2.record(z2.string().min(1), z2.unknown()).defaul
|
|
|
332
503
|
});
|
|
333
504
|
continue;
|
|
334
505
|
}
|
|
335
|
-
const
|
|
506
|
+
const base = getRoleProfile(roleID) ?? synthesiseFallbackProfile(roleID);
|
|
507
|
+
const parsed = AgentRoleProfileSchema.safeParse({
|
|
508
|
+
...base,
|
|
509
|
+
...value,
|
|
510
|
+
roleID
|
|
511
|
+
});
|
|
336
512
|
if (!parsed.success) {
|
|
337
513
|
for (const issue of parsed.error.issues) {
|
|
338
514
|
ctx.addIssue({
|
|
@@ -366,17 +542,47 @@ var OrchestratorRolesSchema = z2.record(z2.string().min(1), z2.unknown()).defaul
|
|
|
366
542
|
if (parsed.data.localModelClass !== undefined) {
|
|
367
543
|
profile.localModelClass = parsed.data.localModelClass;
|
|
368
544
|
}
|
|
545
|
+
if (parsed.data.executionMode !== undefined) {
|
|
546
|
+
profile.executionMode = parsed.data.executionMode;
|
|
547
|
+
}
|
|
548
|
+
if (parsed.data.model !== undefined) {
|
|
549
|
+
profile.model = parsed.data.model;
|
|
550
|
+
}
|
|
551
|
+
if (parsed.data.fallbacks !== undefined) {
|
|
552
|
+
profile.fallbacks = parsed.data.fallbacks;
|
|
553
|
+
}
|
|
554
|
+
if (profile.requiresLocalRuntime === true && profile.executionMode === undefined) {
|
|
555
|
+
profile.executionMode = "local";
|
|
556
|
+
}
|
|
557
|
+
const concretePreferences = profile.preferredFrontierModels.filter((model) => model !== "auto");
|
|
558
|
+
if (profile.requiresLocalRuntime !== true && profile.executionMode === undefined && profile.model === undefined && concretePreferences.length > 0) {
|
|
559
|
+
const primary = concretePreferences[0];
|
|
560
|
+
if (primary !== undefined) {
|
|
561
|
+
profile.executionMode = "frontier";
|
|
562
|
+
profile.model = primary;
|
|
563
|
+
if (profile.fallbacks === undefined && concretePreferences.length > 1) {
|
|
564
|
+
profile.fallbacks = concretePreferences.slice(1);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
369
568
|
parsedRoles[roleID] = profile;
|
|
370
569
|
}
|
|
371
570
|
return parsedRoles;
|
|
372
571
|
});
|
|
572
|
+
var AgentExecutionPolicySchema = z2.object({
|
|
573
|
+
executionMode: ExecutionModeSchema.optional(),
|
|
574
|
+
model: z2.union([ModelRefSchema, z2.literal("auto")]).optional(),
|
|
575
|
+
fallbacks: z2.array(ModelRefSchema).optional()
|
|
576
|
+
}).strict();
|
|
373
577
|
var OrchestratorConfigSchema = z2.object({
|
|
374
578
|
roles: OrchestratorRolesSchema,
|
|
375
|
-
|
|
579
|
+
primary: AgentExecutionPolicySchema.optional(),
|
|
580
|
+
maxRetries: z2.number().int().min(0).optional(),
|
|
581
|
+
maxModelsPerNode: z2.number().int().positive().optional()
|
|
376
582
|
}).strict();
|
|
377
583
|
var defaultFrontierModel = {
|
|
378
|
-
providerID: "
|
|
379
|
-
modelID: "
|
|
584
|
+
providerID: "google",
|
|
585
|
+
modelID: "gemini-3.5-flash"
|
|
380
586
|
};
|
|
381
587
|
var DEFAULT_LOCAL_MAX_CONCURRENCY = 4;
|
|
382
588
|
var LocalRuntimeKindSchema = z2.enum([
|
|
@@ -469,26 +675,34 @@ var OpenTeamConfigObjectSchema = z2.object({
|
|
|
469
675
|
}),
|
|
470
676
|
router: z2.object({
|
|
471
677
|
mode: RouterModeSchema.default("balanced"),
|
|
678
|
+
executionMode: ExecutionModeSchema.optional(),
|
|
472
679
|
localDefault: ModelRefSchema.nullable().default(null),
|
|
473
680
|
trivialPromptMaxChars: z2.number().int().positive().default(280),
|
|
474
681
|
frontierPromptMinChars: z2.number().int().positive().default(2000),
|
|
475
|
-
frontierOnly: z2.boolean().
|
|
476
|
-
localOnly: z2.boolean().
|
|
682
|
+
frontierOnly: z2.boolean().optional(),
|
|
683
|
+
localOnly: z2.boolean().optional()
|
|
477
684
|
}).strict().superRefine((router, ctx) => {
|
|
478
|
-
|
|
685
|
+
try {
|
|
686
|
+
resolveExecutionMode({
|
|
687
|
+
routerMode: router.executionMode,
|
|
688
|
+
legacyLocalOnly: router.localOnly,
|
|
689
|
+
legacyFrontierOnly: router.frontierOnly
|
|
690
|
+
});
|
|
691
|
+
} catch (error) {
|
|
692
|
+
if (!(error instanceof ExecutionPolicyError)) {
|
|
693
|
+
throw error;
|
|
694
|
+
}
|
|
479
695
|
ctx.addIssue({
|
|
480
696
|
code: "custom",
|
|
481
|
-
path: ["localOnly"],
|
|
482
|
-
message:
|
|
697
|
+
path: error.code === "legacy-modes-conflict" ? ["localOnly"] : ["executionMode"],
|
|
698
|
+
message: error.message
|
|
483
699
|
});
|
|
484
700
|
}
|
|
485
701
|
}).default({
|
|
486
702
|
mode: "balanced",
|
|
487
703
|
localDefault: null,
|
|
488
704
|
trivialPromptMaxChars: 280,
|
|
489
|
-
frontierPromptMinChars: 2000
|
|
490
|
-
frontierOnly: false,
|
|
491
|
-
localOnly: false
|
|
705
|
+
frontierPromptMinChars: 2000
|
|
492
706
|
}),
|
|
493
707
|
local: z2.object({
|
|
494
708
|
runtimes: z2.array(LocalRuntimeSchema).default([]).superRefine((runtimes, ctx) => {
|
|
@@ -517,7 +731,7 @@ var OpenTeamConfigObjectSchema = z2.object({
|
|
|
517
731
|
frontierTokensPerSession: z2.number().int().positive().optional(),
|
|
518
732
|
hardStopOnBudgetExhaustion: z2.boolean().default(false)
|
|
519
733
|
}).strict().default({ hardStopOnBudgetExhaustion: false }),
|
|
520
|
-
privacyMode: PrivacyModeSchema.
|
|
734
|
+
privacyMode: PrivacyModeSchema.optional(),
|
|
521
735
|
console: ConsoleConfigSchema,
|
|
522
736
|
storage: StorageConfigSchema,
|
|
523
737
|
memory: MemoryConfigSchema.optional(),
|
|
@@ -529,7 +743,48 @@ var OpenTeamConfigObjectSchema = z2.object({
|
|
|
529
743
|
worktrees: { enabled: false }
|
|
530
744
|
}),
|
|
531
745
|
orchestrator: OrchestratorConfigSchema.optional()
|
|
532
|
-
}).strict()
|
|
746
|
+
}).strict().superRefine((config, ctx) => {
|
|
747
|
+
let routerMode;
|
|
748
|
+
try {
|
|
749
|
+
routerMode = resolveExecutionMode({
|
|
750
|
+
routerMode: config.router.executionMode,
|
|
751
|
+
legacyLocalOnly: config.router.localOnly,
|
|
752
|
+
legacyFrontierOnly: config.router.frontierOnly
|
|
753
|
+
});
|
|
754
|
+
} catch (error) {
|
|
755
|
+
if (!(error instanceof ExecutionPolicyError)) {
|
|
756
|
+
throw error;
|
|
757
|
+
}
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const policies = [
|
|
761
|
+
{
|
|
762
|
+
path: ["orchestrator", "primary"],
|
|
763
|
+
policy: config.orchestrator?.primary
|
|
764
|
+
},
|
|
765
|
+
...Object.entries(config.orchestrator?.roles ?? {}).map(([roleID, role]) => ({
|
|
766
|
+
path: ["orchestrator", "roles", roleID],
|
|
767
|
+
policy: agentExecutionPolicyForRole(role)
|
|
768
|
+
}))
|
|
769
|
+
];
|
|
770
|
+
for (const { path, policy } of policies) {
|
|
771
|
+
if (policy === undefined) {
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
try {
|
|
775
|
+
resolveAgentExecutionPolicy(routerMode, policy);
|
|
776
|
+
} catch (error) {
|
|
777
|
+
if (!(error instanceof ExecutionPolicyError)) {
|
|
778
|
+
throw error;
|
|
779
|
+
}
|
|
780
|
+
ctx.addIssue({
|
|
781
|
+
code: "custom",
|
|
782
|
+
path: [...path, "executionMode"],
|
|
783
|
+
message: error.message
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
});
|
|
533
788
|
var OpenTeamConfigSchema = OpenTeamConfigObjectSchema;
|
|
534
789
|
function resolveMemorySemantic(config) {
|
|
535
790
|
return config.memory?.semantic ?? MemorySemanticSchema.parse(undefined);
|
|
@@ -619,6 +874,7 @@ var DIAGNOSTIC_CODES = [
|
|
|
619
874
|
"graph-gate-denied",
|
|
620
875
|
"memory-extraction-warning",
|
|
621
876
|
"memory-runtime-warning",
|
|
877
|
+
"node-budget-exhausted",
|
|
622
878
|
"opencode-server-url",
|
|
623
879
|
"opencode-version-read-failed",
|
|
624
880
|
"privacy-inert",
|
|
@@ -641,12 +897,14 @@ function diagnosticDescription(code) {
|
|
|
641
897
|
return "Semantic memory extraction reported a non-fatal local-runtime warning; no raw conversation content was recorded.";
|
|
642
898
|
case "memory-runtime-warning":
|
|
643
899
|
return "Semantic memory runtime setup reported a non-fatal warning; memory recall may be incomplete.";
|
|
900
|
+
case "node-budget-exhausted":
|
|
901
|
+
return "A graph node consumed its entire per-node pass budget without ever reporting a substantive done; the node's iteration stopped at the bound and the role produced no substantive report across its passes. Inspect the role's report artifact; the task likely needs a larger orchestrator.maxRetries or a clearer assignment.";
|
|
644
902
|
case "opencode-server-url":
|
|
645
903
|
return "openteam observed the opencode server URL during plugin initialization.";
|
|
646
904
|
case "opencode-version-read-failed":
|
|
647
905
|
return "openteam could not read the live opencode version; graph gates fail closed when this happens.";
|
|
648
906
|
case "privacy-inert":
|
|
649
|
-
return
|
|
907
|
+
return routingMessages.privacyModeDeprecated;
|
|
650
908
|
case "soak-evidence-rejected":
|
|
651
909
|
return "Graph soak evidence contained rejected observation lines; cutover gate evidence may be incomplete.";
|
|
652
910
|
case "telemetry-warning":
|
|
@@ -673,9 +931,9 @@ var RouteEventSchema = EventBaseSchema.extend({
|
|
|
673
931
|
routeKind: z4.enum(["local", "frontier", "blocked"]),
|
|
674
932
|
selected: ModelRefSchema,
|
|
675
933
|
rationale: z4.string(),
|
|
676
|
-
estimatedCostUSD: z4.number().finite().min(0),
|
|
677
|
-
baselineCostUSD: z4.number().finite().min(0),
|
|
678
|
-
estimatedSavingsUSD: z4.number().finite(),
|
|
934
|
+
estimatedCostUSD: z4.number().finite().min(0).nullable(),
|
|
935
|
+
baselineCostUSD: z4.number().finite().min(0).nullable(),
|
|
936
|
+
estimatedSavingsUSD: z4.number().finite().nullable(),
|
|
679
937
|
budgetAction: z4.string().min(1),
|
|
680
938
|
tokensIn: z4.number().int().min(0).optional(),
|
|
681
939
|
tokensOut: z4.number().int().min(0).optional(),
|
|
@@ -766,7 +1024,9 @@ var DiagnosticCodeSchema = z4.enum(DIAGNOSTIC_CODES);
|
|
|
766
1024
|
var DiagnosticEventSchema = EventBaseSchema.extend({
|
|
767
1025
|
type: z4.literal("diagnostic"),
|
|
768
1026
|
level: DiagnosticLevelSchema,
|
|
769
|
-
code: DiagnosticCodeSchema
|
|
1027
|
+
code: DiagnosticCodeSchema,
|
|
1028
|
+
roleID: z4.string().min(1).optional(),
|
|
1029
|
+
passCount: z4.number().int().min(0).optional()
|
|
770
1030
|
});
|
|
771
1031
|
var OpenTeamEventSchema = z4.discriminatedUnion("type", [
|
|
772
1032
|
RouteEventSchema,
|
|
@@ -887,9 +1147,9 @@ var CostRecordSchema = z5.object({
|
|
|
887
1147
|
routeKind: z5.enum(["local", "frontier", "blocked"]),
|
|
888
1148
|
selected: ModelRefSchema,
|
|
889
1149
|
rationale: z5.string(),
|
|
890
|
-
estimatedCostUSD: z5.number().finite().min(0),
|
|
891
|
-
baselineCostUSD: z5.number().finite().min(0),
|
|
892
|
-
estimatedSavingsUSD: z5.number().finite(),
|
|
1150
|
+
estimatedCostUSD: z5.number().finite().min(0).nullable(),
|
|
1151
|
+
baselineCostUSD: z5.number().finite().min(0).nullable(),
|
|
1152
|
+
estimatedSavingsUSD: z5.number().finite().nullable(),
|
|
893
1153
|
budgetAction: z5.string().min(1),
|
|
894
1154
|
tokensIn: z5.number().int().min(0).optional(),
|
|
895
1155
|
tokensOut: z5.number().int().min(0).optional()
|
|
@@ -1148,7 +1408,11 @@ var FAILURE_CAUSE_RULES = [
|
|
|
1148
1408
|
"bad request",
|
|
1149
1409
|
/(?<!\d)400(?!\d)/,
|
|
1150
1410
|
"unprocessable",
|
|
1151
|
-
"malformed"
|
|
1411
|
+
"malformed",
|
|
1412
|
+
"invalid model",
|
|
1413
|
+
"unknown model",
|
|
1414
|
+
"model not found",
|
|
1415
|
+
"model does not exist"
|
|
1152
1416
|
],
|
|
1153
1417
|
"prompt-validation"
|
|
1154
1418
|
],
|
|
@@ -1594,6 +1858,9 @@ function pushUnique(list, value) {
|
|
|
1594
1858
|
list.push(value);
|
|
1595
1859
|
}
|
|
1596
1860
|
}
|
|
1861
|
+
function addEstimate(current, value) {
|
|
1862
|
+
return current === null || value === null ? null : current + value;
|
|
1863
|
+
}
|
|
1597
1864
|
function applyRoute(session, event) {
|
|
1598
1865
|
session.routeCount += 1;
|
|
1599
1866
|
if (event.routeKind === "local") {
|
|
@@ -1601,8 +1868,8 @@ function applyRoute(session, event) {
|
|
|
1601
1868
|
} else if (event.routeKind === "frontier") {
|
|
1602
1869
|
session.frontierCount += 1;
|
|
1603
1870
|
}
|
|
1604
|
-
session.estimatedCostUSD
|
|
1605
|
-
session.estimatedSavingsUSD
|
|
1871
|
+
session.estimatedCostUSD = addEstimate(session.estimatedCostUSD, event.estimatedCostUSD);
|
|
1872
|
+
session.estimatedSavingsUSD = addEstimate(session.estimatedSavingsUSD, event.estimatedSavingsUSD);
|
|
1606
1873
|
pushUnique(session.models, `${event.selected.providerID}/${event.selected.modelID}`);
|
|
1607
1874
|
pushUnique(session.agents, event.agent);
|
|
1608
1875
|
}
|
|
@@ -1668,8 +1935,8 @@ function accumulateTotals(totals, session) {
|
|
|
1668
1935
|
totals.routeCount += session.routeCount;
|
|
1669
1936
|
totals.localCount += session.localCount;
|
|
1670
1937
|
totals.frontierCount += session.frontierCount;
|
|
1671
|
-
totals.estimatedCostUSD
|
|
1672
|
-
totals.estimatedSavingsUSD
|
|
1938
|
+
totals.estimatedCostUSD = addEstimate(totals.estimatedCostUSD, session.estimatedCostUSD);
|
|
1939
|
+
totals.estimatedSavingsUSD = addEstimate(totals.estimatedSavingsUSD, session.estimatedSavingsUSD);
|
|
1673
1940
|
totals.realCostUSD += session.realCostUSD;
|
|
1674
1941
|
totals.messageCount += session.messageCount;
|
|
1675
1942
|
totals.tokensIn += session.tokensIn;
|
|
@@ -1972,6 +2239,7 @@ var KNOWN_CONFIG_KEYS_BY_PATH = new Map([
|
|
|
1972
2239
|
"router",
|
|
1973
2240
|
[
|
|
1974
2241
|
"mode",
|
|
2242
|
+
"executionMode",
|
|
1975
2243
|
"localDefault",
|
|
1976
2244
|
"trivialPromptMaxChars",
|
|
1977
2245
|
"frontierPromptMinChars",
|
|
@@ -2058,7 +2326,9 @@ var KNOWN_CONFIG_KEYS_BY_PATH = new Map([
|
|
|
2058
2326
|
]
|
|
2059
2327
|
],
|
|
2060
2328
|
["graph.worktrees", ["enabled"]],
|
|
2061
|
-
["orchestrator", ["roles", "maxRetries"]]
|
|
2329
|
+
["orchestrator", ["roles", "primary", "maxRetries", "maxModelsPerNode"]],
|
|
2330
|
+
["orchestrator.primary", ["executionMode", "model", "fallbacks"]],
|
|
2331
|
+
["orchestrator.primary.fallbacks[]", MODEL_REF_KEYS]
|
|
2062
2332
|
]);
|
|
2063
2333
|
function isUnrecognizedKeysIssue(issue) {
|
|
2064
2334
|
return issue.code === "unrecognized_keys" && Array.isArray(issue.keys);
|
|
@@ -2164,6 +2434,211 @@ function formatConfigError(error) {
|
|
|
2164
2434
|
});
|
|
2165
2435
|
}
|
|
2166
2436
|
|
|
2437
|
+
// src/messages/executionSetup.ts
|
|
2438
|
+
var executionSetupMessages = {
|
|
2439
|
+
setupIntro: "openteam setup — configure local, frontier, or mixed team execution",
|
|
2440
|
+
runtimeQuestion: "Which local runtimes do you want to enable? (choose 'None' for frontier-only)",
|
|
2441
|
+
runtimeNoneLabel: "None — use frontier models only",
|
|
2442
|
+
runtimeNoneHint: "no local runtime (frontier execution)",
|
|
2443
|
+
executionModeQuestion: "Team execution mode",
|
|
2444
|
+
executionModeChoices: {
|
|
2445
|
+
local: {
|
|
2446
|
+
label: "local — every agent stays on configured local providers",
|
|
2447
|
+
hint: "requires at least one enabled local runtime"
|
|
2448
|
+
},
|
|
2449
|
+
frontier: {
|
|
2450
|
+
label: "frontier — every agent uses frontier providers",
|
|
2451
|
+
hint: "does not require a local runtime"
|
|
2452
|
+
},
|
|
2453
|
+
mixed: {
|
|
2454
|
+
label: "mixed — policies may select local or frontier models",
|
|
2455
|
+
hint: "the global mode is an upper bound; per-agent policies may narrow it"
|
|
2456
|
+
}
|
|
2457
|
+
},
|
|
2458
|
+
frontierOnlyNote: [
|
|
2459
|
+
"No local runtime is enabled, so the team will use frontier providers.",
|
|
2460
|
+
"The selected primary model is written consistently to opencode.json and",
|
|
2461
|
+
".opencode/agent/openteam.md."
|
|
2462
|
+
].join(`
|
|
2463
|
+
`),
|
|
2464
|
+
frontierOnlyTitle: "Frontier execution",
|
|
2465
|
+
localOnlyNote: (model) => [
|
|
2466
|
+
"Every team agent is restricted to configured local providers.",
|
|
2467
|
+
`The primary coordinator is pinned to ${model}.`,
|
|
2468
|
+
"No frontier provider is required for execution while this mode is active."
|
|
2469
|
+
].join(`
|
|
2470
|
+
`),
|
|
2471
|
+
localOnlyTitle: "Local execution",
|
|
2472
|
+
localRequiredError: "local execution requires an enabled local runtime",
|
|
2473
|
+
mixedPrimaryQuestion: "Primary coordinator model",
|
|
2474
|
+
mixedPrimaryLocal: (model) => `Use local thinking model ${model}`,
|
|
2475
|
+
mixedPrimaryFrontier: "Choose a frontier model",
|
|
2476
|
+
frontierModelQuestion: "Frontier model (type to search; cheapest capable entries first)",
|
|
2477
|
+
primaryPolicySummary: (params) => [
|
|
2478
|
+
`Execution mode: ${params.executionMode}`,
|
|
2479
|
+
`Primary model: ${params.primaryModel}`,
|
|
2480
|
+
`Primary fallbacks: ${params.fallbacks}`,
|
|
2481
|
+
"Worker model policy: auto within the global execution mode"
|
|
2482
|
+
],
|
|
2483
|
+
noFallbacks: "none",
|
|
2484
|
+
yoloQuestion: "Enable YOLO mode? (opencode auto-approves all permissions)",
|
|
2485
|
+
configuredSummary: (params) => [
|
|
2486
|
+
...executionSetupMessages.primaryPolicySummary({
|
|
2487
|
+
executionMode: params.executionMode,
|
|
2488
|
+
primaryModel: params.primaryModel,
|
|
2489
|
+
fallbacks: executionSetupMessages.noFallbacks
|
|
2490
|
+
}),
|
|
2491
|
+
`Frontier baseline: ${params.frontierBaseline}`,
|
|
2492
|
+
`Local runtimes: ${params.localRuntimes}`,
|
|
2493
|
+
`YOLO mode: ${params.yolo ? "enabled (auto-approves permissions)" : "disabled"}`,
|
|
2494
|
+
"Web Console: launched separately with 'openteam console' (multi-session, loopback)",
|
|
2495
|
+
`Wrote: ${params.opencodePath}, ${params.configPath}, ${params.agentPath}, ${params.agentDir}/*.md (${params.roleAgentCount} standard subagents), ${params.commandDir}/*.md (${params.commandCount} commands); ensured ${params.gitignorePath}`,
|
|
2496
|
+
"",
|
|
2497
|
+
"Next steps:",
|
|
2498
|
+
params.executionMode === "local" ? " 1. No frontier authentication is required while local execution is active" : " 1. Authenticate the frontier provider: opencode auth login",
|
|
2499
|
+
params.executionMode === "local" ? executionSetupMessages.localRuntimeNextStep : params.executionMode === "mixed" ? executionSetupMessages.mixedNextStep : executionSetupMessages.frontierNextStep,
|
|
2500
|
+
" 3. Press Tab and pick the 'openteam' agent, or type / and pick an /openteam… command"
|
|
2501
|
+
],
|
|
2502
|
+
configuredTitle: "openteam configured",
|
|
2503
|
+
complete: (paths) => `openteam setup complete: ${paths}`,
|
|
2504
|
+
error: (message) => `setup error: ${message}`,
|
|
2505
|
+
localRuntimeNextStep: " 2. Make sure every configured local runtime is reachable",
|
|
2506
|
+
frontierNextStep: " 2. Open opencode in this repo; openteam will use frontier models",
|
|
2507
|
+
mixedNextStep: " 2. Make sure local runtimes are reachable; unavailable candidates can use configured fallbacks",
|
|
2508
|
+
done: "Done. Open opencode and select the 'openteam' agent.",
|
|
2509
|
+
rolePolicy: {
|
|
2510
|
+
whyTitle: "Configure execution and model selection",
|
|
2511
|
+
why: (count) => `${count} roster role(s) have no explicit execution/model override. Configure a compact policy now, or leave them on the built-in automatic policy.`,
|
|
2512
|
+
modeQuestion: (roleID, agentName) => `Execution mode for "${roleID}" (${agentName})`,
|
|
2513
|
+
modeInheritLabel: "Inherit the team execution mode",
|
|
2514
|
+
modeLocalLabel: "Local only",
|
|
2515
|
+
modeFrontierLabel: "Frontier only",
|
|
2516
|
+
modelQuestion: (roleID) => `Model selection for "${roleID}"`,
|
|
2517
|
+
modelAutoLabel: "Auto — choose the cheapest capable eligible model",
|
|
2518
|
+
modelExactLabel: "Exact provider/model pin",
|
|
2519
|
+
modelExactQuestion: (roleID) => `Exact model for "${roleID}" as provider/model`,
|
|
2520
|
+
modelExactPlaceholder: "provider/model",
|
|
2521
|
+
fallbacksQuestion: (roleID) => `Ordered fallbacks for "${roleID}" (comma-separated provider/model, blank for none)`,
|
|
2522
|
+
fallbacksPlaceholder: "provider/model, provider/model",
|
|
2523
|
+
configured: (roleIDs, path) => ` ✓ execution/model policy: ${roleIDs} → ${path}`,
|
|
2524
|
+
invalidModelRef: (value) => `invalid model "${value}"; expected provider/model`
|
|
2525
|
+
}
|
|
2526
|
+
};
|
|
2527
|
+
var executionPolicyMessages = {
|
|
2528
|
+
doctorHelp: " openteam doctor Diagnose runtimes, execution policies, and config",
|
|
2529
|
+
agentsHelp: " openteam agents List agents, model identity, and execution policy",
|
|
2530
|
+
section: "Configured execution policies:",
|
|
2531
|
+
globalMode: (mode) => ` execution mode: ${mode}`,
|
|
2532
|
+
line: (policy) => ` ${policy.owner}: mode=${policy.executionMode ?? "inherit"} · model=${policy.model} · fallbacks=${policy.fallbacks.length > 0 ? policy.fallbacks.join(" → ") : "none"}`,
|
|
2533
|
+
retryLimits: (maxRetries, maxModelsPerNode) => ` retry limits: ${maxRetries} retries per model · ${maxModelsPerNode} model(s) per node`,
|
|
2534
|
+
inheritedDefaultUnset: "inherits the opencode.json default (not set); execution follows the configured team and agent policy",
|
|
2535
|
+
inheritedDefault: (subscription) => `inherits default → ${subscription}; execution follows the configured team and agent policy`,
|
|
2536
|
+
unprofiledRoleRouting: " routing: no explicit override — the role inherits its built-in or fallback policy within the global execution-mode upper bound. Roster prose is not executable configuration.",
|
|
2537
|
+
unprofiledRoleRemedy: " remedy: run `openteam roles init`, or add executionMode, model, and ordered fallbacks under orchestrator.roles in .opencode/openteam.json.",
|
|
2538
|
+
noLocalMixed: " ⚠ No local runtime reachable: mixed execution currently has only frontier candidates.",
|
|
2539
|
+
noLocalBlocked: " ✗ No local runtime reachable: local execution is blocked until a configured runtime is available.",
|
|
2540
|
+
primaryLocalMismatch: (name, providerID) => ` ✗ local execution is configured, but primary agent '${name}' points at provider '${providerID}', which opencode.json does not configure.`,
|
|
2541
|
+
primaryLocalMismatchCause: " cause: the primary agent artifact and opencode.json no longer agree on the selected provider/model identity.",
|
|
2542
|
+
primaryLocalMismatchConsequence: " consequence: opencode cannot start the coordinator, so it cannot read the roster or distribute work.",
|
|
2543
|
+
primaryLocalMismatchRemedy: (sourceFile) => ` remedy: re-run \`openteam setup\` to regenerate ${sourceFile} and opencode.json from the same primary policy, then re-run \`openteam doctor\`.`
|
|
2544
|
+
};
|
|
2545
|
+
var executionPolicyEditorMessages = {
|
|
2546
|
+
title: "Execution and model policy",
|
|
2547
|
+
globalMode: "Global execution mode",
|
|
2548
|
+
primaryModel: "Primary model",
|
|
2549
|
+
primaryFallbacks: "Primary fallbacks",
|
|
2550
|
+
retryLimits: "Retry limits",
|
|
2551
|
+
automatic: "auto",
|
|
2552
|
+
inherit: "inherit",
|
|
2553
|
+
none: "none",
|
|
2554
|
+
retryValue: (maxRetries, maxModelsPerNode) => `${maxRetries} retries per model; ${maxModelsPerNode} model(s) per node`
|
|
2555
|
+
};
|
|
2556
|
+
var localModeChangeMessages = {
|
|
2557
|
+
localProviderInFrontierDomain: (path, domainPath, providerID, modelID) => `${path}: model "${providerID}/${modelID}" uses provider "${providerID}", which is configured for local dispatch and cannot be selected when ${domainPath} is "frontier". Update this policy or use \`openteam local on\`.`,
|
|
2558
|
+
frontierProviderInLocalDomain: (path, domainPath, providerID, modelID) => `${path}: model "${providerID}/${modelID}" uses provider "${providerID}", which is not a configured local provider and cannot be selected when ${domainPath} is "local". Update this policy or use \`openteam local on\`.`
|
|
2559
|
+
};
|
|
2560
|
+
|
|
2561
|
+
// src/config/executionPolicyDomains.ts
|
|
2562
|
+
function compareRoleIDs([left], [right]) {
|
|
2563
|
+
return left === right ? 0 : left < right ? -1 : 1;
|
|
2564
|
+
}
|
|
2565
|
+
function policyModelReferences(policy, path) {
|
|
2566
|
+
const references = [];
|
|
2567
|
+
if (policy?.model !== undefined && policy.model !== "auto") {
|
|
2568
|
+
references.push({ path: `${path}.model`, model: policy.model });
|
|
2569
|
+
}
|
|
2570
|
+
for (const [index, fallback] of (policy?.fallbacks ?? []).entries()) {
|
|
2571
|
+
references.push({
|
|
2572
|
+
path: `${path}.fallbacks.${index}`,
|
|
2573
|
+
model: fallback
|
|
2574
|
+
});
|
|
2575
|
+
}
|
|
2576
|
+
return references;
|
|
2577
|
+
}
|
|
2578
|
+
function configuredPolicies(config) {
|
|
2579
|
+
const policies = [
|
|
2580
|
+
{
|
|
2581
|
+
path: "orchestrator.primary",
|
|
2582
|
+
policy: config.orchestrator?.primary
|
|
2583
|
+
}
|
|
2584
|
+
];
|
|
2585
|
+
const roles = Object.entries(config.orchestrator?.roles ?? {}).sort(compareRoleIDs);
|
|
2586
|
+
for (const [roleID, role] of roles) {
|
|
2587
|
+
policies.push({
|
|
2588
|
+
path: `orchestrator.roles.${roleID}`,
|
|
2589
|
+
policy: agentExecutionPolicyForRole(role)
|
|
2590
|
+
});
|
|
2591
|
+
}
|
|
2592
|
+
return policies;
|
|
2593
|
+
}
|
|
2594
|
+
function effectiveConfigModelDomainViolations(config) {
|
|
2595
|
+
const routerMode = resolveExecutionMode({
|
|
2596
|
+
routerMode: config.router.executionMode,
|
|
2597
|
+
legacyLocalOnly: config.router.localOnly,
|
|
2598
|
+
legacyFrontierOnly: config.router.frontierOnly
|
|
2599
|
+
});
|
|
2600
|
+
const localProviderIDs = new Set(config.local.runtimes.map((runtime) => runtime.defaultModel.providerID));
|
|
2601
|
+
const violations = [];
|
|
2602
|
+
for (const { path, policy } of configuredPolicies(config)) {
|
|
2603
|
+
if (policy === undefined) {
|
|
2604
|
+
continue;
|
|
2605
|
+
}
|
|
2606
|
+
const executionMode = resolveAgentExecutionPolicy(routerMode, policy).executionMode;
|
|
2607
|
+
if (executionMode === "mixed") {
|
|
2608
|
+
continue;
|
|
2609
|
+
}
|
|
2610
|
+
const domainPath = policy.executionMode === undefined ? "router.executionMode" : `${path}.executionMode`;
|
|
2611
|
+
for (const reference of policyModelReferences(policy, path)) {
|
|
2612
|
+
const isLocalProvider = localProviderIDs.has(reference.model.providerID);
|
|
2613
|
+
if (executionMode === "local" && !isLocalProvider || executionMode === "frontier" && isLocalProvider) {
|
|
2614
|
+
violations.push({
|
|
2615
|
+
path: reference.path,
|
|
2616
|
+
domainPath,
|
|
2617
|
+
model: reference.model,
|
|
2618
|
+
executionMode
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
return violations;
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
class EffectiveConfigModelDomainError extends Error {
|
|
2627
|
+
violations;
|
|
2628
|
+
constructor(violations) {
|
|
2629
|
+
super(violations.map((violation) => violation.executionMode === "frontier" ? localModeChangeMessages.localProviderInFrontierDomain(violation.path, violation.domainPath, violation.model.providerID, violation.model.modelID) : localModeChangeMessages.frontierProviderInLocalDomain(violation.path, violation.domainPath, violation.model.providerID, violation.model.modelID)).join(`
|
|
2630
|
+
`));
|
|
2631
|
+
this.name = "EffectiveConfigModelDomainError";
|
|
2632
|
+
this.violations = [...violations];
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
function assertEffectiveConfigModelDomains(config) {
|
|
2636
|
+
const violations = effectiveConfigModelDomainViolations(config);
|
|
2637
|
+
if (violations.length > 0) {
|
|
2638
|
+
throw new EffectiveConfigModelDomainError(violations);
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2167
2642
|
// src/config/load.ts
|
|
2168
2643
|
function loadOpenTeamConfig(raw) {
|
|
2169
2644
|
return OpenTeamConfigSchema.parse(migrateConfig(raw ?? {}));
|
|
@@ -2184,9 +2659,76 @@ async function readOpenTeamConfigFile(path = ".opencode/openteam.json", storage
|
|
|
2184
2659
|
// src/config/persist.ts
|
|
2185
2660
|
var DEFAULT_CONFIG_PATH = ".opencode/openteam.json";
|
|
2186
2661
|
var DEFAULT_LOCAL_OVERLAY_PATH = ".opencode/openteam-local/config.local.json";
|
|
2662
|
+
var ROLE_OVERRIDE_KEYS = [
|
|
2663
|
+
"opencodeAgent",
|
|
2664
|
+
"defaultTier",
|
|
2665
|
+
"minReasoningTier",
|
|
2666
|
+
"minCodeQualityTier",
|
|
2667
|
+
"needsToolCallingByDefault",
|
|
2668
|
+
"preferredFrontierModels",
|
|
2669
|
+
"localFirst",
|
|
2670
|
+
"requiresLocalRuntime",
|
|
2671
|
+
"localRuntimes",
|
|
2672
|
+
"localModels",
|
|
2673
|
+
"localModelClass",
|
|
2674
|
+
"executionMode",
|
|
2675
|
+
"model",
|
|
2676
|
+
"fallbacks"
|
|
2677
|
+
];
|
|
2678
|
+
function sameConfigValue(left, right) {
|
|
2679
|
+
return left === right || JSON.stringify(left) === JSON.stringify(right);
|
|
2680
|
+
}
|
|
2681
|
+
function compactRoleOverride(roleID, role) {
|
|
2682
|
+
const base = getRoleProfile(roleID) ?? synthesiseFallbackProfile(roleID);
|
|
2683
|
+
const override = {};
|
|
2684
|
+
for (const key of ROLE_OVERRIDE_KEYS) {
|
|
2685
|
+
if (!sameConfigValue(role[key], base[key])) {
|
|
2686
|
+
override[key] = role[key];
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
return override;
|
|
2690
|
+
}
|
|
2691
|
+
function compactRoles(roles) {
|
|
2692
|
+
return Object.fromEntries(Object.entries(roles).sort(([left], [right]) => left.localeCompare(right)).map(([roleID, role]) => [roleID, compactRoleOverride(roleID, role)]));
|
|
2693
|
+
}
|
|
2187
2694
|
function serializeOpenTeamConfig(config) {
|
|
2188
2695
|
const validated = OpenTeamConfigSchema.parse(config);
|
|
2189
|
-
|
|
2696
|
+
const {
|
|
2697
|
+
privacyMode: _privacyMode,
|
|
2698
|
+
router: validatedRouter,
|
|
2699
|
+
...withoutLegacyPrivacy
|
|
2700
|
+
} = validated;
|
|
2701
|
+
const {
|
|
2702
|
+
localOnly: _localOnly,
|
|
2703
|
+
frontierOnly: _frontierOnly,
|
|
2704
|
+
mode,
|
|
2705
|
+
trivialPromptMaxChars,
|
|
2706
|
+
frontierPromptMinChars,
|
|
2707
|
+
...canonicalRouter
|
|
2708
|
+
} = validatedRouter;
|
|
2709
|
+
const executionMode = resolveExecutionMode({
|
|
2710
|
+
routerMode: validatedRouter.executionMode,
|
|
2711
|
+
legacyLocalOnly: validatedRouter.localOnly,
|
|
2712
|
+
legacyFrontierOnly: validatedRouter.frontierOnly
|
|
2713
|
+
});
|
|
2714
|
+
const router = {
|
|
2715
|
+
...canonicalRouter,
|
|
2716
|
+
...mode === "balanced" ? {} : { mode },
|
|
2717
|
+
...trivialPromptMaxChars === 280 ? {} : { trivialPromptMaxChars },
|
|
2718
|
+
...frontierPromptMinChars === 2000 ? {} : { frontierPromptMinChars },
|
|
2719
|
+
executionMode
|
|
2720
|
+
};
|
|
2721
|
+
const orchestrator = validated.orchestrator === undefined ? undefined : {
|
|
2722
|
+
...validated.orchestrator,
|
|
2723
|
+
roles: compactRoles(validated.orchestrator.roles)
|
|
2724
|
+
};
|
|
2725
|
+
const canonical = {
|
|
2726
|
+
...withoutLegacyPrivacy,
|
|
2727
|
+
configVersion: CURRENT_CONFIG_VERSION,
|
|
2728
|
+
router,
|
|
2729
|
+
...orchestrator === undefined ? {} : { orchestrator }
|
|
2730
|
+
};
|
|
2731
|
+
return `${JSON.stringify(canonical, null, 2)}
|
|
2190
2732
|
`;
|
|
2191
2733
|
}
|
|
2192
2734
|
async function writeOpenTeamConfigFile(config, path = DEFAULT_CONFIG_PATH, storage = createFsStorageProvider(process.cwd())) {
|
|
@@ -2323,6 +2865,7 @@ async function loadRuntimeOpenTeamConfig(storage, options, path = DEFAULT_CONFIG
|
|
|
2323
2865
|
].filter((entry) => entry !== undefined);
|
|
2324
2866
|
const label = mergedSources.length === 0 ? "runtime config" : `runtime config merged from ${mergedSources.join(", ")}`;
|
|
2325
2867
|
const config = validateConfigInput(label, mergedRaw);
|
|
2868
|
+
const deprecations = detectConfigDeprecations(mergedRaw);
|
|
2326
2869
|
const source = optionsFound ? "options" : fileFound ? "file" : "default";
|
|
2327
2870
|
return {
|
|
2328
2871
|
config,
|
|
@@ -2330,6 +2873,7 @@ async function loadRuntimeOpenTeamConfig(storage, options, path = DEFAULT_CONFIG
|
|
|
2330
2873
|
fileFound,
|
|
2331
2874
|
optionsFound,
|
|
2332
2875
|
overlayFound,
|
|
2876
|
+
deprecations,
|
|
2333
2877
|
searchedPaths: overlayPath === null ? [path] : [path, overlayPath]
|
|
2334
2878
|
};
|
|
2335
2879
|
}
|
|
@@ -2449,6 +2993,10 @@ function scrubSecretStrings(value, secrets) {
|
|
|
2449
2993
|
};
|
|
2450
2994
|
return walk(value);
|
|
2451
2995
|
}
|
|
2996
|
+
function withoutDeprecatedPrivacyMode(config) {
|
|
2997
|
+
const { privacyMode: _privacyMode, ...next } = config;
|
|
2998
|
+
return next;
|
|
2999
|
+
}
|
|
2452
3000
|
var DEFAULT_CONFIG_AUDIT_PATH = ".opencode/openteam-local/config-audit.jsonl";
|
|
2453
3001
|
function editableSurface() {
|
|
2454
3002
|
return [
|
|
@@ -2486,7 +3034,7 @@ function createConfigEditor(ports) {
|
|
|
2486
3034
|
editableSurface,
|
|
2487
3035
|
async read() {
|
|
2488
3036
|
const resolution = await loadRuntimeOpenTeamConfig(ports.storage, undefined, basePath, overlayPath);
|
|
2489
|
-
const scrubbed = scrubSecretStrings(resolution.config, secretValues());
|
|
3037
|
+
const scrubbed = withoutDeprecatedPrivacyMode(scrubSecretStrings(resolution.config, secretValues()));
|
|
2490
3038
|
return {
|
|
2491
3039
|
config: scrubbed,
|
|
2492
3040
|
reload: {
|
|
@@ -2508,6 +3056,7 @@ function createConfigEditor(ports) {
|
|
|
2508
3056
|
let submitted;
|
|
2509
3057
|
try {
|
|
2510
3058
|
submitted = OpenTeamConfigObjectSchema.parse(submittedRaw);
|
|
3059
|
+
assertEffectiveConfigModelDomains(submitted);
|
|
2511
3060
|
} catch (error) {
|
|
2512
3061
|
return {
|
|
2513
3062
|
status: 400,
|
|
@@ -2573,7 +3122,6 @@ var RESTART_REQUIRED_KEYS = [
|
|
|
2573
3122
|
"baseline",
|
|
2574
3123
|
"router",
|
|
2575
3124
|
"local",
|
|
2576
|
-
"privacyMode",
|
|
2577
3125
|
"graph",
|
|
2578
3126
|
"orchestrator",
|
|
2579
3127
|
"memory",
|
|
@@ -2581,6 +3129,110 @@ var RESTART_REQUIRED_KEYS = [
|
|
|
2581
3129
|
"console.port"
|
|
2582
3130
|
];
|
|
2583
3131
|
|
|
3132
|
+
// src/messages/commands.ts
|
|
3133
|
+
var clearCacheMessages = {
|
|
3134
|
+
header: "openteam clear-cache — frozen plugin cache entries:",
|
|
3135
|
+
columns: {
|
|
3136
|
+
specDir: "spec dir",
|
|
3137
|
+
pinned: "spec-pinned",
|
|
3138
|
+
installed: "installed",
|
|
3139
|
+
mtime: "mtime"
|
|
3140
|
+
},
|
|
3141
|
+
reparseSkipSuffix: " [SKIP — reparse point]",
|
|
3142
|
+
deletedLabel: "deleted.",
|
|
3143
|
+
lockedLabel: (message) => `[LOCKED] ${message}`,
|
|
3144
|
+
pathOutsideWarning: (specDir, absolutePath) => ` ⚠ ${specDir}: path outside cacheRoot (${absolutePath}), skipped.`,
|
|
3145
|
+
processed: (count) => `${count} entry(ies) processed.`,
|
|
3146
|
+
found: (count) => `${count} entry(ies) found. Use --delete to remove them.`
|
|
3147
|
+
};
|
|
3148
|
+
var baselineMessages = {
|
|
3149
|
+
effectiveAuto: "cheapest-capable (auto)",
|
|
3150
|
+
pinnedSuffix: (ref) => `${ref} (pinned)`,
|
|
3151
|
+
summary: (params) => [
|
|
3152
|
+
"openteam baseline:",
|
|
3153
|
+
` mode: ${params.mode}`,
|
|
3154
|
+
` pinned: ${params.pinned}`,
|
|
3155
|
+
` hardDefault: ${params.hardDefault}`,
|
|
3156
|
+
` effective: ${params.effective}`
|
|
3157
|
+
],
|
|
3158
|
+
invalidModel: (input) => `Invalid model "${input}". Use the provider/model format, e.g. anthropic/claude-sonnet-4-5.`,
|
|
3159
|
+
pinnedTo: (ref) => `Baseline pinned to ${ref} (pinned mode).`,
|
|
3160
|
+
autoMode: "Baseline set to auto mode (cheapest-capable)."
|
|
3161
|
+
};
|
|
3162
|
+
var localMessages = {
|
|
3163
|
+
help: {
|
|
3164
|
+
status: " openteam local status Show the execution mode (local / frontier / mixed)",
|
|
3165
|
+
off: " openteam local off Use frontier models only",
|
|
3166
|
+
only: " openteam local only Use local models only",
|
|
3167
|
+
on: " openteam local on Enable mixed local/frontier execution"
|
|
3168
|
+
},
|
|
3169
|
+
noRuntimes: "none",
|
|
3170
|
+
modeFrontier: "frontier",
|
|
3171
|
+
modeLocal: "local",
|
|
3172
|
+
modeMixed: "mixed",
|
|
3173
|
+
summary: (params) => [
|
|
3174
|
+
"openteam local:",
|
|
3175
|
+
` execution mode: ${params.mode}`,
|
|
3176
|
+
` local runtimes: ${params.runtimes}`
|
|
3177
|
+
],
|
|
3178
|
+
frontierOnlyNoChange: "No change: execution mode is already frontier.",
|
|
3179
|
+
frontierOnlyEnabled: "Frontier execution enabled.",
|
|
3180
|
+
localOnlyNoRuntimes: "Cannot enable local execution: no enabled local runtime is configured. Run 'openteam setup' or enable a local runtime in .opencode/openteam.json first.",
|
|
3181
|
+
localOnlyNoChange: "No change: execution mode is already local.",
|
|
3182
|
+
localOnlyEnabled: "Local execution enabled.",
|
|
3183
|
+
localFirstNoChange: "No change: execution mode is already mixed.",
|
|
3184
|
+
localFirstReEnabled: "Mixed local/frontier execution enabled. Make sure each configured provider is reachable.",
|
|
3185
|
+
unknownSubcommand: (subcommand, help) => `Unknown local subcommand: ${subcommand}
|
|
3186
|
+
|
|
3187
|
+
${help}`
|
|
3188
|
+
};
|
|
3189
|
+
var yoloMessages = {
|
|
3190
|
+
updated: (path, status, agentNote) => `${path} updated. ${status}${agentNote}
|
|
3191
|
+
Restart opencode (or reload) so agents pick up the new permissions.`
|
|
3192
|
+
};
|
|
3193
|
+
var frontierCatalogMessages = {
|
|
3194
|
+
free: "free",
|
|
3195
|
+
costPerMillion: (inputUSD, outputUSD) => `$${inputUSD}/$${outputUSD} per 1M`
|
|
3196
|
+
};
|
|
3197
|
+
var migrateMessages = {
|
|
3198
|
+
header: "openteam migrate:",
|
|
3199
|
+
nothingToMigrate: " nothing to migrate — the P15b layout is already in place.",
|
|
3200
|
+
counts: (params) => [
|
|
3201
|
+
` moved: ${params.moved} file(s)`,
|
|
3202
|
+
` deduped: ${params.deduped} file(s) (destination already identical)`,
|
|
3203
|
+
` conflicts: ${params.conflicts} file(s)`
|
|
3204
|
+
],
|
|
3205
|
+
relocatedHeader: " relocated:",
|
|
3206
|
+
relocatedEntry: (from, to, deduped) => ` · ${from} → ${to}${deduped ? " (deduped)" : ""}`,
|
|
3207
|
+
conflictsHeader: " ⚠ left in place (destination exists with different content — no data lost):",
|
|
3208
|
+
conflictEntry: (from, to) => ` · ${from} → ${to}`,
|
|
3209
|
+
manualWorktreesHeader: " ⚠ legacy git worktree(s) need a manual move (run `git worktree move`):",
|
|
3210
|
+
manualWorktreeEntry: (label, from, to) => ` · ${label}: ${from} → ${to}`,
|
|
3211
|
+
success: " ✓ machine-local runtime output relocated under .opencode/openteam-local/."
|
|
3212
|
+
};
|
|
3213
|
+
var rolesInitMessages = {
|
|
3214
|
+
help: " openteam roles init Compose per-role execution, model, and fallback policy",
|
|
3215
|
+
header: "openteam roles init:",
|
|
3216
|
+
title: "openteam roles init",
|
|
3217
|
+
noRoster: (path) => ` ✗ no roster at ${path}.`,
|
|
3218
|
+
noRosterRemedy: " Cast the team first: run `openteam setup`, then ask the orchestrator to register the cast.",
|
|
3219
|
+
unparseableRoster: (path, error) => ` ✗ ${path} is present but unparseable: ${error}`,
|
|
3220
|
+
unparseableRosterRemedy: " Run `openteam doctor` for the remedy, then re-run this command.",
|
|
3221
|
+
allProfiled: (count) => ` ✓ ${count} roster role(s) checked; no unprofiled roles — every role resolves a routing profile.`,
|
|
3222
|
+
skipLabel: "Skip for now",
|
|
3223
|
+
skipHint: "leaves the role on its built-in or fallback policy; doctor keeps reporting the missing explicit override",
|
|
3224
|
+
runtimeQuestion: (roleID) => `Preferred local runtime(s) for "${roleID}" (in failover order)`,
|
|
3225
|
+
runtimeHint: "machine-local — written to the git-ignored overlay",
|
|
3226
|
+
cancelled: (reason) => ` ✗ cancelled (${reason}); nothing was written.`,
|
|
3227
|
+
invalidResult: (reason) => ` ✗ refusing to write: the resulting config is invalid — ${reason}`,
|
|
3228
|
+
deprecation: (message) => ` ⚠ ${message}`,
|
|
3229
|
+
nothingWritten: (skipped) => ` · skipped ${skipped}; nothing written.`,
|
|
3230
|
+
nothingToWriteOutro: "Nothing to write.",
|
|
3231
|
+
runtimeBinding: (path) => ` ✓ runtime binding: ${path} (git-ignored, machine-local)`,
|
|
3232
|
+
skippedSummary: (roleIDs) => ` · skipped: ${roleIDs} — using built-in or fallback policy; re-run this command to add an explicit override.`,
|
|
3233
|
+
outro: "Role policy updated."
|
|
3234
|
+
};
|
|
3235
|
+
|
|
2584
3236
|
// src/web/graphRoute.ts
|
|
2585
3237
|
var GRAPH_DISABLED_REMEDIATION = 'Agent tree is off. In .opencode/openteam.json set graph.mode to a value other than "off" and console.graphView.enabled to true, then restart the console.';
|
|
2586
3238
|
var GRAPH_DISABLED_ENVELOPE = {
|
|
@@ -2990,10 +3642,10 @@ function escapeHtml(value) {
|
|
|
2990
3642
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2991
3643
|
}
|
|
2992
3644
|
function usd(value) {
|
|
2993
|
-
return `$${value.toFixed(5)}`;
|
|
3645
|
+
return value === null ? routingMessages.unknownCost : `$${value.toFixed(5)}`;
|
|
2994
3646
|
}
|
|
2995
3647
|
function pct(value) {
|
|
2996
|
-
return `${value.toFixed(2)}%`;
|
|
3648
|
+
return value === null ? routingMessages.unknownCost : `${value.toFixed(2)}%`;
|
|
2997
3649
|
}
|
|
2998
3650
|
function ms(value) {
|
|
2999
3651
|
return value >= 1000 ? `${(value / 1000).toFixed(2)}s` : `${Math.round(value)}ms`;
|
|
@@ -3689,6 +4341,42 @@ var OVERLAY_PATH = ".opencode/openteam-local/config.local.json";
|
|
|
3689
4341
|
function codeList(keys) {
|
|
3690
4342
|
return keys.map((key) => `<code>${escapeHtml(key)}</code>`).join(", ");
|
|
3691
4343
|
}
|
|
4344
|
+
function record(value) {
|
|
4345
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
4346
|
+
}
|
|
4347
|
+
function modelSelection(value) {
|
|
4348
|
+
if (value === "auto" || value === undefined) {
|
|
4349
|
+
return executionPolicyEditorMessages.automatic;
|
|
4350
|
+
}
|
|
4351
|
+
const selection = record(value);
|
|
4352
|
+
return typeof selection.providerID === "string" && typeof selection.modelID === "string" ? `${selection.providerID}/${selection.modelID}` : executionPolicyEditorMessages.automatic;
|
|
4353
|
+
}
|
|
4354
|
+
function fallbackSelections(value) {
|
|
4355
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
4356
|
+
return executionPolicyEditorMessages.none;
|
|
4357
|
+
}
|
|
4358
|
+
return value.map(modelSelection).join(" → ");
|
|
4359
|
+
}
|
|
4360
|
+
function renderExecutionPolicyOverview(config) {
|
|
4361
|
+
const root = record(config);
|
|
4362
|
+
const router = record(root.router);
|
|
4363
|
+
const orchestrator = record(root.orchestrator);
|
|
4364
|
+
const primary = record(orchestrator.primary);
|
|
4365
|
+
const mode = typeof router.executionMode === "string" ? router.executionMode : router.localOnly === true ? "local" : router.frontierOnly === true ? "frontier" : "mixed";
|
|
4366
|
+
const maxRetries = typeof orchestrator.maxRetries === "number" ? orchestrator.maxRetries : 2;
|
|
4367
|
+
const maxModelsPerNode = typeof orchestrator.maxModelsPerNode === "number" ? orchestrator.maxModelsPerNode : 2;
|
|
4368
|
+
return [
|
|
4369
|
+
'<div class="config-policy">',
|
|
4370
|
+
`<h3>${escapeHtml(executionPolicyEditorMessages.title)}</h3>`,
|
|
4371
|
+
'<dl class="config-policy-list">',
|
|
4372
|
+
`<dt>${escapeHtml(executionPolicyEditorMessages.globalMode)}</dt><dd><code>${escapeHtml(mode)}</code></dd>`,
|
|
4373
|
+
`<dt>${escapeHtml(executionPolicyEditorMessages.primaryModel)}</dt><dd><code>${escapeHtml(modelSelection(primary.model))}</code></dd>`,
|
|
4374
|
+
`<dt>${escapeHtml(executionPolicyEditorMessages.primaryFallbacks)}</dt><dd><code>${escapeHtml(fallbackSelections(primary.fallbacks))}</code></dd>`,
|
|
4375
|
+
`<dt>${escapeHtml(executionPolicyEditorMessages.retryLimits)}</dt><dd>${escapeHtml(executionPolicyEditorMessages.retryValue(maxRetries, maxModelsPerNode))}</dd>`,
|
|
4376
|
+
"</dl>",
|
|
4377
|
+
"</div>"
|
|
4378
|
+
].join("");
|
|
4379
|
+
}
|
|
3692
4380
|
function renderConfigReloadTable(read) {
|
|
3693
4381
|
const hot = read.reload.hotReloadable.length > 0 ? codeList(read.reload.hotReloadable) : '<span class="muted">none</span>';
|
|
3694
4382
|
const restart = read.reload.restartRequired.length > 0 ? codeList(read.reload.restartRequired) : '<span class="muted">none</span>';
|
|
@@ -3708,6 +4396,7 @@ function renderConfigEditorForm(read) {
|
|
|
3708
4396
|
const secretsNote = read.secrets.length > 0 ? `<p class="config-note config-secrets">Secrets are read from environment variables and are never shown or editable here: ${read.secrets.map((s) => `<code>${escapeHtml(s.key)}</code> (${s.set ? "set" : "not set"})`).join(", ")}.</p>` : "";
|
|
3709
4397
|
return [
|
|
3710
4398
|
'<form class="config-form" data-config-form>',
|
|
4399
|
+
renderExecutionPolicyOverview(read.config),
|
|
3711
4400
|
renderConfigReloadTable(read),
|
|
3712
4401
|
overlayNote,
|
|
3713
4402
|
secretsNote,
|
|
@@ -3906,6 +4595,9 @@ function emptyTierSummary() {
|
|
|
3906
4595
|
hard: { count: 0, estimatedUSD: 0, savingsUSD: 0 }
|
|
3907
4596
|
};
|
|
3908
4597
|
}
|
|
4598
|
+
function addEstimate2(current, value) {
|
|
4599
|
+
return current === null || value === null ? null : current + value;
|
|
4600
|
+
}
|
|
3909
4601
|
function summarizeCostRecords(records) {
|
|
3910
4602
|
const report = {
|
|
3911
4603
|
count: 0,
|
|
@@ -3920,30 +4612,33 @@ function summarizeCostRecords(records) {
|
|
|
3920
4612
|
tokensOut: 0,
|
|
3921
4613
|
byTier: emptyTierSummary()
|
|
3922
4614
|
};
|
|
3923
|
-
for (const
|
|
4615
|
+
for (const record2 of records) {
|
|
3924
4616
|
report.count += 1;
|
|
3925
|
-
if (
|
|
4617
|
+
if (record2.routeKind === "local") {
|
|
3926
4618
|
report.localCount += 1;
|
|
3927
|
-
} else if (
|
|
4619
|
+
} else if (record2.routeKind === "blocked") {
|
|
3928
4620
|
report.blockedCount += 1;
|
|
3929
4621
|
} else {
|
|
3930
4622
|
report.frontierCount += 1;
|
|
3931
4623
|
}
|
|
3932
|
-
report.totalEstimatedUSD
|
|
3933
|
-
report.totalBaselineUSD
|
|
3934
|
-
report.totalSavingsUSD
|
|
3935
|
-
report.tokensIn +=
|
|
3936
|
-
report.tokensOut +=
|
|
3937
|
-
const tier = report.byTier[
|
|
4624
|
+
report.totalEstimatedUSD = addEstimate2(report.totalEstimatedUSD, record2.estimatedCostUSD);
|
|
4625
|
+
report.totalBaselineUSD = addEstimate2(report.totalBaselineUSD, record2.baselineCostUSD);
|
|
4626
|
+
report.totalSavingsUSD = addEstimate2(report.totalSavingsUSD, record2.estimatedSavingsUSD);
|
|
4627
|
+
report.tokensIn += record2.tokensIn ?? 0;
|
|
4628
|
+
report.tokensOut += record2.tokensOut ?? 0;
|
|
4629
|
+
const tier = report.byTier[record2.tier];
|
|
3938
4630
|
tier.count += 1;
|
|
3939
|
-
tier.estimatedUSD
|
|
3940
|
-
tier.savingsUSD
|
|
4631
|
+
tier.estimatedUSD = addEstimate2(tier.estimatedUSD, record2.estimatedCostUSD);
|
|
4632
|
+
tier.savingsUSD = addEstimate2(tier.savingsUSD, record2.estimatedSavingsUSD);
|
|
3941
4633
|
}
|
|
3942
|
-
report.savingsPct = report.totalBaselineUSD > 0 ? report.totalSavingsUSD / report.totalBaselineUSD * 100 : 0;
|
|
4634
|
+
report.savingsPct = report.totalBaselineUSD === null || report.totalSavingsUSD === null ? null : report.totalBaselineUSD > 0 ? report.totalSavingsUSD / report.totalBaselineUSD * 100 : 0;
|
|
3943
4635
|
return report;
|
|
3944
4636
|
}
|
|
3945
4637
|
function usd2(value) {
|
|
3946
|
-
return `$${value.toFixed(5)}`;
|
|
4638
|
+
return value === null ? routingMessages.unknownCost : `$${value.toFixed(5)}`;
|
|
4639
|
+
}
|
|
4640
|
+
function pct2(value) {
|
|
4641
|
+
return value === null ? routingMessages.unknownCost : `${value.toFixed(2)}%`;
|
|
3947
4642
|
}
|
|
3948
4643
|
function renderReport(report) {
|
|
3949
4644
|
if (report.count === 0) {
|
|
@@ -3954,7 +4649,7 @@ function renderReport(report) {
|
|
|
3954
4649
|
` decisions: ${report.count} (local ${report.localCount} · frontier ${report.frontierCount} · blocked ${report.blockedCount})`,
|
|
3955
4650
|
` real cost: ${usd2(report.totalEstimatedUSD)}`,
|
|
3956
4651
|
` baseline: ${usd2(report.totalBaselineUSD)}`,
|
|
3957
|
-
` savings: ${usd2(report.totalSavingsUSD)} (${report.savingsPct
|
|
4652
|
+
` savings: ${usd2(report.totalSavingsUSD)} (${pct2(report.savingsPct)})`,
|
|
3958
4653
|
` tokens: in ${report.tokensIn} · out ${report.tokensOut}`,
|
|
3959
4654
|
" by tier:"
|
|
3960
4655
|
];
|
|
@@ -4039,16 +4734,16 @@ var DEFAULT_RECENT_ROUTES = 50;
|
|
|
4039
4734
|
var DEFAULT_ACTIVITY_LIMIT = 100;
|
|
4040
4735
|
var DEFAULT_DECISIONS_LIMIT = 100;
|
|
4041
4736
|
var DEFAULT_MEETINGS_LIMIT = 50;
|
|
4042
|
-
function toRouteView(
|
|
4737
|
+
function toRouteView(record2) {
|
|
4043
4738
|
return {
|
|
4044
|
-
ts:
|
|
4045
|
-
tier:
|
|
4046
|
-
routeKind:
|
|
4047
|
-
model: `${
|
|
4048
|
-
promptHash:
|
|
4049
|
-
estimatedCostUSD:
|
|
4050
|
-
estimatedSavingsUSD:
|
|
4051
|
-
budgetAction:
|
|
4739
|
+
ts: record2.ts,
|
|
4740
|
+
tier: record2.tier,
|
|
4741
|
+
routeKind: record2.routeKind,
|
|
4742
|
+
model: `${record2.selected.providerID}/${record2.selected.modelID}`,
|
|
4743
|
+
promptHash: record2.promptHash,
|
|
4744
|
+
estimatedCostUSD: record2.estimatedCostUSD,
|
|
4745
|
+
estimatedSavingsUSD: record2.estimatedSavingsUSD,
|
|
4746
|
+
budgetAction: record2.budgetAction
|
|
4052
4747
|
};
|
|
4053
4748
|
}
|
|
4054
4749
|
function isRoute(event) {
|
|
@@ -4101,7 +4796,7 @@ function activityFeed(aggregate, limit) {
|
|
|
4101
4796
|
function buildSessionTabs(aggregate, costRecords, nowMs, limits, options) {
|
|
4102
4797
|
return aggregate.sessions.map((summary) => {
|
|
4103
4798
|
const id = summary.sessionID;
|
|
4104
|
-
const routes = costRecords.filter((
|
|
4799
|
+
const routes = costRecords.filter((record2) => record2.sessionID === id);
|
|
4105
4800
|
const decisions = aggregate.decisions.filter((decision) => decision.sessionID === id).slice(0, Math.max(0, limits.decisionsLimit));
|
|
4106
4801
|
const meetings = aggregate.meetings.filter((meeting) => meeting.sessionID === id).slice(0, Math.max(0, limits.meetingsLimit));
|
|
4107
4802
|
const activity = composeActivity(aggregate.activity.filter((view) => view.sessionID === id), aggregate.decisions.filter((decision) => decision.sessionID === id), aggregate.meetings.filter((meeting) => meeting.sessionID === id), limits.activityLimit);
|
|
@@ -4279,9 +4974,9 @@ async function handlePermission(request, response, sessionID, deps) {
|
|
|
4279
4974
|
return;
|
|
4280
4975
|
}
|
|
4281
4976
|
const body = await readJsonBody2(request, 4096);
|
|
4282
|
-
const
|
|
4283
|
-
const permissionID =
|
|
4284
|
-
const responseValue =
|
|
4977
|
+
const record2 = typeof body === "object" && body !== null ? body : {};
|
|
4978
|
+
const permissionID = record2.permissionID;
|
|
4979
|
+
const responseValue = record2.response;
|
|
4285
4980
|
if (typeof permissionID !== "string" || permissionID.length === 0 || responseValue !== "once" && responseValue !== "always" && responseValue !== "reject") {
|
|
4286
4981
|
response.writeHead(400, JSON_HEADERS3).end(JSON.stringify({ error: "invalid permission" }));
|
|
4287
4982
|
return;
|
|
@@ -4441,8 +5136,8 @@ async function handleWrite(request, response, op, deps) {
|
|
|
4441
5136
|
return;
|
|
4442
5137
|
}
|
|
4443
5138
|
const body = await readJsonBody3(request, 8 * 1024 * 1024);
|
|
4444
|
-
const
|
|
4445
|
-
const rawPath =
|
|
5139
|
+
const record2 = typeof body === "object" && body !== null ? body : undefined;
|
|
5140
|
+
const rawPath = record2?.path;
|
|
4446
5141
|
if (typeof rawPath !== "string") {
|
|
4447
5142
|
sendJson(response, 400, { error: "invalid path" });
|
|
4448
5143
|
return;
|
|
@@ -4462,7 +5157,7 @@ async function handleWrite(request, response, op, deps) {
|
|
|
4462
5157
|
return;
|
|
4463
5158
|
}
|
|
4464
5159
|
if (op === "write" || op === "append") {
|
|
4465
|
-
const content =
|
|
5160
|
+
const content = record2?.content;
|
|
4466
5161
|
if (typeof content !== "string") {
|
|
4467
5162
|
sendJson(response, 400, { error: "invalid content" });
|
|
4468
5163
|
return;
|
|
@@ -4840,7 +5535,7 @@ function classifyAgent(info, context) {
|
|
|
4840
5535
|
name: info.name,
|
|
4841
5536
|
mode: info.mode,
|
|
4842
5537
|
kind: "inherited",
|
|
4843
|
-
subscription:
|
|
5538
|
+
subscription: executionPolicyMessages.inheritedDefaultUnset
|
|
4844
5539
|
};
|
|
4845
5540
|
}
|
|
4846
5541
|
const { subscription } = subscriptionLabel(context.defaultModel, context.localProviders);
|
|
@@ -4849,7 +5544,7 @@ function classifyAgent(info, context) {
|
|
|
4849
5544
|
mode: info.mode,
|
|
4850
5545
|
kind: "inherited",
|
|
4851
5546
|
model: context.defaultModel,
|
|
4852
|
-
subscription:
|
|
5547
|
+
subscription: executionPolicyMessages.inheritedDefault(subscription)
|
|
4853
5548
|
};
|
|
4854
5549
|
}
|
|
4855
5550
|
function modeRank(mode) {
|
|
@@ -4870,6 +5565,51 @@ function classifyAgents(files, context) {
|
|
|
4870
5565
|
function modelText(classification) {
|
|
4871
5566
|
return classification.model === undefined ? "" : ` (${classification.model.providerID}/${classification.model.modelID})`;
|
|
4872
5567
|
}
|
|
5568
|
+
function asRecord3(value) {
|
|
5569
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
5570
|
+
}
|
|
5571
|
+
function selectionText(value) {
|
|
5572
|
+
if (value === "auto" || value === undefined) {
|
|
5573
|
+
return "auto";
|
|
5574
|
+
}
|
|
5575
|
+
const ref = asRecord3(value);
|
|
5576
|
+
return typeof ref?.providerID === "string" && typeof ref.modelID === "string" ? `${ref.providerID}/${ref.modelID}` : "auto";
|
|
5577
|
+
}
|
|
5578
|
+
function fallbackTexts(value) {
|
|
5579
|
+
if (!Array.isArray(value)) {
|
|
5580
|
+
return [];
|
|
5581
|
+
}
|
|
5582
|
+
return value.map(selectionText).filter((selection) => selection !== "auto");
|
|
5583
|
+
}
|
|
5584
|
+
function executionPolicySummaries(config) {
|
|
5585
|
+
const root = config;
|
|
5586
|
+
const router = asRecord3(root.router);
|
|
5587
|
+
const orchestrator = asRecord3(root.orchestrator);
|
|
5588
|
+
const primary = asRecord3(orchestrator?.primary);
|
|
5589
|
+
const roles = asRecord3(orchestrator?.roles);
|
|
5590
|
+
const globalMode = typeof router?.executionMode === "string" ? router.executionMode : router?.localOnly === true ? "local" : router?.frontierOnly === true ? "frontier" : "mixed";
|
|
5591
|
+
const summaries = [
|
|
5592
|
+
{
|
|
5593
|
+
owner: "primary",
|
|
5594
|
+
executionMode: typeof primary?.executionMode === "string" ? primary.executionMode : globalMode,
|
|
5595
|
+
model: selectionText(primary?.model),
|
|
5596
|
+
fallbacks: fallbackTexts(primary?.fallbacks)
|
|
5597
|
+
}
|
|
5598
|
+
];
|
|
5599
|
+
for (const [roleID, value] of Object.entries(roles ?? {}).sort(([a], [b]) => a.localeCompare(b))) {
|
|
5600
|
+
const policy = asRecord3(value);
|
|
5601
|
+
if (policy === undefined) {
|
|
5602
|
+
continue;
|
|
5603
|
+
}
|
|
5604
|
+
summaries.push({
|
|
5605
|
+
owner: roleID,
|
|
5606
|
+
...typeof policy.executionMode === "string" ? { executionMode: policy.executionMode } : {},
|
|
5607
|
+
model: selectionText(policy.model),
|
|
5608
|
+
fallbacks: fallbackTexts(policy.fallbacks)
|
|
5609
|
+
});
|
|
5610
|
+
}
|
|
5611
|
+
return summaries;
|
|
5612
|
+
}
|
|
4873
5613
|
function renderAgentList(classifications, context) {
|
|
4874
5614
|
const lines = ["openteam agents — LLM per agent:", ""];
|
|
4875
5615
|
if (classifications.length === 0) {
|
|
@@ -4896,6 +5636,15 @@ function renderAgentList(classifications, context) {
|
|
|
4896
5636
|
lines.push("opencode.json default: (not set)");
|
|
4897
5637
|
}
|
|
4898
5638
|
lines.push("Legend: ● primary · ○ subagent · local = no token cost · frontier = uses your subscription.");
|
|
5639
|
+
if (context.executionPolicies !== undefined && context.executionPolicies.length > 0) {
|
|
5640
|
+
lines.push("", executionPolicyMessages.section);
|
|
5641
|
+
for (const policy of context.executionPolicies) {
|
|
5642
|
+
lines.push(executionPolicyMessages.line(policy));
|
|
5643
|
+
}
|
|
5644
|
+
if (context.retryLimits !== undefined) {
|
|
5645
|
+
lines.push(executionPolicyMessages.retryLimits(context.retryLimits.maxRetries, context.retryLimits.maxModelsPerNode));
|
|
5646
|
+
}
|
|
5647
|
+
}
|
|
4899
5648
|
if (context.opencodeConfigError !== undefined) {
|
|
4900
5649
|
lines.push("");
|
|
4901
5650
|
lines.push(`⚠ opencode config parse error: ${context.opencodeConfigError}`);
|
|
@@ -4918,22 +5667,22 @@ var KNOWN_FRONTIER_PROVIDER_IDS = new Set([
|
|
|
4918
5667
|
"openrouter"
|
|
4919
5668
|
]);
|
|
4920
5669
|
var VIRTUAL_ROUTER_PROVIDER_ID = "openteam-router";
|
|
4921
|
-
function
|
|
5670
|
+
function isRecord2(value) {
|
|
4922
5671
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4923
5672
|
}
|
|
4924
5673
|
function parseOpencodeProviders(opencodeConfig) {
|
|
4925
5674
|
const result = new Map;
|
|
4926
5675
|
const provider = opencodeConfig?.provider;
|
|
4927
|
-
if (!
|
|
5676
|
+
if (!isRecord2(provider)) {
|
|
4928
5677
|
return result;
|
|
4929
5678
|
}
|
|
4930
5679
|
for (const [id, value] of Object.entries(provider)) {
|
|
4931
|
-
if (!
|
|
5680
|
+
if (!isRecord2(value)) {
|
|
4932
5681
|
result.set(id, {});
|
|
4933
5682
|
continue;
|
|
4934
5683
|
}
|
|
4935
5684
|
const models = value.models;
|
|
4936
|
-
result.set(id,
|
|
5685
|
+
result.set(id, isRecord2(models) ? { models: new Set(Object.keys(models)) } : {});
|
|
4937
5686
|
}
|
|
4938
5687
|
return result;
|
|
4939
5688
|
}
|
|
@@ -5675,8 +6424,8 @@ function canonical(value) {
|
|
|
5675
6424
|
if (value === null || typeof value !== "object") {
|
|
5676
6425
|
return JSON.stringify(value);
|
|
5677
6426
|
}
|
|
5678
|
-
const
|
|
5679
|
-
const entries = Object.keys(
|
|
6427
|
+
const record2 = value;
|
|
6428
|
+
const entries = Object.keys(record2).sort().map((key) => `${JSON.stringify(key)}:${canonical(record2[key])}`);
|
|
5680
6429
|
return `{${entries.join(",")}}`;
|
|
5681
6430
|
}
|
|
5682
6431
|
function frameDigest(prev, event) {
|
|
@@ -5937,7 +6686,7 @@ function createFsGraphJournal(root) {
|
|
|
5937
6686
|
|
|
5938
6687
|
// src/telemetry/graphProjection.ts
|
|
5939
6688
|
function projectGraphEvent(event) {
|
|
5940
|
-
const
|
|
6689
|
+
const record2 = {
|
|
5941
6690
|
v: event.v,
|
|
5942
6691
|
seq: event.seq,
|
|
5943
6692
|
runID: event.runID,
|
|
@@ -5950,8 +6699,8 @@ function projectGraphEvent(event) {
|
|
|
5950
6699
|
..."specDigest" in event ? { specDigest: event.specDigest } : {},
|
|
5951
6700
|
..."artifact" in event ? { artifact: redactedRef(event.artifact) } : {}
|
|
5952
6701
|
};
|
|
5953
|
-
assertReferenceOnly(
|
|
5954
|
-
return
|
|
6702
|
+
assertReferenceOnly(record2);
|
|
6703
|
+
return record2;
|
|
5955
6704
|
}
|
|
5956
6705
|
|
|
5957
6706
|
// src/telemetry/graphRuntimeHealth.ts
|
|
@@ -6042,18 +6791,18 @@ function indexTelemetry(telemetry) {
|
|
|
6042
6791
|
const lastError = new Map;
|
|
6043
6792
|
const lastFailure = new Map;
|
|
6044
6793
|
const lastArtifact = new Map;
|
|
6045
|
-
for (const
|
|
6046
|
-
if (
|
|
6794
|
+
for (const record2 of telemetry) {
|
|
6795
|
+
if (record2.nodeID === undefined) {
|
|
6047
6796
|
continue;
|
|
6048
6797
|
}
|
|
6049
|
-
if (
|
|
6050
|
-
lastError.set(
|
|
6798
|
+
if (record2.type === "node.failed" && record2.errorClass !== undefined) {
|
|
6799
|
+
lastError.set(record2.nodeID, record2.errorClass);
|
|
6051
6800
|
}
|
|
6052
|
-
if (
|
|
6053
|
-
lastFailure.set(
|
|
6801
|
+
if (record2.type === "node.failed" && record2.failureClass !== undefined) {
|
|
6802
|
+
lastFailure.set(record2.nodeID, record2.failureClass);
|
|
6054
6803
|
}
|
|
6055
|
-
if (
|
|
6056
|
-
lastArtifact.set(
|
|
6804
|
+
if (record2.type === "node.succeeded" && record2.artifact !== undefined) {
|
|
6805
|
+
lastArtifact.set(record2.nodeID, record2.artifact);
|
|
6057
6806
|
}
|
|
6058
6807
|
}
|
|
6059
6808
|
return { lastError, lastFailure, lastArtifact };
|
|
@@ -6244,8 +6993,8 @@ async function runConsoleServe(deps, options) {
|
|
|
6244
6993
|
load: () => Promise.resolve(deps.loadConfig(deps.configPath)),
|
|
6245
6994
|
configPath: deps.configPath
|
|
6246
6995
|
});
|
|
6247
|
-
const auditSink = async (
|
|
6248
|
-
await deps.storage.append(DEFAULT_CONFIG_AUDIT_PATH, `${JSON.stringify(
|
|
6996
|
+
const auditSink = async (record2) => {
|
|
6997
|
+
await deps.storage.append(DEFAULT_CONFIG_AUDIT_PATH, `${JSON.stringify(record2)}
|
|
6249
6998
|
`);
|
|
6250
6999
|
};
|
|
6251
7000
|
const configEditor = createConfigEditor({
|
|
@@ -6338,11 +7087,6 @@ async function runConsoleServe(deps, options) {
|
|
|
6338
7087
|
// src/cli/graphOperator.ts
|
|
6339
7088
|
import { join as join8 } from "node:path";
|
|
6340
7089
|
|
|
6341
|
-
// src/graph/privacyPolicy.ts
|
|
6342
|
-
function frontierAllowed(privacyClass, mode) {
|
|
6343
|
-
return !(privacyClass === "sensitive" && mode === "forceLocalOnSensitive");
|
|
6344
|
-
}
|
|
6345
|
-
|
|
6346
7090
|
// src/graph/langgraph/reducer.ts
|
|
6347
7091
|
function reduceAdapterState(current, update) {
|
|
6348
7092
|
const route = update.route !== undefined ? update.route : current.route;
|
|
@@ -6357,21 +7101,22 @@ function reduceAdapterState(current, update) {
|
|
|
6357
7101
|
|
|
6358
7102
|
// src/graph/langgraph/adapter.ts
|
|
6359
7103
|
var GRAPH_END = "__end__";
|
|
6360
|
-
function resolveNodeRoute(
|
|
6361
|
-
return
|
|
7104
|
+
function resolveNodeRoute(preferredOrPrivacyClass, _legacyMode, legacyPreferred) {
|
|
7105
|
+
return legacyPreferred ?? preferredOrPrivacyClass;
|
|
6362
7106
|
}
|
|
6363
7107
|
var routeNode = (state) => ({
|
|
6364
|
-
route: resolveNodeRoute(state.input.
|
|
7108
|
+
route: resolveNodeRoute(state.input.preferredRoute),
|
|
6365
7109
|
visited: ["route"]
|
|
6366
7110
|
});
|
|
6367
7111
|
var dispatchNode = async (state, deps) => {
|
|
6368
|
-
const route = state.route ?? resolveNodeRoute(state.input.
|
|
6369
|
-
const
|
|
7112
|
+
const route = state.route ?? resolveNodeRoute(state.input.preferredRoute);
|
|
7113
|
+
const request = {
|
|
6370
7114
|
nodeID: state.input.nodeID,
|
|
6371
7115
|
route,
|
|
6372
|
-
privacyClass: state.input.privacyClass,
|
|
6373
|
-
mode: state.input.mode
|
|
6374
|
-
}
|
|
7116
|
+
...state.input.privacyClass === undefined ? {} : { privacyClass: state.input.privacyClass },
|
|
7117
|
+
...state.input.mode === undefined ? {} : { mode: state.input.mode }
|
|
7118
|
+
};
|
|
7119
|
+
const receipt = await deps.executor.execute(request);
|
|
6375
7120
|
return { receipt, visited: ["dispatch"] };
|
|
6376
7121
|
};
|
|
6377
7122
|
function buildAdapterDefinition() {
|
|
@@ -6436,7 +7181,7 @@ function selectCheckpointer(location) {
|
|
|
6436
7181
|
function resolveGraphEngine(config) {
|
|
6437
7182
|
return config.graph?.engine ?? "builtin";
|
|
6438
7183
|
}
|
|
6439
|
-
function createLangGraphEffects(nodeExecutor) {
|
|
7184
|
+
function createLangGraphEffects(nodeExecutor, defaultRoute = "local") {
|
|
6440
7185
|
const definition = buildAdapterDefinition();
|
|
6441
7186
|
return {
|
|
6442
7187
|
async execute(request) {
|
|
@@ -6445,11 +7190,9 @@ function createLangGraphEffects(nodeExecutor) {
|
|
|
6445
7190
|
}
|
|
6446
7191
|
const input = {
|
|
6447
7192
|
nodeID: request.node.id,
|
|
6448
|
-
|
|
6449
|
-
mode: "forceLocalOnSensitive",
|
|
6450
|
-
preferredRoute: "local"
|
|
7193
|
+
preferredRoute: request.routeKind ?? defaultRoute
|
|
6451
7194
|
};
|
|
6452
|
-
const resolvedRoute = resolveNodeRoute(input.
|
|
7195
|
+
const resolvedRoute = resolveNodeRoute(input.preferredRoute);
|
|
6453
7196
|
let state;
|
|
6454
7197
|
try {
|
|
6455
7198
|
state = await invokeStateGraph(definition, input, {
|
|
@@ -6642,8 +7385,8 @@ function createWorkspaceLease(root, registry = createRunRegistry(root)) {
|
|
|
6642
7385
|
return text === undefined ? undefined : parse2(text);
|
|
6643
7386
|
};
|
|
6644
7387
|
const grant = async (runID, holder, token) => {
|
|
6645
|
-
const
|
|
6646
|
-
writeDurable(leasePath(runID), JSON.stringify(
|
|
7388
|
+
const record2 = { runID, holder, token, released: false };
|
|
7389
|
+
writeDurable(leasePath(runID), JSON.stringify(record2));
|
|
6647
7390
|
await registry.markActive(runID);
|
|
6648
7391
|
return { runID, holder, token };
|
|
6649
7392
|
};
|
|
@@ -7374,19 +8117,19 @@ function createWorktreeAdapter(config) {
|
|
|
7374
8117
|
const base = `${canonicalizePath(worktreesDir)}/`;
|
|
7375
8118
|
return canonicalizePath(worktreePath).startsWith(base);
|
|
7376
8119
|
};
|
|
7377
|
-
const toInfo = (
|
|
7378
|
-
runID: runIDFromPath(
|
|
7379
|
-
path:
|
|
7380
|
-
head:
|
|
7381
|
-
detached:
|
|
7382
|
-
locked:
|
|
8120
|
+
const toInfo = (record2) => ({
|
|
8121
|
+
runID: runIDFromPath(record2.path),
|
|
8122
|
+
path: record2.path,
|
|
8123
|
+
head: record2.head,
|
|
8124
|
+
detached: record2.detached,
|
|
8125
|
+
locked: record2.locked
|
|
7383
8126
|
});
|
|
7384
8127
|
const list = async () => {
|
|
7385
8128
|
const output = await runGit(["worktree", "list", "--porcelain"]);
|
|
7386
8129
|
if (output.exitCode !== 0) {
|
|
7387
8130
|
throw new WorktreeError("git-failed", `worktree list: ${output.stderr.trim() || output.exitCode}`);
|
|
7388
8131
|
}
|
|
7389
|
-
return parsePorcelain(output.stdout).filter((
|
|
8132
|
+
return parsePorcelain(output.stdout).filter((record2) => isOurs(record2.path)).map(toInfo);
|
|
7390
8133
|
};
|
|
7391
8134
|
const findExisting = async (expected) => (await list()).find((info) => samePath(info.path, expected));
|
|
7392
8135
|
const addWorktree = async (runID, expected) => runGit([
|
|
@@ -7926,7 +8669,7 @@ function buildOrchestratorAgent(model, options = {}) {
|
|
|
7926
8669
|
];
|
|
7927
8670
|
const frontmatter = [
|
|
7928
8671
|
"---",
|
|
7929
|
-
'description: "openteam multi-agent orchestrator: on every request it checks whether a team exists; if not, it understands the tasks and creates the team of subagents on demand, plans dependencies, parallelizes independent work and keeps the todo list attributed to the owner of each task,
|
|
8672
|
+
'description: "openteam multi-agent orchestrator: on every request it checks whether a team exists; if not, it understands the tasks and creates the team of subagents on demand, plans dependencies, parallelizes independent work and keeps the todo list attributed to the owner of each task, using the configured execution and model policies."',
|
|
7930
8673
|
"mode: primary",
|
|
7931
8674
|
`model: ${modelSpec}`,
|
|
7932
8675
|
"temperature: 0.2",
|
|
@@ -8048,10 +8791,9 @@ function buildOrchestratorAgent(model, options = {}) {
|
|
|
8048
8791
|
" global one, so an `ask`/`deny` in the subagent reintroduces the",
|
|
8049
8792
|
" prompts. If YOLO is disabled, use minimal permissions (leave `bash`",
|
|
8050
8793
|
" in ask mode).",
|
|
8051
|
-
" - Leave `model` unset
|
|
8052
|
-
"
|
|
8053
|
-
"
|
|
8054
|
-
" openteam already routes cheapest-capable automatically.",
|
|
8794
|
+
" - Leave `model` unset in the agent file. Configure its execution domain,",
|
|
8795
|
+
" exact model or `auto`, and ordered fallbacks under `orchestrator.roles`",
|
|
8796
|
+
" in `.opencode/openteam.json`; the runtime policy is authoritative.",
|
|
8055
8797
|
" - A focused, actionable role prompt.",
|
|
8056
8798
|
"4. Register the cast with the `openteam-register-cast` tool and **hand out",
|
|
8057
8799
|
" the tasks** to the newly created team with the `task` tool.",
|
|
@@ -8065,22 +8807,20 @@ function buildOrchestratorAgent(model, options = {}) {
|
|
|
8065
8807
|
"",
|
|
8066
8808
|
"- **scribe** — the team's silent memory. Records decisions and",
|
|
8067
8809
|
" learnings in a shared log (`.opencode/openteam/decisions.md`) without",
|
|
8068
|
-
" executing code changes.
|
|
8069
|
-
"
|
|
8810
|
+
" executing code changes. Use read-only permissions plus editing of that",
|
|
8811
|
+
" log. **Parseable format** for the console: one decision",
|
|
8070
8812
|
" per line as a Markdown list item, already **redacted** (no prompts or",
|
|
8071
|
-
"
|
|
8813
|
+
" credentials):",
|
|
8072
8814
|
" `- YYYY-MM-DD [agent] Short summary of the decision #tag1 #tag2`",
|
|
8073
8815
|
" The date, the `[agent]` and the `#tags` are optional; the summary is",
|
|
8074
8816
|
" required.",
|
|
8075
8817
|
"- **ralph** — automation and triage. Monitors pending work",
|
|
8076
8818
|
" (issues/tasks), prioritizes it, coordinates execution by delegating to the",
|
|
8077
8819
|
" specialists and **escalates to the human** on blockers, risks or approvals.",
|
|
8078
|
-
"- **guardian** — security
|
|
8079
|
-
"
|
|
8080
|
-
"
|
|
8081
|
-
"
|
|
8082
|
-
" lets sensitive data through to frontier. Reviews the inputs and outputs of the",
|
|
8083
|
-
" other agents when they touch sensitive data.",
|
|
8820
|
+
"- **guardian** — an assignable security reviewer. Use it when a task calls",
|
|
8821
|
+
" for threat analysis, exploitability checks or security-focused review.",
|
|
8822
|
+
" It is not an invisible pre-dispatch gate and does not inspect or rewrite",
|
|
8823
|
+
" every other agent's input automatically.",
|
|
8084
8824
|
"",
|
|
8085
8825
|
"## Work planning and parallelization",
|
|
8086
8826
|
"",
|
|
@@ -8100,10 +8840,9 @@ function buildOrchestratorAgent(model, options = {}) {
|
|
|
8100
8840
|
"4. **Do not parallelize work that collides** on the same files or",
|
|
8101
8841
|
" state: those are implicit dependencies; split it into separate waves to",
|
|
8102
8842
|
" avoid write conflicts.",
|
|
8103
|
-
"5. Distribute by **capability
|
|
8104
|
-
"
|
|
8105
|
-
"
|
|
8106
|
-
" parallel: nothing sensitive leaves without passing through it, even with several waves.",
|
|
8843
|
+
"5. Distribute by **capability** and let each configured execution/model",
|
|
8844
|
+
" policy select local, frontier or mixed candidates. Parallelism never",
|
|
8845
|
+
" broadens a role beyond its configured execution domain.",
|
|
8107
8846
|
"",
|
|
8108
8847
|
"Briefly explain your plan to the user (what runs in parallel and what in series, and",
|
|
8109
8848
|
"why) before or while you launch the tasks.",
|
|
@@ -8197,8 +8936,8 @@ function buildOrchestratorAgent(model, options = {}) {
|
|
|
8197
8936
|
"- Create subagents **exactly when the work calls for it**, never “just in case”.",
|
|
8198
8937
|
` Review \`.opencode/agent/\` and the \`${OPENTEAM_ROSTER_FILENAME}\`, and reuse the`,
|
|
8199
8938
|
" existing ones before creating a new one.",
|
|
8200
|
-
"- Respect openteam's
|
|
8201
|
-
"
|
|
8939
|
+
"- Respect openteam's configured execution domains, exact pins and ordered",
|
|
8940
|
+
" fallback chains. Assign `guardian` explicitly when security review is needed.",
|
|
8202
8941
|
"- Summarize for the user which universe you chose, which team you created and why.",
|
|
8203
8942
|
"",
|
|
8204
8943
|
"## Limits",
|
|
@@ -8267,8 +9006,8 @@ function buildOpenteamCommands() {
|
|
|
8267
9006
|
return [
|
|
8268
9007
|
file("openteam", buildOpenteamCommand()),
|
|
8269
9008
|
file("openteam-baseline", buildBaselineCommand()),
|
|
8270
|
-
file("openteam-doctor", renderCommand("openteam · diagnostics for
|
|
8271
|
-
file("openteam-agents", renderCommand("openteam · list
|
|
9009
|
+
file("openteam-doctor", renderCommand("openteam · diagnostics for runtimes, execution policies, and configuration", fixedActionBody("doctor"))),
|
|
9010
|
+
file("openteam-agents", renderCommand("openteam · list agents, model identity, execution policy, and subscription classification", fixedActionBody("agents"))),
|
|
8272
9011
|
file("openteam-console", renderCommand("openteam · status and URL of the multi-session web Console", [
|
|
8273
9012
|
"> ℹ️ The Console interface may still evolve between releases.",
|
|
8274
9013
|
"",
|
|
@@ -8485,7 +9224,7 @@ var CURATED_FRONTIER_PROFILES = [
|
|
|
8485
9224
|
];
|
|
8486
9225
|
|
|
8487
9226
|
// src/capabilities/normalize.ts
|
|
8488
|
-
function
|
|
9227
|
+
function isRecord3(value) {
|
|
8489
9228
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8490
9229
|
}
|
|
8491
9230
|
function stringValue(value) {
|
|
@@ -8506,7 +9245,7 @@ function boolValue(value) {
|
|
|
8506
9245
|
}
|
|
8507
9246
|
function nestedRecord(value, key) {
|
|
8508
9247
|
const nested = value[key];
|
|
8509
|
-
return
|
|
9248
|
+
return isRecord3(nested) ? nested : undefined;
|
|
8510
9249
|
}
|
|
8511
9250
|
function nestedBoolean(value, firstKey, secondKey) {
|
|
8512
9251
|
const nested = nestedRecord(value, firstKey);
|
|
@@ -8519,7 +9258,7 @@ function includesImage(value) {
|
|
|
8519
9258
|
if (Array.isArray(value)) {
|
|
8520
9259
|
return value.some(includesImage);
|
|
8521
9260
|
}
|
|
8522
|
-
if (
|
|
9261
|
+
if (isRecord3(value)) {
|
|
8523
9262
|
return Object.values(value).some(includesImage);
|
|
8524
9263
|
}
|
|
8525
9264
|
return false;
|
|
@@ -8538,7 +9277,7 @@ function tierFromModelName(modelID, reasoning) {
|
|
|
8538
9277
|
if (typeof reasoning === "number") {
|
|
8539
9278
|
return Math.max(0, Math.min(5, Math.trunc(reasoning)));
|
|
8540
9279
|
}
|
|
8541
|
-
if (reasoning === true ||
|
|
9280
|
+
if (reasoning === true || isRecord3(reasoning)) {
|
|
8542
9281
|
return normalized.includes("flash") || normalized.includes("mini") ? 3 : 4;
|
|
8543
9282
|
}
|
|
8544
9283
|
if (normalized.includes("opus") || normalized.includes("codex")) {
|
|
@@ -8568,31 +9307,44 @@ function codeTierFromModelName(modelID) {
|
|
|
8568
9307
|
function availability(value) {
|
|
8569
9308
|
return value === "degraded" || value === "unavailable" ? value : "available";
|
|
8570
9309
|
}
|
|
9310
|
+
function modelAvailability(availabilityValue, status) {
|
|
9311
|
+
if (availabilityValue === "degraded" || status === "deprecated") {
|
|
9312
|
+
return "degraded";
|
|
9313
|
+
}
|
|
9314
|
+
return availability(availabilityValue);
|
|
9315
|
+
}
|
|
8571
9316
|
function modelEntriesFromProvider(providerID, provider) {
|
|
8572
9317
|
const models = provider.models;
|
|
8573
9318
|
if (Array.isArray(models)) {
|
|
8574
|
-
return models.filter(
|
|
8575
|
-
providerID,
|
|
9319
|
+
return models.filter(isRecord3).map((model) => ({
|
|
9320
|
+
providerID: stringValue(model.providerID) ?? providerID,
|
|
8576
9321
|
modelID: stringValue(model.id) ?? stringValue(model.modelID) ?? "",
|
|
8577
9322
|
value: model
|
|
8578
9323
|
})).filter((entry) => entry.modelID.length > 0);
|
|
8579
9324
|
}
|
|
8580
|
-
if (
|
|
8581
|
-
return Object.entries(models).
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
9325
|
+
if (isRecord3(models)) {
|
|
9326
|
+
return Object.entries(models).flatMap(([modelID, model]) => {
|
|
9327
|
+
if (!isRecord3(model)) {
|
|
9328
|
+
return [];
|
|
9329
|
+
}
|
|
9330
|
+
return [
|
|
9331
|
+
{
|
|
9332
|
+
providerID: stringValue(model.providerID) ?? providerID,
|
|
9333
|
+
modelID: stringValue(model.id) ?? stringValue(model.modelID) ?? modelID,
|
|
9334
|
+
value: model
|
|
9335
|
+
}
|
|
9336
|
+
];
|
|
9337
|
+
});
|
|
8586
9338
|
}
|
|
8587
9339
|
return [];
|
|
8588
9340
|
}
|
|
8589
9341
|
function catalogEntries(raw) {
|
|
8590
|
-
if (!
|
|
9342
|
+
if (!isRecord3(raw)) {
|
|
8591
9343
|
return [];
|
|
8592
9344
|
}
|
|
8593
|
-
const providers =
|
|
9345
|
+
const providers = isRecord3(raw.providers) ? raw.providers : raw;
|
|
8594
9346
|
return Object.entries(providers).flatMap(([providerKey, provider]) => {
|
|
8595
|
-
if (!
|
|
9347
|
+
if (!isRecord3(provider)) {
|
|
8596
9348
|
return [];
|
|
8597
9349
|
}
|
|
8598
9350
|
const providerID = stringValue(provider.id) ?? stringValue(provider.providerID) ?? providerKey;
|
|
@@ -8601,6 +9353,9 @@ function catalogEntries(raw) {
|
|
|
8601
9353
|
}
|
|
8602
9354
|
function normalizeEntry(entry) {
|
|
8603
9355
|
const limit = nestedRecord(entry.value, "limit");
|
|
9356
|
+
const capabilities = nestedRecord(entry.value, "capabilities");
|
|
9357
|
+
const capabilityName = stringValue(entry.value.name) ?? entry.modelID;
|
|
9358
|
+
const inputCapabilities = capabilities === undefined ? undefined : nestedRecord(capabilities, "input");
|
|
8604
9359
|
const contextWindow = numberValue(entry.value.contextWindow) ?? numberValue(entry.value.context_window) ?? (limit === undefined ? undefined : numberValue(limit.context));
|
|
8605
9360
|
const maxOutputTokens = numberValue(entry.value.maxOutputTokens) ?? numberValue(entry.value.max_output_tokens) ?? (limit === undefined ? undefined : numberValue(limit.output));
|
|
8606
9361
|
const costPer1M = costPair(entry.value);
|
|
@@ -8612,12 +9367,12 @@ function normalizeEntry(entry) {
|
|
|
8612
9367
|
kind: "frontier",
|
|
8613
9368
|
contextWindow,
|
|
8614
9369
|
maxOutputTokens,
|
|
8615
|
-
supportsToolCalling: boolValue(entry.value.tool_call) || boolValue(entry.value.toolCall) || nestedBoolean(entry.value, "features", "tool_call"),
|
|
8616
|
-
supportsVision: boolValue(entry.value.vision) || includesImage(entry.value.modalities) || includesImage(entry.value.input_modalities),
|
|
8617
|
-
reasoningTier: tierFromModelName(
|
|
8618
|
-
codeQualityTier: codeTierFromModelName(
|
|
9370
|
+
supportsToolCalling: boolValue(entry.value.tool_call) || boolValue(entry.value.toolCall) || boolValue(capabilities?.toolcall) || nestedBoolean(entry.value, "features", "tool_call"),
|
|
9371
|
+
supportsVision: boolValue(entry.value.vision) || boolValue(inputCapabilities?.image) || includesImage(entry.value.modalities) || includesImage(entry.value.input_modalities),
|
|
9372
|
+
reasoningTier: tierFromModelName(capabilityName, entry.value.reasoning ?? capabilities?.reasoning),
|
|
9373
|
+
codeQualityTier: codeTierFromModelName(capabilityName),
|
|
8619
9374
|
costPer1M,
|
|
8620
|
-
availability:
|
|
9375
|
+
availability: modelAvailability(entry.value.availability, entry.value.status)
|
|
8621
9376
|
};
|
|
8622
9377
|
const parsed = ModelCapabilityProfileSchema.safeParse(profile);
|
|
8623
9378
|
return parsed.success ? parsed.data : undefined;
|
|
@@ -9454,123 +10209,6 @@ function isGateApplicable(mode) {
|
|
|
9454
10209
|
return mode === "active";
|
|
9455
10210
|
}
|
|
9456
10211
|
|
|
9457
|
-
// src/messages/commands.ts
|
|
9458
|
-
var clearCacheMessages = {
|
|
9459
|
-
header: "openteam clear-cache — frozen plugin cache entries:",
|
|
9460
|
-
columns: {
|
|
9461
|
-
specDir: "spec dir",
|
|
9462
|
-
pinned: "spec-pinned",
|
|
9463
|
-
installed: "installed",
|
|
9464
|
-
mtime: "mtime"
|
|
9465
|
-
},
|
|
9466
|
-
reparseSkipSuffix: " [SKIP — reparse point]",
|
|
9467
|
-
deletedLabel: "deleted.",
|
|
9468
|
-
lockedLabel: (message) => `[LOCKED] ${message}`,
|
|
9469
|
-
pathOutsideWarning: (specDir, absolutePath) => ` ⚠ ${specDir}: path outside cacheRoot (${absolutePath}), skipped.`,
|
|
9470
|
-
processed: (count) => `${count} entry(ies) processed.`,
|
|
9471
|
-
found: (count) => `${count} entry(ies) found. Use --delete to remove them.`
|
|
9472
|
-
};
|
|
9473
|
-
var baselineMessages = {
|
|
9474
|
-
effectiveAuto: "cheapest-capable (auto)",
|
|
9475
|
-
pinnedSuffix: (ref) => `${ref} (pinned)`,
|
|
9476
|
-
summary: (params) => [
|
|
9477
|
-
"openteam baseline:",
|
|
9478
|
-
` mode: ${params.mode}`,
|
|
9479
|
-
` pinned: ${params.pinned}`,
|
|
9480
|
-
` hardDefault: ${params.hardDefault}`,
|
|
9481
|
-
` effective: ${params.effective}`
|
|
9482
|
-
],
|
|
9483
|
-
invalidModel: (input) => `Invalid model "${input}". Use the provider/model format, e.g. anthropic/claude-sonnet-4-5.`,
|
|
9484
|
-
pinnedTo: (ref) => `Baseline pinned to ${ref} (pinned mode).`,
|
|
9485
|
-
autoMode: "Baseline set to auto mode (cheapest-capable)."
|
|
9486
|
-
};
|
|
9487
|
-
var localMessages = {
|
|
9488
|
-
help: {
|
|
9489
|
-
status: " openteam local status Show the routing mode (local-first / local-only / frontier-only)",
|
|
9490
|
-
off: " openteam local off Use frontier models only (no local runtime)",
|
|
9491
|
-
only: " openteam local only Use local models only (never frontier)",
|
|
9492
|
-
on: " openteam local on Re-enable local-first routing"
|
|
9493
|
-
},
|
|
9494
|
-
noRuntimes: "none",
|
|
9495
|
-
modeFrontierOnly: "frontier-only (frontierOnly)",
|
|
9496
|
-
modeLocalOnly: "local-only (localOnly)",
|
|
9497
|
-
modeLocalFirst: "local-first",
|
|
9498
|
-
summary: (params) => [
|
|
9499
|
-
"openteam local:",
|
|
9500
|
-
` mode: ${params.mode}`,
|
|
9501
|
-
` frontierOnly: ${params.frontierOnly}`,
|
|
9502
|
-
` localOnly: ${params.localOnly}`,
|
|
9503
|
-
` privacy: ${params.privacy}`,
|
|
9504
|
-
` runtimes: ${params.runtimes}`
|
|
9505
|
-
],
|
|
9506
|
-
frontierOnlyNoChange: "No change: already in frontier-only mode.",
|
|
9507
|
-
privacyAdjustedNote: " Privacy adjusted to 'consentBeforeFrontier' (forceLocalOnSensitive requires a local runtime).",
|
|
9508
|
-
frontierOnlyEnabled: "Frontier-only mode enabled (frontierOnly=true, localOnly=false).",
|
|
9509
|
-
localOnlyNoRuntimes: "Cannot enable local-only mode: no enabled local runtime is configured. Run 'openteam setup' or enable a local runtime in .opencode/openteam.json first.",
|
|
9510
|
-
localOnlyNoChange: "No change: already in local-only mode.",
|
|
9511
|
-
localOnlyEnabled: "Local-only mode enabled (localOnly=true, frontierOnly=false).",
|
|
9512
|
-
localFirstNoChange: "No change: local-first routing is already active.",
|
|
9513
|
-
localFirstReEnabled: "Local-first routing re-enabled (frontierOnly=false, localOnly=false). Make sure a local runtime is reachable.",
|
|
9514
|
-
unknownSubcommand: (subcommand, help) => `Unknown local subcommand: ${subcommand}
|
|
9515
|
-
|
|
9516
|
-
${help}`
|
|
9517
|
-
};
|
|
9518
|
-
var frontierCatalogMessages = {
|
|
9519
|
-
free: "free",
|
|
9520
|
-
costPerMillion: (inputUSD, outputUSD) => `$${inputUSD}/$${outputUSD} per 1M`
|
|
9521
|
-
};
|
|
9522
|
-
var migrateMessages = {
|
|
9523
|
-
header: "openteam migrate:",
|
|
9524
|
-
nothingToMigrate: " nothing to migrate — the P15b layout is already in place.",
|
|
9525
|
-
counts: (params) => [
|
|
9526
|
-
` moved: ${params.moved} file(s)`,
|
|
9527
|
-
` deduped: ${params.deduped} file(s) (destination already identical)`,
|
|
9528
|
-
` conflicts: ${params.conflicts} file(s)`
|
|
9529
|
-
],
|
|
9530
|
-
relocatedHeader: " relocated:",
|
|
9531
|
-
relocatedEntry: (from, to, deduped) => ` · ${from} → ${to}${deduped ? " (deduped)" : ""}`,
|
|
9532
|
-
conflictsHeader: " ⚠ left in place (destination exists with different content — no data lost):",
|
|
9533
|
-
conflictEntry: (from, to) => ` · ${from} → ${to}`,
|
|
9534
|
-
manualWorktreesHeader: " ⚠ legacy git worktree(s) need a manual move (run `git worktree move`):",
|
|
9535
|
-
manualWorktreeEntry: (label, from, to) => ` · ${label}: ${from} → ${to}`,
|
|
9536
|
-
success: " ✓ machine-local runtime output relocated under .opencode/openteam-local/."
|
|
9537
|
-
};
|
|
9538
|
-
var rolesInitMessages = {
|
|
9539
|
-
header: "openteam roles init:",
|
|
9540
|
-
title: "openteam roles init",
|
|
9541
|
-
noRoster: (path) => ` ✗ no roster at ${path}.`,
|
|
9542
|
-
noRosterRemedy: " Cast the team first: run `openteam setup`, then ask the orchestrator to register the cast.",
|
|
9543
|
-
unparseableRoster: (path, error) => ` ✗ ${path} is present but unparseable: ${error}`,
|
|
9544
|
-
unparseableRosterRemedy: " Run `openteam doctor` for the remedy, then re-run this command.",
|
|
9545
|
-
allProfiled: (count) => ` ✓ ${count} roster role(s) checked; no unprofiled roles — every role resolves a routing profile.`,
|
|
9546
|
-
whyTitle: "Why you are being asked",
|
|
9547
|
-
why: (count) => [
|
|
9548
|
-
`${count} roster role(s) resolve no routing profile.`,
|
|
9549
|
-
"Until a profile exists they are frontier-eligible: their work can escalate",
|
|
9550
|
-
"to a frontier model. A 'local only' note in the roster prose does not",
|
|
9551
|
-
"prevent this — prose outside the JSON fence is never read."
|
|
9552
|
-
].join(`
|
|
9553
|
-
`),
|
|
9554
|
-
policyQuestion: (roleID, agentName) => `Routing policy for "${roleID}" (${agentName})`,
|
|
9555
|
-
localOnlyLabel: (roleID, agentName) => `Local only — ${roleID} (${agentName}) never leaves this machine`,
|
|
9556
|
-
localOnlyHint: "sets requiresLocalRuntime; the role fails closed when no local runtime is reachable",
|
|
9557
|
-
frontierOkLabel: "Local first, frontier allowed",
|
|
9558
|
-
frontierOkHint: "records today's implicit behaviour: cheapest-capable frontier when the task warrants it",
|
|
9559
|
-
skipLabel: "Skip for now",
|
|
9560
|
-
skipHint: "leaves the role unprofiled, so it stays frontier-eligible and doctor keeps reporting it",
|
|
9561
|
-
runtimeQuestion: (roleID) => `Preferred local runtime(s) for "${roleID}" (in failover order)`,
|
|
9562
|
-
runtimeHint: "machine-local — written to the git-ignored overlay",
|
|
9563
|
-
cancelled: (reason) => ` ✗ cancelled (${reason}); nothing was written.`,
|
|
9564
|
-
invalidResult: (reason) => ` ✗ refusing to write: the resulting config is invalid — ${reason}`,
|
|
9565
|
-
nothingWritten: (skipped) => ` · skipped ${skipped}; nothing written.`,
|
|
9566
|
-
nothingToWriteOutro: "Nothing to write.",
|
|
9567
|
-
pinnedLocal: (roleIDs, path) => ` ✓ pinned local-only: ${roleIDs} → ${path} (tracked, travels with the roster)`,
|
|
9568
|
-
frontierAllowed: (roleIDs, path) => ` ✓ frontier allowed: ${roleIDs} → ${path} (model choice stays "auto": cheapest-capable per task)`,
|
|
9569
|
-
runtimeBinding: (path) => ` ✓ runtime binding: ${path} (git-ignored, machine-local)`,
|
|
9570
|
-
skippedSummary: (roleIDs) => ` · skipped: ${roleIDs} — still frontier-eligible; re-run this command to revisit.`,
|
|
9571
|
-
outro: "Role policy updated."
|
|
9572
|
-
};
|
|
9573
|
-
|
|
9574
10212
|
// src/orchestrator/worktreeReconciler.ts
|
|
9575
10213
|
function planOne(wt) {
|
|
9576
10214
|
const base = { runID: wt.runID, path: wt.path };
|
|
@@ -10126,8 +10764,7 @@ function rosterHealthSection(audit, loadError, rosterRoleCount, rosterMissing) {
|
|
|
10126
10764
|
if (unprofiled.length > 0) {
|
|
10127
10765
|
const roleIDs = unprofiled.map((entry) => entry.roleID).join(", ");
|
|
10128
10766
|
lines.push(` · unprofiled roleID(s): ${roleIDs}`);
|
|
10129
|
-
lines.push(
|
|
10130
|
-
lines.push(' remedy: run `openteam roles init` to compose a profile interactively, or add one by hand under orchestrator.roles in .opencode/openteam.json with "requiresLocalRuntime": true to pin the role local; for curated routing, change roleID to a TEAM_ROLES key instead.');
|
|
10767
|
+
lines.push(executionPolicyMessages.unprofiledRoleRouting, executionPolicyMessages.unprofiledRoleRemedy);
|
|
10131
10768
|
}
|
|
10132
10769
|
return lines;
|
|
10133
10770
|
}
|
|
@@ -10169,10 +10806,7 @@ function agentModelsSection(diagnostics, searchedPaths, localOnly) {
|
|
|
10169
10806
|
}
|
|
10170
10807
|
const localOnlyGap = localOnly ? diagnostics.find((d) => d.mode === "primary" && d.resolution?.status === "unresolvable" && d.resolution.reason === "provider-not-configured") : undefined;
|
|
10171
10808
|
if (localOnlyGap?.model !== undefined) {
|
|
10172
|
-
lines.push(
|
|
10173
|
-
lines.push(" cause: `openteam setup` recorded the frontier baseline placeholder as this agent's `model:`. That value is correctly inert as baseline.hardDefault while router.localOnly is true, but an agent's own `model:` is load-bearing, so opencode cannot resolve the coordinator against your local-only providers.");
|
|
10174
|
-
lines.push(" consequence: opencode cannot start the openteam coordinator, so it reads no roster and splits no work — even though every subagent resolves on your local runtime.");
|
|
10175
|
-
lines.push(` remedy: re-run \`openteam setup\` and keep local-only mode to regenerate ${localOnlyGap.sourceFile} with your local thinking model, then re-run \`openteam doctor\`.`);
|
|
10809
|
+
lines.push(executionPolicyMessages.primaryLocalMismatch(localOnlyGap.name, localOnlyGap.model.providerID), executionPolicyMessages.primaryLocalMismatchCause, executionPolicyMessages.primaryLocalMismatchConsequence, executionPolicyMessages.primaryLocalMismatchRemedy(localOnlyGap.sourceFile));
|
|
10176
10810
|
}
|
|
10177
10811
|
lines.push(" note: 'declared' = model set in .opencode/agent/<name>.md frontmatter; 'inherited' = opencode.json default.");
|
|
10178
10812
|
lines.push(" note: static checks are offline and deterministic; live checks need a reachable local runtime and can be inconclusive.");
|
|
@@ -10181,10 +10815,16 @@ function agentModelsSection(diagnostics, searchedPaths, localOnly) {
|
|
|
10181
10815
|
function renderDoctor(input) {
|
|
10182
10816
|
const enabledRuntimes = input.config.local.runtimes.filter((r) => r.enabled);
|
|
10183
10817
|
const reachable = input.snapshots.filter((s) => s.reachable).length;
|
|
10818
|
+
const configRecord = input.config;
|
|
10819
|
+
const routerRecord = typeof configRecord.router === "object" && configRecord.router !== null && !Array.isArray(configRecord.router) ? configRecord.router : {};
|
|
10820
|
+
const executionMode = typeof routerRecord.executionMode === "string" ? routerRecord.executionMode : routerRecord.localOnly === true ? "local" : routerRecord.frontierOnly === true ? "frontier" : "mixed";
|
|
10821
|
+
const orchestratorRecord = typeof configRecord.orchestrator === "object" && configRecord.orchestrator !== null && !Array.isArray(configRecord.orchestrator) ? configRecord.orchestrator : {};
|
|
10822
|
+
const maxRetries = typeof orchestratorRecord.maxRetries === "number" ? orchestratorRecord.maxRetries : 2;
|
|
10823
|
+
const maxModelsPerNode = typeof orchestratorRecord.maxModelsPerNode === "number" ? orchestratorRecord.maxModelsPerNode : 2;
|
|
10184
10824
|
const lines = [
|
|
10185
10825
|
"openteam doctor:",
|
|
10186
10826
|
` baseline: ${input.config.baseline.mode}` + (input.config.baseline.mode === "pinned" && input.config.baseline.pinnedModel !== null ? ` (${input.config.baseline.pinnedModel.providerID}/${input.config.baseline.pinnedModel.modelID})` : ""),
|
|
10187
|
-
|
|
10827
|
+
executionPolicyMessages.globalMode(executionMode),
|
|
10188
10828
|
` local runtimes (${reachable}/${enabledRuntimes.length} reachable):`
|
|
10189
10829
|
];
|
|
10190
10830
|
if (input.snapshots.length === 0) {
|
|
@@ -10212,6 +10852,11 @@ function renderDoctor(input) {
|
|
|
10212
10852
|
lines.push(` ⚠ ${snapshot.id} is reached over the network: its ${cap}-slot cap is enforced per openteam process, so`, " two processes at once (an opencode session plus 'openteam console') can together exceed it.");
|
|
10213
10853
|
}
|
|
10214
10854
|
lines.push(` telemetry: ${input.telemetryPath} — ${input.telemetryRecords} record(s)`);
|
|
10855
|
+
lines.push(executionPolicyMessages.section);
|
|
10856
|
+
for (const policy of executionPolicySummaries(input.config)) {
|
|
10857
|
+
lines.push(executionPolicyMessages.line(policy));
|
|
10858
|
+
}
|
|
10859
|
+
lines.push(executionPolicyMessages.retryLimits(maxRetries, maxModelsPerNode));
|
|
10215
10860
|
if (input.queueWait !== undefined) {
|
|
10216
10861
|
lines.push(...queueWaitSection(input.queueWait));
|
|
10217
10862
|
}
|
|
@@ -10239,8 +10884,10 @@ function renderDoctor(input) {
|
|
|
10239
10884
|
lines.push(" console:");
|
|
10240
10885
|
lines.push(" command: openteam console");
|
|
10241
10886
|
lines.push(" launches: the multi-session web Console (status and URL)");
|
|
10242
|
-
if (reachable === 0) {
|
|
10243
|
-
lines.push(
|
|
10887
|
+
if (reachable === 0 && executionMode === "mixed") {
|
|
10888
|
+
lines.push(executionPolicyMessages.noLocalMixed);
|
|
10889
|
+
} else if (reachable === 0 && executionMode === "local") {
|
|
10890
|
+
lines.push(executionPolicyMessages.noLocalBlocked);
|
|
10244
10891
|
}
|
|
10245
10892
|
if (input.graphHealth !== undefined) {
|
|
10246
10893
|
const h = input.graphHealth;
|
|
@@ -10262,7 +10909,7 @@ function renderDoctor(input) {
|
|
|
10262
10909
|
lines.push(" Run `openteam clear-cache --delete` to clean up.");
|
|
10263
10910
|
}
|
|
10264
10911
|
if (input.agentModels !== undefined) {
|
|
10265
|
-
lines.push(...agentModelsSection(input.agentModels, input.opencodeConfigPaths ?? [],
|
|
10912
|
+
lines.push(...agentModelsSection(input.agentModels, input.opencodeConfigPaths ?? [], executionMode === "local"));
|
|
10266
10913
|
}
|
|
10267
10914
|
if (input.opencodeConfigError !== undefined) {
|
|
10268
10915
|
lines.push(` ⚠ opencode config parse error: ${input.opencodeConfigError}`);
|
|
@@ -10387,6 +11034,47 @@ function renderWorktreeReconciliation(report) {
|
|
|
10387
11034
|
function hasEnabledLocalRuntime(config) {
|
|
10388
11035
|
return config.local.runtimes.some((runtime) => runtime.enabled);
|
|
10389
11036
|
}
|
|
11037
|
+
function executionMode(config) {
|
|
11038
|
+
const router = config.router;
|
|
11039
|
+
if (router.executionMode !== undefined) {
|
|
11040
|
+
return router.executionMode;
|
|
11041
|
+
}
|
|
11042
|
+
if (router.localOnly === true) {
|
|
11043
|
+
return "local";
|
|
11044
|
+
}
|
|
11045
|
+
if (router.frontierOnly === true) {
|
|
11046
|
+
return "frontier";
|
|
11047
|
+
}
|
|
11048
|
+
return "mixed";
|
|
11049
|
+
}
|
|
11050
|
+
function withExecutionMode(config, mode) {
|
|
11051
|
+
const {
|
|
11052
|
+
privacyMode: _legacyPrivacyMode,
|
|
11053
|
+
router: currentRouter,
|
|
11054
|
+
...configWithoutLegacyPrivacy
|
|
11055
|
+
} = config;
|
|
11056
|
+
const {
|
|
11057
|
+
frontierOnly: _legacyFrontierOnly,
|
|
11058
|
+
localOnly: _legacyLocalOnly,
|
|
11059
|
+
...currentRouterWithoutLegacyModes
|
|
11060
|
+
} = currentRouter;
|
|
11061
|
+
const candidate = {
|
|
11062
|
+
...configWithoutLegacyPrivacy,
|
|
11063
|
+
router: {
|
|
11064
|
+
...currentRouterWithoutLegacyModes,
|
|
11065
|
+
executionMode: mode
|
|
11066
|
+
}
|
|
11067
|
+
};
|
|
11068
|
+
const parsed = OpenTeamConfigSchema.safeParse(candidate);
|
|
11069
|
+
if (!parsed.success) {
|
|
11070
|
+
throw new Error(formatConfigError(parsed.error));
|
|
11071
|
+
}
|
|
11072
|
+
assertEffectiveConfigModelDomains(parsed.data);
|
|
11073
|
+
return parsed.data;
|
|
11074
|
+
}
|
|
11075
|
+
function requiresExecutionMigration(config) {
|
|
11076
|
+
return config.router.localOnly !== undefined || config.router.frontierOnly !== undefined || config.privacyMode !== undefined;
|
|
11077
|
+
}
|
|
10390
11078
|
function enabledRuntimesSummary(config) {
|
|
10391
11079
|
const enabled = config.local.runtimes.filter((runtime) => runtime.enabled);
|
|
10392
11080
|
if (enabled.length === 0) {
|
|
@@ -10399,59 +11087,44 @@ function enabledRuntimesSummary(config) {
|
|
|
10399
11087
|
}).join(", ");
|
|
10400
11088
|
}
|
|
10401
11089
|
function showLocal(config) {
|
|
10402
|
-
const mode = config
|
|
11090
|
+
const mode = executionMode(config);
|
|
11091
|
+
const modeLabel = mode === "local" ? localMessages.modeLocal : mode === "frontier" ? localMessages.modeFrontier : localMessages.modeMixed;
|
|
10403
11092
|
const lines = localMessages.summary({
|
|
10404
|
-
mode,
|
|
10405
|
-
frontierOnly: config.router.frontierOnly,
|
|
10406
|
-
localOnly: config.router.localOnly,
|
|
10407
|
-
privacy: config.privacyMode,
|
|
11093
|
+
mode: modeLabel,
|
|
10408
11094
|
runtimes: enabledRuntimesSummary(config)
|
|
10409
11095
|
});
|
|
10410
11096
|
return { message: lines.join(`
|
|
10411
11097
|
`) };
|
|
10412
11098
|
}
|
|
10413
11099
|
function setFrontierOnly(config) {
|
|
10414
|
-
if (config
|
|
11100
|
+
if (executionMode(config) === "frontier" && !requiresExecutionMigration(config)) {
|
|
11101
|
+
assertEffectiveConfigModelDomains(config);
|
|
10415
11102
|
return { message: localMessages.frontierOnlyNoChange };
|
|
10416
11103
|
}
|
|
10417
|
-
const flipsPrivacy = config.privacyMode === "forceLocalOnSensitive";
|
|
10418
|
-
const next = {
|
|
10419
|
-
...config,
|
|
10420
|
-
router: { ...config.router, frontierOnly: true, localOnly: false },
|
|
10421
|
-
privacyMode: flipsPrivacy ? "consentBeforeFrontier" : config.privacyMode
|
|
10422
|
-
};
|
|
10423
|
-
const privacyNote = flipsPrivacy ? localMessages.privacyAdjustedNote : "";
|
|
10424
11104
|
return {
|
|
10425
|
-
config:
|
|
10426
|
-
message:
|
|
11105
|
+
config: withExecutionMode(config, "frontier"),
|
|
11106
|
+
message: localMessages.frontierOnlyEnabled
|
|
10427
11107
|
};
|
|
10428
11108
|
}
|
|
10429
11109
|
function setLocalOnly(config) {
|
|
10430
11110
|
if (!hasEnabledLocalRuntime(config)) {
|
|
10431
11111
|
return { message: localMessages.localOnlyNoRuntimes };
|
|
10432
11112
|
}
|
|
10433
|
-
if (config
|
|
11113
|
+
if (executionMode(config) === "local" && !requiresExecutionMigration(config)) {
|
|
11114
|
+
assertEffectiveConfigModelDomains(config);
|
|
10434
11115
|
return { message: localMessages.localOnlyNoChange };
|
|
10435
11116
|
}
|
|
10436
|
-
const next = {
|
|
10437
|
-
...config,
|
|
10438
|
-
router: { ...config.router, frontierOnly: false, localOnly: true }
|
|
10439
|
-
};
|
|
10440
11117
|
return {
|
|
10441
|
-
config:
|
|
11118
|
+
config: withExecutionMode(config, "local"),
|
|
10442
11119
|
message: localMessages.localOnlyEnabled
|
|
10443
11120
|
};
|
|
10444
11121
|
}
|
|
10445
11122
|
function setLocalFirst(config) {
|
|
10446
|
-
if (
|
|
11123
|
+
if (executionMode(config) === "mixed" && !requiresExecutionMigration(config)) {
|
|
10447
11124
|
return { message: localMessages.localFirstNoChange };
|
|
10448
11125
|
}
|
|
10449
|
-
const next = {
|
|
10450
|
-
...config,
|
|
10451
|
-
router: { ...config.router, frontierOnly: false, localOnly: false }
|
|
10452
|
-
};
|
|
10453
11126
|
return {
|
|
10454
|
-
config:
|
|
11127
|
+
config: withExecutionMode(config, "mixed"),
|
|
10455
11128
|
message: localMessages.localFirstReEnabled
|
|
10456
11129
|
};
|
|
10457
11130
|
}
|
|
@@ -10552,7 +11225,7 @@ function stripJsoncComments(src) {
|
|
|
10552
11225
|
}
|
|
10553
11226
|
return out.join("");
|
|
10554
11227
|
}
|
|
10555
|
-
function
|
|
11228
|
+
function isRecord4(v) {
|
|
10556
11229
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
10557
11230
|
}
|
|
10558
11231
|
var OPENCODE_CONFIG_CANDIDATES = [
|
|
@@ -10563,7 +11236,7 @@ function mergeOpencodeConfigs(base, override) {
|
|
|
10563
11236
|
const result = { ...base };
|
|
10564
11237
|
for (const [key, value] of Object.entries(override)) {
|
|
10565
11238
|
const baseVal = result[key];
|
|
10566
|
-
if (
|
|
11239
|
+
if (isRecord4(baseVal) && isRecord4(value)) {
|
|
10567
11240
|
result[key] = mergeOpencodeConfigs(baseVal, value);
|
|
10568
11241
|
} else {
|
|
10569
11242
|
result[key] = value;
|
|
@@ -10764,17 +11437,17 @@ var GENERATED_OPENTEAM_STATE_GROUPS = [
|
|
|
10764
11437
|
var OPENCODE_DIR = ".opencode";
|
|
10765
11438
|
var AGENT_DIR = `${OPENCODE_DIR}/agent`;
|
|
10766
11439
|
var ROLE_AGENT_DESCRIPTIONS = {
|
|
10767
|
-
architect: "openteam architect subagent: high-level design, cross-cutting refactors and hard technical decisions
|
|
11440
|
+
architect: "openteam architect subagent: high-level design, cross-cutting refactors and hard technical decisions.",
|
|
10768
11441
|
integration: "openteam integration subagent: wires components together, owns end-to-end flows and cross-module glue.",
|
|
10769
|
-
"local-runtime": "openteam local-runtime subagent: local model operation and runtime health
|
|
11442
|
+
"local-runtime": "openteam local-runtime subagent: local model operation, provider binding and runtime health.",
|
|
10770
11443
|
"routing-cost": "openteam routing-cost subagent: cost-aware routing analysis and token/budget optimisation.",
|
|
10771
|
-
scribe: "openteam scribe subagent: the team's silent memory. Records decisions and learnings in .opencode/openteam/decisions.md without changing code.
|
|
10772
|
-
ralph: "openteam ralph subagent: automation and triage driver. Monitors pending work, prioritises it and coordinates execution, escalating blockers to the human.
|
|
10773
|
-
guardian: "openteam guardian subagent:
|
|
11444
|
+
scribe: "openteam scribe subagent: the team's silent memory. Records decisions and learnings in .opencode/openteam/decisions.md without changing code. When a conversation yields durable, reusable knowledge — such as facts, entities, and the typed relations between them (e.g. a decision that affects a component) — also call the `openteam-memory` tool with those messages to record it in the team's semantic graph; it never stores the raw conversation, and a reply that memory is disabled or that nothing was recorded is expected, so do not retry.",
|
|
11445
|
+
ralph: "openteam ralph subagent: automation and triage driver. Monitors pending work, prioritises it and coordinates execution, escalating blockers to the human.",
|
|
11446
|
+
guardian: "openteam guardian subagent: an assignable security reviewer for threat analysis, exploitability checks and security-focused review.",
|
|
10774
11447
|
tester: "openteam tester subagent: writes and runs tests, reproduces bugs and validates fixes.",
|
|
10775
11448
|
reviewer: "openteam reviewer subagent: reviews diffs for correctness, security and quality."
|
|
10776
11449
|
};
|
|
10777
|
-
function buildRoleAgent(agentName, description
|
|
11450
|
+
function buildRoleAgent(agentName, description) {
|
|
10778
11451
|
const frontmatter = [
|
|
10779
11452
|
"---",
|
|
10780
11453
|
`description: ${JSON.stringify(description)}`,
|
|
@@ -10788,7 +11461,7 @@ function buildRoleAgent(agentName, description, localOnly) {
|
|
|
10788
11461
|
"",
|
|
10789
11462
|
description,
|
|
10790
11463
|
"",
|
|
10791
|
-
|
|
11464
|
+
"Your execution domain, selected model and ordered fallbacks come from the matching policy in `.opencode/openteam.json`; leave `model` unset here so the runtime policy remains authoritative.",
|
|
10792
11465
|
"",
|
|
10793
11466
|
"Stay focused on your role. Keep changes surgical and report what you did."
|
|
10794
11467
|
].join(`
|
|
@@ -10798,19 +11471,19 @@ function buildRoleAgent(agentName, description, localOnly) {
|
|
|
10798
11471
|
${body}
|
|
10799
11472
|
`;
|
|
10800
11473
|
}
|
|
10801
|
-
function buildRoleAgentFiles() {
|
|
10802
|
-
const seen = new
|
|
11474
|
+
function buildRoleAgentFiles(_config) {
|
|
11475
|
+
const seen = new Set;
|
|
10803
11476
|
for (const role of Object.values(TEAM_ROLES)) {
|
|
10804
11477
|
const agentName = role.opencodeAgent;
|
|
10805
11478
|
if (agentName === undefined || seen.has(agentName)) {
|
|
10806
11479
|
continue;
|
|
10807
11480
|
}
|
|
10808
|
-
seen.
|
|
11481
|
+
seen.add(agentName);
|
|
10809
11482
|
}
|
|
10810
|
-
return [...seen
|
|
11483
|
+
return [...seen].sort((a, b) => a.localeCompare(b)).map((agentName) => ({
|
|
10811
11484
|
agentName,
|
|
10812
11485
|
path: `${AGENT_DIR}/${agentName}.md`,
|
|
10813
|
-
contents: buildRoleAgent(agentName, ROLE_AGENT_DESCRIPTIONS[agentName] ?? `openteam ${agentName} subagent
|
|
11486
|
+
contents: buildRoleAgent(agentName, ROLE_AGENT_DESCRIPTIONS[agentName] ?? `openteam ${agentName} subagent.`)
|
|
10814
11487
|
}));
|
|
10815
11488
|
}
|
|
10816
11489
|
function splitSetupPath(path2) {
|
|
@@ -10956,40 +11629,42 @@ function buildOpenTeamConfig(answers) {
|
|
|
10956
11629
|
return runtime;
|
|
10957
11630
|
});
|
|
10958
11631
|
const primaryLocal = firstEnabled(answers.runtimes);
|
|
11632
|
+
if (answers.executionMode === "local" && primaryLocal === undefined) {
|
|
11633
|
+
throw new Error(executionSetupMessages.localRequiredError);
|
|
11634
|
+
}
|
|
10959
11635
|
const localDefault = primaryLocal !== undefined ? {
|
|
10960
11636
|
providerID: choiceProviderID(primaryLocal),
|
|
10961
11637
|
modelID: primaryLocal.defaultModelID
|
|
10962
11638
|
} : null;
|
|
10963
|
-
const
|
|
10964
|
-
|
|
11639
|
+
const roles = Object.fromEntries(Object.keys(TEAM_ROLES).sort().map((roleID) => [roleID, { model: "auto" }]));
|
|
11640
|
+
const config = OpenTeamConfigSchema.parse({
|
|
11641
|
+
configVersion: 2,
|
|
10965
11642
|
baseline: {
|
|
10966
11643
|
mode: "auto",
|
|
10967
11644
|
pinnedModel: null,
|
|
10968
11645
|
hardDefault: answers.frontier
|
|
10969
11646
|
},
|
|
10970
11647
|
router: {
|
|
10971
|
-
|
|
10972
|
-
localDefault
|
|
10973
|
-
frontierOnly: primaryLocal === undefined,
|
|
10974
|
-
localOnly
|
|
11648
|
+
executionMode: answers.executionMode,
|
|
11649
|
+
localDefault
|
|
10975
11650
|
},
|
|
10976
11651
|
local: { runtimes },
|
|
10977
|
-
|
|
11652
|
+
orchestrator: {
|
|
11653
|
+
primary: {
|
|
11654
|
+
model: answers.primaryModel,
|
|
11655
|
+
fallbacks: []
|
|
11656
|
+
},
|
|
11657
|
+
roles,
|
|
11658
|
+
maxRetries: 2,
|
|
11659
|
+
maxModelsPerNode: 2
|
|
11660
|
+
},
|
|
10978
11661
|
graph: { mode: "active" }
|
|
10979
11662
|
});
|
|
11663
|
+
assertEffectiveConfigModelDomains(config);
|
|
11664
|
+
return config;
|
|
10980
11665
|
}
|
|
10981
11666
|
function orchestratorAgentModel(answers) {
|
|
10982
|
-
|
|
10983
|
-
if (answers.localOnly === true && primaryLocal !== undefined) {
|
|
10984
|
-
return {
|
|
10985
|
-
providerID: choiceProviderID(primaryLocal),
|
|
10986
|
-
modelID: primaryLocal.thinkingModelID ?? primaryLocal.defaultModelID
|
|
10987
|
-
};
|
|
10988
|
-
}
|
|
10989
|
-
return {
|
|
10990
|
-
providerID: answers.frontier.providerID,
|
|
10991
|
-
modelID: answers.frontier.modelID
|
|
10992
|
-
};
|
|
11667
|
+
return { ...answers.primaryModel };
|
|
10993
11668
|
}
|
|
10994
11669
|
function buildOpencodeConfig(answers) {
|
|
10995
11670
|
const provider = {};
|
|
@@ -11003,12 +11678,22 @@ function buildOpencodeConfig(answers) {
|
|
|
11003
11678
|
if (baseURL === undefined) {
|
|
11004
11679
|
continue;
|
|
11005
11680
|
}
|
|
11006
|
-
const
|
|
11681
|
+
const providerID = choiceProviderID(choice);
|
|
11682
|
+
const modelIDs = new Set(choice.models);
|
|
11683
|
+
modelIDs.add(choice.defaultModelID);
|
|
11684
|
+
if (choice.thinkingModelID !== undefined) {
|
|
11685
|
+
modelIDs.add(choice.thinkingModelID);
|
|
11686
|
+
}
|
|
11687
|
+
if (choice.coderModelID !== undefined) {
|
|
11688
|
+
modelIDs.add(choice.coderModelID);
|
|
11689
|
+
}
|
|
11690
|
+
if (answers.primaryModel.providerID === providerID) {
|
|
11691
|
+
modelIDs.add(answers.primaryModel.modelID);
|
|
11692
|
+
}
|
|
11007
11693
|
const models = {};
|
|
11008
11694
|
for (const modelID of modelIDs) {
|
|
11009
11695
|
models[modelID] = { name: modelID };
|
|
11010
11696
|
}
|
|
11011
|
-
const providerID = choiceProviderID(choice);
|
|
11012
11697
|
const owner = providerOwner.get(providerID);
|
|
11013
11698
|
if (owner !== undefined) {
|
|
11014
11699
|
if (owner.baseURL !== baseURL) {
|
|
@@ -11033,8 +11718,7 @@ function buildOpencodeConfig(answers) {
|
|
|
11033
11718
|
providerOwner.set(providerID, { instanceId: choice.id, baseURL });
|
|
11034
11719
|
}
|
|
11035
11720
|
const primaryLocal = firstEnabled(answers.runtimes);
|
|
11036
|
-
const
|
|
11037
|
-
const mainModel = localOnly && primaryLocal !== undefined ? `${choiceProviderID(primaryLocal)}/${primaryLocal.defaultModelID}` : `${answers.frontier.providerID}/${answers.frontier.modelID}`;
|
|
11721
|
+
const mainModel = `${answers.primaryModel.providerID}/${answers.primaryModel.modelID}`;
|
|
11038
11722
|
const config = {
|
|
11039
11723
|
$schema: "https://opencode.ai/config.json",
|
|
11040
11724
|
plugin: [OPENTEAM_PLUGIN_SPEC],
|
|
@@ -11114,7 +11798,7 @@ async function chooseFrontier(prompt, profiles) {
|
|
|
11114
11798
|
const sorted = sortFrontierByCost(profiles);
|
|
11115
11799
|
const first = sorted[0];
|
|
11116
11800
|
const selected = await prompt.autocomplete({
|
|
11117
|
-
message:
|
|
11801
|
+
message: executionSetupMessages.frontierModelQuestion,
|
|
11118
11802
|
placeholder: "search by provider or model…",
|
|
11119
11803
|
choices: [
|
|
11120
11804
|
...sorted.map((profile) => ({
|
|
@@ -11400,7 +12084,7 @@ async function resolveRuntimeInstances(deps, meta, info, usedIds) {
|
|
|
11400
12084
|
async function runSetup(deps) {
|
|
11401
12085
|
const { prompt } = deps;
|
|
11402
12086
|
try {
|
|
11403
|
-
prompt.intro(
|
|
12087
|
+
prompt.intro(executionSetupMessages.setupIntro);
|
|
11404
12088
|
const detected = await deps.detect();
|
|
11405
12089
|
const detectedById = new Map(detected.map((item) => [item.id, item]));
|
|
11406
12090
|
const runtimeChoices = KNOWN_RUNTIMES.map((meta) => {
|
|
@@ -11414,11 +12098,11 @@ async function runSetup(deps) {
|
|
|
11414
12098
|
});
|
|
11415
12099
|
runtimeChoices.push({
|
|
11416
12100
|
value: RUNTIME_NONE,
|
|
11417
|
-
label:
|
|
11418
|
-
hint:
|
|
12101
|
+
label: executionSetupMessages.runtimeNoneLabel,
|
|
12102
|
+
hint: executionSetupMessages.runtimeNoneHint
|
|
11419
12103
|
});
|
|
11420
12104
|
const enabledIds = await prompt.multiselect({
|
|
11421
|
-
message:
|
|
12105
|
+
message: executionSetupMessages.runtimeQuestion,
|
|
11422
12106
|
choices: runtimeChoices,
|
|
11423
12107
|
required: true
|
|
11424
12108
|
});
|
|
@@ -11445,10 +12129,27 @@ async function runSetup(deps) {
|
|
|
11445
12129
|
runtimes.push(...await resolveRuntimeInstances(deps, meta, info, usedIds));
|
|
11446
12130
|
}
|
|
11447
12131
|
const hasLocalEnabled = enabled.size > 0;
|
|
11448
|
-
const
|
|
11449
|
-
message:
|
|
11450
|
-
|
|
11451
|
-
|
|
12132
|
+
const executionMode2 = hasLocalEnabled ? await prompt.select({
|
|
12133
|
+
message: executionSetupMessages.executionModeQuestion,
|
|
12134
|
+
choices: [
|
|
12135
|
+
{
|
|
12136
|
+
value: "mixed",
|
|
12137
|
+
label: executionSetupMessages.executionModeChoices.mixed.label,
|
|
12138
|
+
hint: executionSetupMessages.executionModeChoices.mixed.hint
|
|
12139
|
+
},
|
|
12140
|
+
{
|
|
12141
|
+
value: "local",
|
|
12142
|
+
label: executionSetupMessages.executionModeChoices.local.label,
|
|
12143
|
+
hint: executionSetupMessages.executionModeChoices.local.hint
|
|
12144
|
+
},
|
|
12145
|
+
{
|
|
12146
|
+
value: "frontier",
|
|
12147
|
+
label: executionSetupMessages.executionModeChoices.frontier.label,
|
|
12148
|
+
hint: executionSetupMessages.executionModeChoices.frontier.hint
|
|
12149
|
+
}
|
|
12150
|
+
],
|
|
12151
|
+
initial: "mixed"
|
|
12152
|
+
}) : "frontier";
|
|
11452
12153
|
let frontierProfiles;
|
|
11453
12154
|
try {
|
|
11454
12155
|
const loaded = await deps.loadFrontierProfiles();
|
|
@@ -11456,68 +12157,55 @@ async function runSetup(deps) {
|
|
|
11456
12157
|
} catch {
|
|
11457
12158
|
frontierProfiles = [...CURATED_FRONTIER_PROFILES];
|
|
11458
12159
|
}
|
|
11459
|
-
const
|
|
11460
|
-
|
|
11461
|
-
|
|
11462
|
-
|
|
11463
|
-
|
|
11464
|
-
|
|
11465
|
-
|
|
11466
|
-
|
|
11467
|
-
|
|
11468
|
-
|
|
11469
|
-
|
|
11470
|
-
|
|
11471
|
-
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
|
|
11475
|
-
|
|
11476
|
-
|
|
11477
|
-
|
|
11478
|
-
|
|
11479
|
-
|
|
11480
|
-
|
|
11481
|
-
|
|
11482
|
-
|
|
11483
|
-
|
|
12160
|
+
const primaryLocal = firstEnabled(runtimes);
|
|
12161
|
+
const localPrimary = primaryLocal === undefined ? undefined : {
|
|
12162
|
+
providerID: choiceProviderID(primaryLocal),
|
|
12163
|
+
modelID: primaryLocal.thinkingModelID ?? primaryLocal.defaultModelID
|
|
12164
|
+
};
|
|
12165
|
+
let frontier;
|
|
12166
|
+
let primaryModel;
|
|
12167
|
+
if (executionMode2 === "local") {
|
|
12168
|
+
if (localPrimary === undefined) {
|
|
12169
|
+
throw new Error(executionSetupMessages.localRequiredError);
|
|
12170
|
+
}
|
|
12171
|
+
frontier = cheapestFrontier(frontierProfiles);
|
|
12172
|
+
primaryModel = localPrimary;
|
|
12173
|
+
prompt.note(executionSetupMessages.localOnlyNote(`${primaryModel.providerID}/${primaryModel.modelID}`), executionSetupMessages.localOnlyTitle);
|
|
12174
|
+
} else {
|
|
12175
|
+
frontier = await chooseFrontier(prompt, frontierProfiles);
|
|
12176
|
+
if (executionMode2 === "frontier" || localPrimary === undefined) {
|
|
12177
|
+
primaryModel = frontier;
|
|
12178
|
+
} else {
|
|
12179
|
+
const primaryDomain = await prompt.select({
|
|
12180
|
+
message: executionSetupMessages.mixedPrimaryQuestion,
|
|
12181
|
+
choices: [
|
|
12182
|
+
{
|
|
12183
|
+
value: "local",
|
|
12184
|
+
label: executionSetupMessages.mixedPrimaryLocal(`${localPrimary.providerID}/${localPrimary.modelID}`)
|
|
12185
|
+
},
|
|
12186
|
+
{
|
|
12187
|
+
value: "frontier",
|
|
12188
|
+
label: executionSetupMessages.mixedPrimaryFrontier
|
|
12189
|
+
}
|
|
12190
|
+
],
|
|
12191
|
+
initial: "frontier"
|
|
12192
|
+
});
|
|
12193
|
+
primaryModel = primaryDomain === "local" ? localPrimary : frontier;
|
|
12194
|
+
}
|
|
12195
|
+
}
|
|
11484
12196
|
if (!hasLocalEnabled) {
|
|
11485
|
-
prompt.note(
|
|
11486
|
-
|
|
11487
|
-
"frontier models (the configured cheapest-capable).",
|
|
11488
|
-
"",
|
|
11489
|
-
"For sensitive prompts, 'forceLocalOnSensitive' would have no local",
|
|
11490
|
-
"target, so 'consentBeforeFrontier' is recommended."
|
|
11491
|
-
].join(`
|
|
11492
|
-
`), "Frontier-only mode");
|
|
11493
|
-
}
|
|
11494
|
-
const privacyMode = localOnly ? "forceLocalOnSensitive" : await prompt.select({
|
|
11495
|
-
message: "Privacy for sensitive prompts",
|
|
11496
|
-
choices: [
|
|
11497
|
-
{
|
|
11498
|
-
value: "forceLocalOnSensitive",
|
|
11499
|
-
label: "forceLocalOnSensitive (never sends sensitive data to frontier)",
|
|
11500
|
-
...hasLocalEnabled ? {} : { hint: "requires an enabled local runtime" }
|
|
11501
|
-
},
|
|
11502
|
-
{
|
|
11503
|
-
value: "consentBeforeFrontier",
|
|
11504
|
-
label: "consentBeforeFrontier (asks for consent)"
|
|
11505
|
-
},
|
|
11506
|
-
{ value: "off", label: "off (no protection)" }
|
|
11507
|
-
],
|
|
11508
|
-
initial: hasLocalEnabled ? "forceLocalOnSensitive" : "consentBeforeFrontier"
|
|
11509
|
-
});
|
|
12197
|
+
prompt.note(executionSetupMessages.frontierOnlyNote, executionSetupMessages.frontierOnlyTitle);
|
|
12198
|
+
}
|
|
11510
12199
|
const yolo = await prompt.confirm({
|
|
11511
|
-
message:
|
|
12200
|
+
message: executionSetupMessages.yoloQuestion,
|
|
11512
12201
|
initial: false
|
|
11513
12202
|
});
|
|
11514
12203
|
const answers = {
|
|
11515
12204
|
runtimes,
|
|
11516
12205
|
frontier,
|
|
11517
|
-
|
|
11518
|
-
|
|
11519
|
-
yolo
|
|
11520
|
-
localOnly
|
|
12206
|
+
executionMode: executionMode2,
|
|
12207
|
+
primaryModel,
|
|
12208
|
+
yolo
|
|
11521
12209
|
};
|
|
11522
12210
|
const openTeamConfig = buildOpenTeamConfig(answers);
|
|
11523
12211
|
const opencodeConfig = buildOpencodeConfig(answers);
|
|
@@ -11527,7 +12215,7 @@ async function runSetup(deps) {
|
|
|
11527
12215
|
const existingOpencodePath = await deps.resolveOpencodeWriteTarget(OPENCODE_CONFIG_CANDIDATES);
|
|
11528
12216
|
const opencodePath = existingOpencodePath ?? OPENCODE_CONFIG_CANDIDATES[0];
|
|
11529
12217
|
const agentPath = ORCHESTRATOR_AGENT_PATH;
|
|
11530
|
-
const roleAgentFiles = buildRoleAgentFiles();
|
|
12218
|
+
const roleAgentFiles = buildRoleAgentFiles(openTeamConfig);
|
|
11531
12219
|
const opencodeGitignorePath = OPENCODE_GITIGNORE_PATH;
|
|
11532
12220
|
const existingGitignore = await deps.fileExists(opencodeGitignorePath) ? await deps.readFile(opencodeGitignorePath) ?? "" : "";
|
|
11533
12221
|
const opencodeGitignore = buildOpencodeGitignore(existingGitignore);
|
|
@@ -11584,27 +12272,30 @@ async function runSetup(deps) {
|
|
|
11584
12272
|
const remote = runtime.baseURL !== undefined && !runtime.baseURL.includes("localhost") && !runtime.baseURL.includes("127.0.0.1") ? ` @ ${runtime.baseURL}` : "";
|
|
11585
12273
|
return `${runtime.id} (${runtime.defaultModelID})${remote}`;
|
|
11586
12274
|
}).join(", ");
|
|
11587
|
-
prompt.note(
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
|
|
11597
|
-
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
12275
|
+
prompt.note(executionSetupMessages.configuredSummary({
|
|
12276
|
+
executionMode: executionMode2,
|
|
12277
|
+
primaryModel: `${primaryModel.providerID}/${primaryModel.modelID}`,
|
|
12278
|
+
frontierBaseline: `${frontier.providerID}/${frontier.modelID}`,
|
|
12279
|
+
localRuntimes: enabledSummary || executionSetupMessages.runtimeNoneHint,
|
|
12280
|
+
yolo,
|
|
12281
|
+
opencodePath,
|
|
12282
|
+
configPath: DEFAULT_CONFIG_PATH,
|
|
12283
|
+
agentPath: ORCHESTRATOR_AGENT_PATH,
|
|
12284
|
+
agentDir: AGENT_DIR,
|
|
12285
|
+
roleAgentCount: roleAgentFiles.length,
|
|
12286
|
+
commandDir: OPENTEAM_COMMAND_DIR,
|
|
12287
|
+
commandCount: slashCommands.length,
|
|
12288
|
+
gitignorePath: OPENCODE_GITIGNORE_PATH
|
|
12289
|
+
}).join(`
|
|
12290
|
+
`), executionSetupMessages.configuredTitle);
|
|
12291
|
+
prompt.outro(executionSetupMessages.done);
|
|
11601
12292
|
return {
|
|
11602
12293
|
exitCode: 0,
|
|
11603
|
-
stdout:
|
|
12294
|
+
stdout: executionSetupMessages.complete(`${opencodePath}, ${DEFAULT_CONFIG_PATH}, ${ORCHESTRATOR_AGENT_PATH}, ${AGENT_DIR}/*.md (${roleAgentFiles.length} subagents), ${OPENTEAM_COMMAND_DIR}/*.md, ${OPENCODE_GITIGNORE_PATH}`)
|
|
11604
12295
|
};
|
|
11605
12296
|
} catch (error) {
|
|
11606
12297
|
const message = error instanceof Error ? error.message : String(error);
|
|
11607
|
-
return { exitCode: 1, stdout:
|
|
12298
|
+
return { exitCode: 1, stdout: executionSetupMessages.error(message) };
|
|
11608
12299
|
}
|
|
11609
12300
|
}
|
|
11610
12301
|
|
|
@@ -11874,8 +12565,8 @@ function stripOpenteamFromOpencodeConfig(raw) {
|
|
|
11874
12565
|
}
|
|
11875
12566
|
return { status: "not-present" };
|
|
11876
12567
|
}
|
|
11877
|
-
const
|
|
11878
|
-
const plugins =
|
|
12568
|
+
const record2 = parsed;
|
|
12569
|
+
const plugins = record2.plugin;
|
|
11879
12570
|
if (!Array.isArray(plugins)) {
|
|
11880
12571
|
if (raw.includes(OPENTEAM_PLUGIN_SPEC)) {
|
|
11881
12572
|
return {
|
|
@@ -12289,10 +12980,10 @@ var HELP = [
|
|
|
12289
12980
|
localMessages.help.off,
|
|
12290
12981
|
localMessages.help.only,
|
|
12291
12982
|
localMessages.help.on,
|
|
12292
|
-
|
|
12293
|
-
|
|
12983
|
+
executionPolicyMessages.doctorHelp,
|
|
12984
|
+
rolesInitMessages.help,
|
|
12294
12985
|
" openteam migrate Relocate pre-P15b artifacts to their split homes (idempotent)",
|
|
12295
|
-
|
|
12986
|
+
executionPolicyMessages.agentsHelp,
|
|
12296
12987
|
" openteam console Launch the multi-session web Console (Ctrl+C to stop; --open opens the browser)",
|
|
12297
12988
|
" openteam console --status Show the Console config without launching it",
|
|
12298
12989
|
" openteam graph status Show the Graph journal and lease health",
|
|
@@ -12510,9 +13201,7 @@ ${HELP}`
|
|
|
12510
13201
|
const agentNote = await regenerateOrchestratorAgent(deps, config, enable);
|
|
12511
13202
|
return {
|
|
12512
13203
|
exitCode: 0,
|
|
12513
|
-
stdout:
|
|
12514
|
-
Note: YOLO does not disable openteam privacy; sensitive prompts still stay off frontier with forceLocalOnSensitive.` : ""}
|
|
12515
|
-
Restart opencode (or reload) so agents pick up the new permissions.`
|
|
13204
|
+
stdout: yoloMessages.updated(writePath, renderYoloStatus(enable), agentNote)
|
|
12516
13205
|
};
|
|
12517
13206
|
}
|
|
12518
13207
|
async function regenerateOrchestratorAgent(deps, config, enable) {
|
|
@@ -12572,6 +13261,11 @@ async function runAgents(deps, configPath, opencodeConfigPaths, configResolution
|
|
|
12572
13261
|
exitCode: 0,
|
|
12573
13262
|
stdout: renderAgentList(classifications, {
|
|
12574
13263
|
...context,
|
|
13264
|
+
executionPolicies: executionPolicySummaries(config),
|
|
13265
|
+
retryLimits: {
|
|
13266
|
+
maxRetries: config.orchestrator?.maxRetries ?? 2,
|
|
13267
|
+
maxModelsPerNode: config.orchestrator?.maxModelsPerNode ?? 2
|
|
13268
|
+
},
|
|
12575
13269
|
...opencodeConfigError !== undefined ? { opencodeConfigError } : {}
|
|
12576
13270
|
})
|
|
12577
13271
|
};
|
|
@@ -13158,9 +13852,12 @@ function auditParsedRoster(roster, configuredRoles) {
|
|
|
13158
13852
|
}
|
|
13159
13853
|
|
|
13160
13854
|
// src/commands/rolesInit.ts
|
|
13161
|
-
var
|
|
13162
|
-
var
|
|
13855
|
+
var ROLES_INIT_AUTO = "auto";
|
|
13856
|
+
var ROLES_INIT_PIN = "pin";
|
|
13163
13857
|
var ROLES_INIT_SKIP = "skip";
|
|
13858
|
+
var ROLES_INIT_INHERIT = "inherit";
|
|
13859
|
+
var ROLES_INIT_LOCAL_ONLY = "local";
|
|
13860
|
+
var ROLES_INIT_FRONTIER_ONLY = "frontier";
|
|
13164
13861
|
function isJsonRecord2(value) {
|
|
13165
13862
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13166
13863
|
}
|
|
@@ -13193,29 +13890,15 @@ function withRole(config, roleID, profile) {
|
|
|
13193
13890
|
}
|
|
13194
13891
|
};
|
|
13195
13892
|
}
|
|
13196
|
-
function
|
|
13197
|
-
const base = synthesiseFallbackProfile(roleID);
|
|
13198
|
-
return {
|
|
13199
|
-
...base,
|
|
13200
|
-
preferredFrontierModels: [],
|
|
13201
|
-
localFirst: true,
|
|
13202
|
-
requiresLocalRuntime: true
|
|
13203
|
-
};
|
|
13204
|
-
}
|
|
13205
|
-
function frontierEligibleProfile(roleID) {
|
|
13206
|
-
return { ...synthesiseFallbackProfile(roleID) };
|
|
13207
|
-
}
|
|
13208
|
-
function policyChoices(roleID, agentName) {
|
|
13893
|
+
function policyChoices() {
|
|
13209
13894
|
return [
|
|
13210
13895
|
{
|
|
13211
|
-
value:
|
|
13212
|
-
label:
|
|
13213
|
-
hint: rolesInitMessages.localOnlyHint
|
|
13896
|
+
value: ROLES_INIT_AUTO,
|
|
13897
|
+
label: executionSetupMessages.rolePolicy.modelAutoLabel
|
|
13214
13898
|
},
|
|
13215
13899
|
{
|
|
13216
|
-
value:
|
|
13217
|
-
label:
|
|
13218
|
-
hint: rolesInitMessages.frontierOkHint
|
|
13900
|
+
value: ROLES_INIT_PIN,
|
|
13901
|
+
label: executionSetupMessages.rolePolicy.modelExactLabel
|
|
13219
13902
|
},
|
|
13220
13903
|
{
|
|
13221
13904
|
value: ROLES_INIT_SKIP,
|
|
@@ -13224,6 +13907,56 @@ function policyChoices(roleID, agentName) {
|
|
|
13224
13907
|
}
|
|
13225
13908
|
];
|
|
13226
13909
|
}
|
|
13910
|
+
function executionModeChoices() {
|
|
13911
|
+
return [
|
|
13912
|
+
{
|
|
13913
|
+
value: ROLES_INIT_INHERIT,
|
|
13914
|
+
label: executionSetupMessages.rolePolicy.modeInheritLabel
|
|
13915
|
+
},
|
|
13916
|
+
{
|
|
13917
|
+
value: ROLES_INIT_LOCAL_ONLY,
|
|
13918
|
+
label: executionSetupMessages.rolePolicy.modeLocalLabel
|
|
13919
|
+
},
|
|
13920
|
+
{
|
|
13921
|
+
value: ROLES_INIT_FRONTIER_ONLY,
|
|
13922
|
+
label: executionSetupMessages.rolePolicy.modeFrontierLabel
|
|
13923
|
+
}
|
|
13924
|
+
];
|
|
13925
|
+
}
|
|
13926
|
+
function rawExecutionMode(config) {
|
|
13927
|
+
const router = isJsonRecord2(config.router) ? config.router : {};
|
|
13928
|
+
if (router.executionMode === "local" || router.executionMode === "frontier" || router.executionMode === "mixed") {
|
|
13929
|
+
return router.executionMode;
|
|
13930
|
+
}
|
|
13931
|
+
if (router.localOnly === true) {
|
|
13932
|
+
return "local";
|
|
13933
|
+
}
|
|
13934
|
+
if (router.frontierOnly === true) {
|
|
13935
|
+
return "frontier";
|
|
13936
|
+
}
|
|
13937
|
+
return "mixed";
|
|
13938
|
+
}
|
|
13939
|
+
function normalizePortableConfig(raw) {
|
|
13940
|
+
const migrated = migrateConfig(raw);
|
|
13941
|
+
const normalized = isJsonRecord2(migrated) ? { ...migrated } : {};
|
|
13942
|
+
delete normalized.privacyMode;
|
|
13943
|
+
return normalized;
|
|
13944
|
+
}
|
|
13945
|
+
function parseModelRef2(value) {
|
|
13946
|
+
const trimmed = value.trim();
|
|
13947
|
+
const slash = trimmed.indexOf("/");
|
|
13948
|
+
if (slash <= 0 || slash === trimmed.length - 1) {
|
|
13949
|
+
throw new Error(executionSetupMessages.rolePolicy.invalidModelRef(value));
|
|
13950
|
+
}
|
|
13951
|
+
return {
|
|
13952
|
+
providerID: trimmed.slice(0, slash),
|
|
13953
|
+
modelID: trimmed.slice(slash + 1)
|
|
13954
|
+
};
|
|
13955
|
+
}
|
|
13956
|
+
function parseFallbacks(value) {
|
|
13957
|
+
const trimmed = value.trim();
|
|
13958
|
+
return trimmed.length === 0 ? [] : trimmed.split(",").map((entry) => parseModelRef2(entry));
|
|
13959
|
+
}
|
|
13227
13960
|
async function runRolesInit(deps) {
|
|
13228
13961
|
const lines = [rolesInitMessages.header];
|
|
13229
13962
|
const rosterContent = await deps.readFile(OPENTEAM_ROSTER_PATH);
|
|
@@ -13238,8 +13971,18 @@ async function runRolesInit(deps) {
|
|
|
13238
13971
|
return { stdout: lines.join(`
|
|
13239
13972
|
`), exitCode: 1 };
|
|
13240
13973
|
}
|
|
13241
|
-
const
|
|
13974
|
+
const rawBaseConfig = await readJsonFile(deps, DEFAULT_CONFIG_PATH);
|
|
13975
|
+
lines.push(...detectConfigDeprecations(rawBaseConfig).map((deprecation) => rolesInitMessages.deprecation(deprecation.message)));
|
|
13976
|
+
let baseConfig;
|
|
13977
|
+
try {
|
|
13978
|
+
baseConfig = normalizePortableConfig(rawBaseConfig);
|
|
13979
|
+
} catch (error) {
|
|
13980
|
+
lines.push(rolesInitMessages.invalidResult(error instanceof Error ? error.message : String(error)));
|
|
13981
|
+
return { stdout: lines.join(`
|
|
13982
|
+
`), exitCode: 1 };
|
|
13983
|
+
}
|
|
13242
13984
|
const configuredRoles = existingRoles(baseConfig);
|
|
13985
|
+
const globalExecutionMode = rawExecutionMode(baseConfig);
|
|
13243
13986
|
const unprofiled = parsed.roster.entries.filter((entry) => getRoleProfile(entry.roleID, configuredRoles) === undefined && !isKnownNonWorkerRole(entry.roleID));
|
|
13244
13987
|
if (unprofiled.length === 0) {
|
|
13245
13988
|
lines.push(rolesInitMessages.allProfiled(parsed.roster.entries.length));
|
|
@@ -13247,32 +13990,44 @@ async function runRolesInit(deps) {
|
|
|
13247
13990
|
`), exitCode: 0 };
|
|
13248
13991
|
}
|
|
13249
13992
|
deps.prompt.intro(rolesInitMessages.title);
|
|
13250
|
-
deps.prompt.note(
|
|
13993
|
+
deps.prompt.note(executionSetupMessages.rolePolicy.why(unprofiled.length), executionSetupMessages.rolePolicy.whyTitle);
|
|
13251
13994
|
const runtimes = (await deps.detectRuntimes?.())?.filter((runtime) => runtime.available);
|
|
13252
13995
|
let nextConfig = baseConfig;
|
|
13253
13996
|
let nextOverlay;
|
|
13254
|
-
const
|
|
13255
|
-
const relaxed = [];
|
|
13997
|
+
const configured = [];
|
|
13256
13998
|
const skipped = [];
|
|
13257
13999
|
try {
|
|
13258
14000
|
for (const entry of unprofiled) {
|
|
13259
14001
|
const choice = await deps.prompt.select({
|
|
13260
|
-
message:
|
|
13261
|
-
choices: policyChoices(
|
|
13262
|
-
initial:
|
|
14002
|
+
message: executionSetupMessages.rolePolicy.modelQuestion(entry.roleID),
|
|
14003
|
+
choices: policyChoices(),
|
|
14004
|
+
initial: ROLES_INIT_AUTO
|
|
13263
14005
|
});
|
|
13264
14006
|
if (choice === ROLES_INIT_SKIP) {
|
|
13265
14007
|
skipped.push(entry.roleID);
|
|
13266
14008
|
continue;
|
|
13267
14009
|
}
|
|
13268
|
-
|
|
13269
|
-
|
|
13270
|
-
|
|
13271
|
-
|
|
13272
|
-
}
|
|
13273
|
-
|
|
13274
|
-
|
|
13275
|
-
|
|
14010
|
+
const modeChoice = globalExecutionMode === "mixed" ? await deps.prompt.select({
|
|
14011
|
+
message: executionSetupMessages.rolePolicy.modeQuestion(entry.roleID, entry.agentName),
|
|
14012
|
+
choices: executionModeChoices(),
|
|
14013
|
+
initial: ROLES_INIT_INHERIT
|
|
14014
|
+
}) : ROLES_INIT_INHERIT;
|
|
14015
|
+
const model = choice === ROLES_INIT_AUTO ? "auto" : parseModelRef2(await deps.prompt.text({
|
|
14016
|
+
message: executionSetupMessages.rolePolicy.modelExactQuestion(entry.roleID),
|
|
14017
|
+
placeholder: executionSetupMessages.rolePolicy.modelExactPlaceholder
|
|
14018
|
+
}));
|
|
14019
|
+
const fallbacks = parseFallbacks(await deps.prompt.text({
|
|
14020
|
+
message: executionSetupMessages.rolePolicy.fallbacksQuestion(entry.roleID),
|
|
14021
|
+
placeholder: executionSetupMessages.rolePolicy.fallbacksPlaceholder
|
|
14022
|
+
}));
|
|
14023
|
+
const policy = {
|
|
14024
|
+
model,
|
|
14025
|
+
fallbacks,
|
|
14026
|
+
...modeChoice === ROLES_INIT_INHERIT ? {} : { executionMode: modeChoice }
|
|
14027
|
+
};
|
|
14028
|
+
nextConfig = withRole(nextConfig, entry.roleID, policy);
|
|
14029
|
+
configured.push(entry.roleID);
|
|
14030
|
+
if (modeChoice === ROLES_INIT_LOCAL_ONLY && runtimes !== undefined && runtimes.length > 0) {
|
|
13276
14031
|
const selected = await deps.prompt.multiselect({
|
|
13277
14032
|
message: rolesInitMessages.runtimeQuestion(entry.roleID),
|
|
13278
14033
|
choices: runtimes.map((runtime) => ({
|
|
@@ -13294,15 +14049,21 @@ async function runRolesInit(deps) {
|
|
|
13294
14049
|
return { stdout: lines.join(`
|
|
13295
14050
|
`), exitCode: 1 };
|
|
13296
14051
|
}
|
|
13297
|
-
if (
|
|
14052
|
+
if (configured.length === 0) {
|
|
13298
14053
|
lines.push(rolesInitMessages.nothingWritten(skipped.join(", ")));
|
|
13299
14054
|
deps.prompt.outro(rolesInitMessages.nothingToWriteOutro);
|
|
13300
14055
|
return { stdout: lines.join(`
|
|
13301
14056
|
`), exitCode: 0 };
|
|
13302
14057
|
}
|
|
13303
|
-
const merged = mergeConfigInputs(nextConfig, nextOverlay);
|
|
13304
14058
|
try {
|
|
13305
|
-
|
|
14059
|
+
const existingOverlay = await readJsonFile(deps, DEFAULT_LOCAL_OVERLAY_PATH);
|
|
14060
|
+
const effectiveOverlay = nextOverlay === undefined ? existingOverlay : mergeConfigInputs(existingOverlay, nextOverlay);
|
|
14061
|
+
const merged = mergeConfigInputs(nextConfig, effectiveOverlay);
|
|
14062
|
+
const effectiveConfig = loadOpenTeamConfig(merged);
|
|
14063
|
+
assertEffectiveConfigModelDomains(effectiveConfig);
|
|
14064
|
+
if (nextOverlay !== undefined) {
|
|
14065
|
+
nextOverlay = effectiveOverlay;
|
|
14066
|
+
}
|
|
13306
14067
|
} catch (error) {
|
|
13307
14068
|
lines.push(rolesInitMessages.invalidResult(error instanceof Error ? error.message : String(error)));
|
|
13308
14069
|
return { stdout: lines.join(`
|
|
@@ -13312,12 +14073,7 @@ async function runRolesInit(deps) {
|
|
|
13312
14073
|
if (nextOverlay !== undefined) {
|
|
13313
14074
|
await deps.writeFile(DEFAULT_LOCAL_OVERLAY_PATH, serialize(nextOverlay));
|
|
13314
14075
|
}
|
|
13315
|
-
|
|
13316
|
-
lines.push(rolesInitMessages.pinnedLocal(pinned.join(", "), DEFAULT_CONFIG_PATH));
|
|
13317
|
-
}
|
|
13318
|
-
if (relaxed.length > 0) {
|
|
13319
|
-
lines.push(rolesInitMessages.frontierAllowed(relaxed.join(", "), DEFAULT_CONFIG_PATH));
|
|
13320
|
-
}
|
|
14076
|
+
lines.push(executionSetupMessages.rolePolicy.configured(configured.join(", "), DEFAULT_CONFIG_PATH));
|
|
13321
14077
|
if (nextOverlay !== undefined) {
|
|
13322
14078
|
lines.push(rolesInitMessages.runtimeBinding(DEFAULT_LOCAL_OVERLAY_PATH));
|
|
13323
14079
|
}
|
|
@@ -14280,21 +15036,21 @@ var DEFAULT_RANK_SIGNALS = {
|
|
|
14280
15036
|
accessWeight: 0.1,
|
|
14281
15037
|
recencyHalfLifeMs: 14 * DAY_MS
|
|
14282
15038
|
};
|
|
14283
|
-
function recencyTimestamp(
|
|
14284
|
-
if (
|
|
14285
|
-
return
|
|
15039
|
+
function recencyTimestamp(record2) {
|
|
15040
|
+
if (record2.kind === "preference" && record2.lastAccessedAt !== null) {
|
|
15041
|
+
return record2.lastAccessedAt;
|
|
14286
15042
|
}
|
|
14287
|
-
return
|
|
15043
|
+
return record2.createdAt;
|
|
14288
15044
|
}
|
|
14289
|
-
function recencyScore(
|
|
14290
|
-
const ageMs = Math.max(0, signals.now - recencyTimestamp(
|
|
15045
|
+
function recencyScore(record2, signals) {
|
|
15046
|
+
const ageMs = Math.max(0, signals.now - recencyTimestamp(record2));
|
|
14291
15047
|
return 0.5 ** (ageMs / signals.recencyHalfLifeMs);
|
|
14292
15048
|
}
|
|
14293
|
-
function accessScore(
|
|
14294
|
-
if (
|
|
15049
|
+
function accessScore(record2) {
|
|
15050
|
+
if (record2.kind !== "preference") {
|
|
14295
15051
|
return 0;
|
|
14296
15052
|
}
|
|
14297
|
-
return 1 - 1 / (1 +
|
|
15053
|
+
return 1 - 1 / (1 + record2.accessCount);
|
|
14298
15054
|
}
|
|
14299
15055
|
function scoreCandidate(candidate, signals) {
|
|
14300
15056
|
return signals.similarityWeight * candidate.similarity + signals.recencyWeight * recencyScore(candidate.record, signals) + signals.accessWeight * accessScore(candidate.record);
|
|
@@ -14312,20 +15068,20 @@ function rankRecall(candidates, signals) {
|
|
|
14312
15068
|
}
|
|
14313
15069
|
|
|
14314
15070
|
// src/memory/temporal.ts
|
|
14315
|
-
function isTransactionActiveAt(
|
|
14316
|
-
return
|
|
15071
|
+
function isTransactionActiveAt(record2, asOf) {
|
|
15072
|
+
return record2.invalidatedAt === null || record2.invalidatedAt > asOf;
|
|
14317
15073
|
}
|
|
14318
|
-
function isValidAt(
|
|
14319
|
-
if (
|
|
15074
|
+
function isValidAt(record2, asOf) {
|
|
15075
|
+
if (record2.validFrom > asOf) {
|
|
14320
15076
|
return false;
|
|
14321
15077
|
}
|
|
14322
|
-
return
|
|
15078
|
+
return record2.validUntil === null || record2.validUntil > asOf;
|
|
14323
15079
|
}
|
|
14324
|
-
function isActiveAt(
|
|
14325
|
-
return isTransactionActiveAt(
|
|
15080
|
+
function isActiveAt(record2, asOf) {
|
|
15081
|
+
return isTransactionActiveAt(record2, asOf) && isValidAt(record2, asOf);
|
|
14326
15082
|
}
|
|
14327
15083
|
function filterActive(records, asOf) {
|
|
14328
|
-
return records.filter((
|
|
15084
|
+
return records.filter((record2) => isActiveAt(record2, asOf));
|
|
14329
15085
|
}
|
|
14330
15086
|
function coalesceWindow(a, b) {
|
|
14331
15087
|
const validFrom = Math.min(a.validFrom, b.validFrom);
|
|
@@ -14361,21 +15117,21 @@ function cosineSimilarity(a, b) {
|
|
|
14361
15117
|
}
|
|
14362
15118
|
|
|
14363
15119
|
// src/memory/recall.ts
|
|
14364
|
-
function matchesScope(
|
|
14365
|
-
if (
|
|
15120
|
+
function matchesScope(record2, query) {
|
|
15121
|
+
if (record2.ownerKey === query.ownerKey) {
|
|
14366
15122
|
return true;
|
|
14367
15123
|
}
|
|
14368
|
-
return query.includeShared &&
|
|
15124
|
+
return query.includeShared && record2.ownerKey === SHARED_OWNER_KEY;
|
|
14369
15125
|
}
|
|
14370
15126
|
function recall(records, queryEmbedding, query, deps) {
|
|
14371
15127
|
const asOf = query.asOf ?? deps.now;
|
|
14372
15128
|
const kinds = new Set(query.kinds);
|
|
14373
15129
|
const candidates = [];
|
|
14374
|
-
for (const
|
|
14375
|
-
if (!kinds.has(
|
|
15130
|
+
for (const record2 of filterActive(records, asOf)) {
|
|
15131
|
+
if (!kinds.has(record2.kind) || !matchesScope(record2, query)) {
|
|
14376
15132
|
continue;
|
|
14377
15133
|
}
|
|
14378
|
-
const embedding = deps.embeddings.get(
|
|
15134
|
+
const embedding = deps.embeddings.get(record2.id);
|
|
14379
15135
|
if (embedding === undefined) {
|
|
14380
15136
|
continue;
|
|
14381
15137
|
}
|
|
@@ -14383,7 +15139,7 @@ function recall(records, queryEmbedding, query, deps) {
|
|
|
14383
15139
|
if (similarity < query.minSimilarity) {
|
|
14384
15140
|
continue;
|
|
14385
15141
|
}
|
|
14386
|
-
candidates.push({ record, similarity });
|
|
15142
|
+
candidates.push({ record: record2, similarity });
|
|
14387
15143
|
}
|
|
14388
15144
|
const signals = {
|
|
14389
15145
|
now: deps.now,
|
|
@@ -14573,11 +15329,11 @@ function emptyState() {
|
|
|
14573
15329
|
return { facts: [], preferences: [], entities: [], relations: [] };
|
|
14574
15330
|
}
|
|
14575
15331
|
function applyTombstone(state, tombstone) {
|
|
14576
|
-
const mark = (records) => records.map((
|
|
14577
|
-
...
|
|
15332
|
+
const mark = (records) => records.map((record2) => record2.id === tombstone.id ? {
|
|
15333
|
+
...record2,
|
|
14578
15334
|
invalidatedAt: tombstone.invalidatedAt,
|
|
14579
15335
|
supersededBy: tombstone.supersededBy
|
|
14580
|
-
} :
|
|
15336
|
+
} : record2);
|
|
14581
15337
|
if (tombstone.kind === "fact") {
|
|
14582
15338
|
state.facts = mark(state.facts);
|
|
14583
15339
|
} else if (tombstone.kind === "preference") {
|
|
@@ -14617,8 +15373,8 @@ function allRecords(state) {
|
|
|
14617
15373
|
}
|
|
14618
15374
|
|
|
14619
15375
|
// src/memory/log.ts
|
|
14620
|
-
function serializeAssertion(
|
|
14621
|
-
return JSON.stringify(
|
|
15376
|
+
function serializeAssertion(record2) {
|
|
15377
|
+
return JSON.stringify(record2);
|
|
14622
15378
|
}
|
|
14623
15379
|
function parseMemoryLog(text2) {
|
|
14624
15380
|
const records = [];
|
|
@@ -14680,8 +15436,8 @@ function insertRecords(db, records) {
|
|
|
14680
15436
|
const stmt = db.query(INSERT_SQL2);
|
|
14681
15437
|
db.run("BEGIN");
|
|
14682
15438
|
try {
|
|
14683
|
-
for (const
|
|
14684
|
-
stmt.run(
|
|
15439
|
+
for (const record2 of records) {
|
|
15440
|
+
stmt.run(record2.id, record2.kind, record2.ownerKey, record2.createdAt, record2.invalidatedAt, JSON.stringify(record2));
|
|
14685
15441
|
}
|
|
14686
15442
|
db.run("COMMIT");
|
|
14687
15443
|
} catch (error) {
|
|
@@ -14734,15 +15490,15 @@ function createMemoryIndex(db, warn) {
|
|
|
14734
15490
|
const preferences = [];
|
|
14735
15491
|
const entities = [];
|
|
14736
15492
|
const relations = [];
|
|
14737
|
-
for (const
|
|
14738
|
-
if (
|
|
14739
|
-
facts.push(
|
|
14740
|
-
} else if (
|
|
14741
|
-
preferences.push(
|
|
14742
|
-
} else if (
|
|
14743
|
-
entities.push(
|
|
15493
|
+
for (const record2 of allRecordsWithWarning()) {
|
|
15494
|
+
if (record2.kind === "fact") {
|
|
15495
|
+
facts.push(record2);
|
|
15496
|
+
} else if (record2.kind === "preference") {
|
|
15497
|
+
preferences.push(record2);
|
|
15498
|
+
} else if (record2.kind === "entity") {
|
|
15499
|
+
entities.push(record2);
|
|
14744
15500
|
} else {
|
|
14745
|
-
relations.push(
|
|
15501
|
+
relations.push(record2);
|
|
14746
15502
|
}
|
|
14747
15503
|
}
|
|
14748
15504
|
return { facts, preferences, entities, relations };
|
|
@@ -14881,31 +15637,31 @@ function createLocalEmbedder(request, deps) {
|
|
|
14881
15637
|
function normalize(parts) {
|
|
14882
15638
|
return parts.map((part) => part.trim()).filter((part) => part.length > 0).join(" ");
|
|
14883
15639
|
}
|
|
14884
|
-
function recordEmbeddingText(
|
|
14885
|
-
if (
|
|
15640
|
+
function recordEmbeddingText(record2) {
|
|
15641
|
+
if (record2.kind === "fact") {
|
|
14886
15642
|
return normalize([
|
|
14887
|
-
|
|
14888
|
-
|
|
14889
|
-
|
|
14890
|
-
|
|
15643
|
+
record2.subject,
|
|
15644
|
+
record2.predicate,
|
|
15645
|
+
record2.object,
|
|
15646
|
+
record2.category ?? ""
|
|
14891
15647
|
]);
|
|
14892
15648
|
}
|
|
14893
|
-
if (
|
|
15649
|
+
if (record2.kind === "preference") {
|
|
14894
15650
|
return normalize([
|
|
14895
|
-
|
|
14896
|
-
|
|
14897
|
-
|
|
15651
|
+
record2.category,
|
|
15652
|
+
record2.preference,
|
|
15653
|
+
record2.context ?? ""
|
|
14898
15654
|
]);
|
|
14899
15655
|
}
|
|
14900
|
-
if (
|
|
15656
|
+
if (record2.kind === "relation") {
|
|
14901
15657
|
return normalize([
|
|
14902
|
-
|
|
14903
|
-
|
|
14904
|
-
|
|
14905
|
-
|
|
15658
|
+
record2.from,
|
|
15659
|
+
record2.predicate,
|
|
15660
|
+
record2.to,
|
|
15661
|
+
record2.annotation ?? ""
|
|
14906
15662
|
]);
|
|
14907
15663
|
}
|
|
14908
|
-
return normalize([
|
|
15664
|
+
return normalize([record2.canonicalName, record2.type, ...record2.aliases]);
|
|
14909
15665
|
}
|
|
14910
15666
|
|
|
14911
15667
|
// src/storage/index/memoryEmbeddings.ts
|
|
@@ -14921,7 +15677,7 @@ async function ensureMemoryEmbeddings(deps) {
|
|
|
14921
15677
|
const records = index.allRecords();
|
|
14922
15678
|
index.pruneEmbeddings();
|
|
14923
15679
|
const have = modelChanged ? new Set : new Set(index.embeddingMap().keys());
|
|
14924
|
-
const todo = records.filter((
|
|
15680
|
+
const todo = records.filter((record2) => !have.has(record2.id));
|
|
14925
15681
|
if (todo.length === 0) {
|
|
14926
15682
|
return { embedded: 0, failed: 0, modelChanged };
|
|
14927
15683
|
}
|
|
@@ -14937,8 +15693,8 @@ async function ensureMemoryEmbeddings(deps) {
|
|
|
14937
15693
|
warn(`[openteam] could not compute embeddings for ${todo.length} memory record(s): ` + `the embedder returned ${vectors.length} vector(s). Recall over them is unavailable.`);
|
|
14938
15694
|
return { embedded: 0, failed: todo.length, modelChanged };
|
|
14939
15695
|
}
|
|
14940
|
-
const entries = todo.map((
|
|
14941
|
-
recordId:
|
|
15696
|
+
const entries = todo.map((record2, position) => ({
|
|
15697
|
+
recordId: record2.id,
|
|
14942
15698
|
vector: vectors[position]
|
|
14943
15699
|
}));
|
|
14944
15700
|
index.putEmbeddings(entries);
|
|
@@ -14987,7 +15743,7 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
|
|
|
14987
15743
|
// package.json
|
|
14988
15744
|
var package_default = {
|
|
14989
15745
|
name: "@jmanuelcorral/openteam",
|
|
14990
|
-
version: "0.
|
|
15746
|
+
version: "0.19.0",
|
|
14991
15747
|
packageManager: "bun@1.3.14",
|
|
14992
15748
|
description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
|
|
14993
15749
|
license: "MIT",
|
|
@@ -15178,30 +15934,30 @@ function isMissingFile(error) {
|
|
|
15178
15934
|
}
|
|
15179
15935
|
|
|
15180
15936
|
// src/memory/traverse.ts
|
|
15181
|
-
function inOwnerScope(
|
|
15182
|
-
if (
|
|
15937
|
+
function inOwnerScope(record2, ownerKey, includeShared) {
|
|
15938
|
+
if (record2.ownerKey === ownerKey) {
|
|
15183
15939
|
return true;
|
|
15184
15940
|
}
|
|
15185
|
-
return includeShared &&
|
|
15941
|
+
return includeShared && record2.ownerKey === SHARED_OWNER_KEY;
|
|
15186
15942
|
}
|
|
15187
15943
|
function traverseRelations(records, query) {
|
|
15188
15944
|
const includeShared = query.includeShared ?? true;
|
|
15189
15945
|
const adjacency = new Map;
|
|
15190
|
-
for (const
|
|
15191
|
-
if (
|
|
15946
|
+
for (const record2 of records) {
|
|
15947
|
+
if (record2.kind !== "relation") {
|
|
15192
15948
|
continue;
|
|
15193
15949
|
}
|
|
15194
|
-
if (!isActiveAt(
|
|
15950
|
+
if (!isActiveAt(record2, query.asOf)) {
|
|
15195
15951
|
continue;
|
|
15196
15952
|
}
|
|
15197
|
-
if (!inOwnerScope(
|
|
15953
|
+
if (!inOwnerScope(record2, query.ownerKey, includeShared)) {
|
|
15198
15954
|
continue;
|
|
15199
15955
|
}
|
|
15200
|
-
const list = adjacency.get(
|
|
15956
|
+
const list = adjacency.get(record2.from);
|
|
15201
15957
|
if (list === undefined) {
|
|
15202
|
-
adjacency.set(
|
|
15958
|
+
adjacency.set(record2.from, [record2]);
|
|
15203
15959
|
} else {
|
|
15204
|
-
list.push(
|
|
15960
|
+
list.push(record2);
|
|
15205
15961
|
}
|
|
15206
15962
|
}
|
|
15207
15963
|
const visited = new Set([query.seed]);
|
|
@@ -15232,8 +15988,8 @@ function traverseRelations(records, query) {
|
|
|
15232
15988
|
}
|
|
15233
15989
|
return { seed: query.seed, nodes, edges };
|
|
15234
15990
|
}
|
|
15235
|
-
function seedNodeId(
|
|
15236
|
-
return
|
|
15991
|
+
function seedNodeId(record2) {
|
|
15992
|
+
return record2.kind === "entity" ? record2.canonicalName : undefined;
|
|
15237
15993
|
}
|
|
15238
15994
|
function recallGraph(records, queryEmbedding, query, options, deps) {
|
|
15239
15995
|
const seeds = recall(records, queryEmbedding, query, deps);
|
|
@@ -15529,32 +16285,32 @@ function projectRelationGraph(graph) {
|
|
|
15529
16285
|
};
|
|
15530
16286
|
}
|
|
15531
16287
|
function projectRecall(results) {
|
|
15532
|
-
return results.map(({ record, similarity, score }) => {
|
|
16288
|
+
return results.map(({ record: record2, similarity, score }) => {
|
|
15533
16289
|
const base = {
|
|
15534
|
-
id:
|
|
15535
|
-
kind:
|
|
15536
|
-
ownerKey:
|
|
15537
|
-
confidence:
|
|
16290
|
+
id: record2.id,
|
|
16291
|
+
kind: record2.kind,
|
|
16292
|
+
ownerKey: record2.ownerKey,
|
|
16293
|
+
confidence: record2.confidence,
|
|
15538
16294
|
similarity,
|
|
15539
16295
|
score
|
|
15540
16296
|
};
|
|
15541
|
-
if (
|
|
16297
|
+
if (record2.kind === "fact") {
|
|
15542
16298
|
return {
|
|
15543
16299
|
...base,
|
|
15544
|
-
subject:
|
|
15545
|
-
predicate:
|
|
15546
|
-
object:
|
|
15547
|
-
category:
|
|
16300
|
+
subject: record2.subject,
|
|
16301
|
+
predicate: record2.predicate,
|
|
16302
|
+
object: record2.object,
|
|
16303
|
+
category: record2.category
|
|
15548
16304
|
};
|
|
15549
16305
|
}
|
|
15550
|
-
if (
|
|
16306
|
+
if (record2.kind === "preference") {
|
|
15551
16307
|
return {
|
|
15552
16308
|
...base,
|
|
15553
|
-
category:
|
|
15554
|
-
preference:
|
|
16309
|
+
category: record2.category,
|
|
16310
|
+
preference: record2.preference
|
|
15555
16311
|
};
|
|
15556
16312
|
}
|
|
15557
|
-
const entity =
|
|
16313
|
+
const entity = record2;
|
|
15558
16314
|
return { ...base, canonicalName: entity.canonicalName, type: entity.type };
|
|
15559
16315
|
});
|
|
15560
16316
|
}
|
|
@@ -15729,24 +16485,24 @@ var DEFAULT_DECAY_PARAMS = {
|
|
|
15729
16485
|
halfLifeMs: 90 * DAY_MS2,
|
|
15730
16486
|
forgetThreshold: 0.05
|
|
15731
16487
|
};
|
|
15732
|
-
function lastReinforcedAt(
|
|
15733
|
-
if (
|
|
15734
|
-
return
|
|
16488
|
+
function lastReinforcedAt(record2) {
|
|
16489
|
+
if (record2.kind === "preference" && record2.lastAccessedAt !== null) {
|
|
16490
|
+
return record2.lastAccessedAt;
|
|
15735
16491
|
}
|
|
15736
|
-
return
|
|
16492
|
+
return record2.createdAt;
|
|
15737
16493
|
}
|
|
15738
|
-
function decayedConfidence(
|
|
15739
|
-
const ageMs = Math.max(0, now - lastReinforcedAt(
|
|
15740
|
-
return
|
|
16494
|
+
function decayedConfidence(record2, now, params = DEFAULT_DECAY_PARAMS) {
|
|
16495
|
+
const ageMs = Math.max(0, now - lastReinforcedAt(record2));
|
|
16496
|
+
return record2.confidence * 0.5 ** (ageMs / params.halfLifeMs);
|
|
15741
16497
|
}
|
|
15742
16498
|
function consolidate(records, now, params = DEFAULT_DECAY_PARAMS) {
|
|
15743
16499
|
const tombstones = [];
|
|
15744
|
-
for (const
|
|
15745
|
-
if (!isTransactionActiveAt(
|
|
16500
|
+
for (const record2 of records) {
|
|
16501
|
+
if (!isTransactionActiveAt(record2, now)) {
|
|
15746
16502
|
continue;
|
|
15747
16503
|
}
|
|
15748
|
-
if (decayedConfidence(
|
|
15749
|
-
tombstones.push({ ...
|
|
16504
|
+
if (decayedConfidence(record2, now, params) < params.forgetThreshold) {
|
|
16505
|
+
tombstones.push({ ...record2, invalidatedAt: now, supersededBy: null });
|
|
15750
16506
|
}
|
|
15751
16507
|
}
|
|
15752
16508
|
return tombstones;
|
|
@@ -15861,17 +16617,21 @@ async function loadConfig(path4, resolution = { explicit: false }) {
|
|
|
15861
16617
|
read: (requestedPath) => requestedPath === path4 ? readConfigFile(path4, resolution) : storage.read(requestedPath)
|
|
15862
16618
|
} : storage;
|
|
15863
16619
|
const overlayPath = resolution.explicit ? null : DEFAULT_LOCAL_OVERLAY_PATH;
|
|
15864
|
-
|
|
16620
|
+
const loaded = await loadRuntimeOpenTeamConfig(configStorage, undefined, path4, overlayPath);
|
|
16621
|
+
for (const deprecation of loaded.deprecations) {
|
|
16622
|
+
console.warn(deprecation.message);
|
|
16623
|
+
}
|
|
16624
|
+
return loaded.config;
|
|
15865
16625
|
}
|
|
15866
16626
|
async function saveConfig(config, path4, resolution = { explicit: false }) {
|
|
15867
16627
|
if (!resolution.explicit) {
|
|
15868
16628
|
await writeOpenTeamConfigFile(config, path4, storage);
|
|
15869
16629
|
return;
|
|
15870
16630
|
}
|
|
16631
|
+
const contents = serializeOpenTeamConfig(config);
|
|
15871
16632
|
const diskPath = resolve6(path4);
|
|
15872
16633
|
await mkdir2(dirname6(diskPath), { recursive: true });
|
|
15873
|
-
await writeFile2(diskPath,
|
|
15874
|
-
`, "utf8");
|
|
16634
|
+
await writeFile2(diskPath, contents, "utf8");
|
|
15875
16635
|
}
|
|
15876
16636
|
function parseConfigSelection(argv) {
|
|
15877
16637
|
for (let i = 0;i < argv.length; i += 1) {
|