@jmanuelcorral/openteam 0.18.1 → 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 +1348 -593
- 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 +3136 -1122
- 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 +19 -12
- 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 +5 -6
- 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 +50 -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.map +1 -1
- package/dist/telemetry/events.d.ts +6 -6
- 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);
|
|
@@ -649,7 +904,7 @@ function diagnosticDescription(code) {
|
|
|
649
904
|
case "opencode-version-read-failed":
|
|
650
905
|
return "openteam could not read the live opencode version; graph gates fail closed when this happens.";
|
|
651
906
|
case "privacy-inert":
|
|
652
|
-
return
|
|
907
|
+
return routingMessages.privacyModeDeprecated;
|
|
653
908
|
case "soak-evidence-rejected":
|
|
654
909
|
return "Graph soak evidence contained rejected observation lines; cutover gate evidence may be incomplete.";
|
|
655
910
|
case "telemetry-warning":
|
|
@@ -676,9 +931,9 @@ var RouteEventSchema = EventBaseSchema.extend({
|
|
|
676
931
|
routeKind: z4.enum(["local", "frontier", "blocked"]),
|
|
677
932
|
selected: ModelRefSchema,
|
|
678
933
|
rationale: z4.string(),
|
|
679
|
-
estimatedCostUSD: z4.number().finite().min(0),
|
|
680
|
-
baselineCostUSD: z4.number().finite().min(0),
|
|
681
|
-
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(),
|
|
682
937
|
budgetAction: z4.string().min(1),
|
|
683
938
|
tokensIn: z4.number().int().min(0).optional(),
|
|
684
939
|
tokensOut: z4.number().int().min(0).optional(),
|
|
@@ -892,9 +1147,9 @@ var CostRecordSchema = z5.object({
|
|
|
892
1147
|
routeKind: z5.enum(["local", "frontier", "blocked"]),
|
|
893
1148
|
selected: ModelRefSchema,
|
|
894
1149
|
rationale: z5.string(),
|
|
895
|
-
estimatedCostUSD: z5.number().finite().min(0),
|
|
896
|
-
baselineCostUSD: z5.number().finite().min(0),
|
|
897
|
-
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(),
|
|
898
1153
|
budgetAction: z5.string().min(1),
|
|
899
1154
|
tokensIn: z5.number().int().min(0).optional(),
|
|
900
1155
|
tokensOut: z5.number().int().min(0).optional()
|
|
@@ -1153,7 +1408,11 @@ var FAILURE_CAUSE_RULES = [
|
|
|
1153
1408
|
"bad request",
|
|
1154
1409
|
/(?<!\d)400(?!\d)/,
|
|
1155
1410
|
"unprocessable",
|
|
1156
|
-
"malformed"
|
|
1411
|
+
"malformed",
|
|
1412
|
+
"invalid model",
|
|
1413
|
+
"unknown model",
|
|
1414
|
+
"model not found",
|
|
1415
|
+
"model does not exist"
|
|
1157
1416
|
],
|
|
1158
1417
|
"prompt-validation"
|
|
1159
1418
|
],
|
|
@@ -1599,6 +1858,9 @@ function pushUnique(list, value) {
|
|
|
1599
1858
|
list.push(value);
|
|
1600
1859
|
}
|
|
1601
1860
|
}
|
|
1861
|
+
function addEstimate(current, value) {
|
|
1862
|
+
return current === null || value === null ? null : current + value;
|
|
1863
|
+
}
|
|
1602
1864
|
function applyRoute(session, event) {
|
|
1603
1865
|
session.routeCount += 1;
|
|
1604
1866
|
if (event.routeKind === "local") {
|
|
@@ -1606,8 +1868,8 @@ function applyRoute(session, event) {
|
|
|
1606
1868
|
} else if (event.routeKind === "frontier") {
|
|
1607
1869
|
session.frontierCount += 1;
|
|
1608
1870
|
}
|
|
1609
|
-
session.estimatedCostUSD
|
|
1610
|
-
session.estimatedSavingsUSD
|
|
1871
|
+
session.estimatedCostUSD = addEstimate(session.estimatedCostUSD, event.estimatedCostUSD);
|
|
1872
|
+
session.estimatedSavingsUSD = addEstimate(session.estimatedSavingsUSD, event.estimatedSavingsUSD);
|
|
1611
1873
|
pushUnique(session.models, `${event.selected.providerID}/${event.selected.modelID}`);
|
|
1612
1874
|
pushUnique(session.agents, event.agent);
|
|
1613
1875
|
}
|
|
@@ -1673,8 +1935,8 @@ function accumulateTotals(totals, session) {
|
|
|
1673
1935
|
totals.routeCount += session.routeCount;
|
|
1674
1936
|
totals.localCount += session.localCount;
|
|
1675
1937
|
totals.frontierCount += session.frontierCount;
|
|
1676
|
-
totals.estimatedCostUSD
|
|
1677
|
-
totals.estimatedSavingsUSD
|
|
1938
|
+
totals.estimatedCostUSD = addEstimate(totals.estimatedCostUSD, session.estimatedCostUSD);
|
|
1939
|
+
totals.estimatedSavingsUSD = addEstimate(totals.estimatedSavingsUSD, session.estimatedSavingsUSD);
|
|
1678
1940
|
totals.realCostUSD += session.realCostUSD;
|
|
1679
1941
|
totals.messageCount += session.messageCount;
|
|
1680
1942
|
totals.tokensIn += session.tokensIn;
|
|
@@ -1977,6 +2239,7 @@ var KNOWN_CONFIG_KEYS_BY_PATH = new Map([
|
|
|
1977
2239
|
"router",
|
|
1978
2240
|
[
|
|
1979
2241
|
"mode",
|
|
2242
|
+
"executionMode",
|
|
1980
2243
|
"localDefault",
|
|
1981
2244
|
"trivialPromptMaxChars",
|
|
1982
2245
|
"frontierPromptMinChars",
|
|
@@ -2063,7 +2326,9 @@ var KNOWN_CONFIG_KEYS_BY_PATH = new Map([
|
|
|
2063
2326
|
]
|
|
2064
2327
|
],
|
|
2065
2328
|
["graph.worktrees", ["enabled"]],
|
|
2066
|
-
["orchestrator", ["roles", "maxRetries"]]
|
|
2329
|
+
["orchestrator", ["roles", "primary", "maxRetries", "maxModelsPerNode"]],
|
|
2330
|
+
["orchestrator.primary", ["executionMode", "model", "fallbacks"]],
|
|
2331
|
+
["orchestrator.primary.fallbacks[]", MODEL_REF_KEYS]
|
|
2067
2332
|
]);
|
|
2068
2333
|
function isUnrecognizedKeysIssue(issue) {
|
|
2069
2334
|
return issue.code === "unrecognized_keys" && Array.isArray(issue.keys);
|
|
@@ -2169,6 +2434,211 @@ function formatConfigError(error) {
|
|
|
2169
2434
|
});
|
|
2170
2435
|
}
|
|
2171
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
|
+
|
|
2172
2642
|
// src/config/load.ts
|
|
2173
2643
|
function loadOpenTeamConfig(raw) {
|
|
2174
2644
|
return OpenTeamConfigSchema.parse(migrateConfig(raw ?? {}));
|
|
@@ -2189,9 +2659,76 @@ async function readOpenTeamConfigFile(path = ".opencode/openteam.json", storage
|
|
|
2189
2659
|
// src/config/persist.ts
|
|
2190
2660
|
var DEFAULT_CONFIG_PATH = ".opencode/openteam.json";
|
|
2191
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
|
+
}
|
|
2192
2694
|
function serializeOpenTeamConfig(config) {
|
|
2193
2695
|
const validated = OpenTeamConfigSchema.parse(config);
|
|
2194
|
-
|
|
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)}
|
|
2195
2732
|
`;
|
|
2196
2733
|
}
|
|
2197
2734
|
async function writeOpenTeamConfigFile(config, path = DEFAULT_CONFIG_PATH, storage = createFsStorageProvider(process.cwd())) {
|
|
@@ -2328,6 +2865,7 @@ async function loadRuntimeOpenTeamConfig(storage, options, path = DEFAULT_CONFIG
|
|
|
2328
2865
|
].filter((entry) => entry !== undefined);
|
|
2329
2866
|
const label = mergedSources.length === 0 ? "runtime config" : `runtime config merged from ${mergedSources.join(", ")}`;
|
|
2330
2867
|
const config = validateConfigInput(label, mergedRaw);
|
|
2868
|
+
const deprecations = detectConfigDeprecations(mergedRaw);
|
|
2331
2869
|
const source = optionsFound ? "options" : fileFound ? "file" : "default";
|
|
2332
2870
|
return {
|
|
2333
2871
|
config,
|
|
@@ -2335,6 +2873,7 @@ async function loadRuntimeOpenTeamConfig(storage, options, path = DEFAULT_CONFIG
|
|
|
2335
2873
|
fileFound,
|
|
2336
2874
|
optionsFound,
|
|
2337
2875
|
overlayFound,
|
|
2876
|
+
deprecations,
|
|
2338
2877
|
searchedPaths: overlayPath === null ? [path] : [path, overlayPath]
|
|
2339
2878
|
};
|
|
2340
2879
|
}
|
|
@@ -2454,6 +2993,10 @@ function scrubSecretStrings(value, secrets) {
|
|
|
2454
2993
|
};
|
|
2455
2994
|
return walk(value);
|
|
2456
2995
|
}
|
|
2996
|
+
function withoutDeprecatedPrivacyMode(config) {
|
|
2997
|
+
const { privacyMode: _privacyMode, ...next } = config;
|
|
2998
|
+
return next;
|
|
2999
|
+
}
|
|
2457
3000
|
var DEFAULT_CONFIG_AUDIT_PATH = ".opencode/openteam-local/config-audit.jsonl";
|
|
2458
3001
|
function editableSurface() {
|
|
2459
3002
|
return [
|
|
@@ -2491,7 +3034,7 @@ function createConfigEditor(ports) {
|
|
|
2491
3034
|
editableSurface,
|
|
2492
3035
|
async read() {
|
|
2493
3036
|
const resolution = await loadRuntimeOpenTeamConfig(ports.storage, undefined, basePath, overlayPath);
|
|
2494
|
-
const scrubbed = scrubSecretStrings(resolution.config, secretValues());
|
|
3037
|
+
const scrubbed = withoutDeprecatedPrivacyMode(scrubSecretStrings(resolution.config, secretValues()));
|
|
2495
3038
|
return {
|
|
2496
3039
|
config: scrubbed,
|
|
2497
3040
|
reload: {
|
|
@@ -2513,6 +3056,7 @@ function createConfigEditor(ports) {
|
|
|
2513
3056
|
let submitted;
|
|
2514
3057
|
try {
|
|
2515
3058
|
submitted = OpenTeamConfigObjectSchema.parse(submittedRaw);
|
|
3059
|
+
assertEffectiveConfigModelDomains(submitted);
|
|
2516
3060
|
} catch (error) {
|
|
2517
3061
|
return {
|
|
2518
3062
|
status: 400,
|
|
@@ -2578,7 +3122,6 @@ var RESTART_REQUIRED_KEYS = [
|
|
|
2578
3122
|
"baseline",
|
|
2579
3123
|
"router",
|
|
2580
3124
|
"local",
|
|
2581
|
-
"privacyMode",
|
|
2582
3125
|
"graph",
|
|
2583
3126
|
"orchestrator",
|
|
2584
3127
|
"memory",
|
|
@@ -2586,6 +3129,110 @@ var RESTART_REQUIRED_KEYS = [
|
|
|
2586
3129
|
"console.port"
|
|
2587
3130
|
];
|
|
2588
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
|
+
|
|
2589
3236
|
// src/web/graphRoute.ts
|
|
2590
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.';
|
|
2591
3238
|
var GRAPH_DISABLED_ENVELOPE = {
|
|
@@ -2995,10 +3642,10 @@ function escapeHtml(value) {
|
|
|
2995
3642
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2996
3643
|
}
|
|
2997
3644
|
function usd(value) {
|
|
2998
|
-
return `$${value.toFixed(5)}`;
|
|
3645
|
+
return value === null ? routingMessages.unknownCost : `$${value.toFixed(5)}`;
|
|
2999
3646
|
}
|
|
3000
3647
|
function pct(value) {
|
|
3001
|
-
return `${value.toFixed(2)}%`;
|
|
3648
|
+
return value === null ? routingMessages.unknownCost : `${value.toFixed(2)}%`;
|
|
3002
3649
|
}
|
|
3003
3650
|
function ms(value) {
|
|
3004
3651
|
return value >= 1000 ? `${(value / 1000).toFixed(2)}s` : `${Math.round(value)}ms`;
|
|
@@ -3694,6 +4341,42 @@ var OVERLAY_PATH = ".opencode/openteam-local/config.local.json";
|
|
|
3694
4341
|
function codeList(keys) {
|
|
3695
4342
|
return keys.map((key) => `<code>${escapeHtml(key)}</code>`).join(", ");
|
|
3696
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
|
+
}
|
|
3697
4380
|
function renderConfigReloadTable(read) {
|
|
3698
4381
|
const hot = read.reload.hotReloadable.length > 0 ? codeList(read.reload.hotReloadable) : '<span class="muted">none</span>';
|
|
3699
4382
|
const restart = read.reload.restartRequired.length > 0 ? codeList(read.reload.restartRequired) : '<span class="muted">none</span>';
|
|
@@ -3713,6 +4396,7 @@ function renderConfigEditorForm(read) {
|
|
|
3713
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>` : "";
|
|
3714
4397
|
return [
|
|
3715
4398
|
'<form class="config-form" data-config-form>',
|
|
4399
|
+
renderExecutionPolicyOverview(read.config),
|
|
3716
4400
|
renderConfigReloadTable(read),
|
|
3717
4401
|
overlayNote,
|
|
3718
4402
|
secretsNote,
|
|
@@ -3911,6 +4595,9 @@ function emptyTierSummary() {
|
|
|
3911
4595
|
hard: { count: 0, estimatedUSD: 0, savingsUSD: 0 }
|
|
3912
4596
|
};
|
|
3913
4597
|
}
|
|
4598
|
+
function addEstimate2(current, value) {
|
|
4599
|
+
return current === null || value === null ? null : current + value;
|
|
4600
|
+
}
|
|
3914
4601
|
function summarizeCostRecords(records) {
|
|
3915
4602
|
const report = {
|
|
3916
4603
|
count: 0,
|
|
@@ -3925,30 +4612,33 @@ function summarizeCostRecords(records) {
|
|
|
3925
4612
|
tokensOut: 0,
|
|
3926
4613
|
byTier: emptyTierSummary()
|
|
3927
4614
|
};
|
|
3928
|
-
for (const
|
|
4615
|
+
for (const record2 of records) {
|
|
3929
4616
|
report.count += 1;
|
|
3930
|
-
if (
|
|
4617
|
+
if (record2.routeKind === "local") {
|
|
3931
4618
|
report.localCount += 1;
|
|
3932
|
-
} else if (
|
|
4619
|
+
} else if (record2.routeKind === "blocked") {
|
|
3933
4620
|
report.blockedCount += 1;
|
|
3934
4621
|
} else {
|
|
3935
4622
|
report.frontierCount += 1;
|
|
3936
4623
|
}
|
|
3937
|
-
report.totalEstimatedUSD
|
|
3938
|
-
report.totalBaselineUSD
|
|
3939
|
-
report.totalSavingsUSD
|
|
3940
|
-
report.tokensIn +=
|
|
3941
|
-
report.tokensOut +=
|
|
3942
|
-
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];
|
|
3943
4630
|
tier.count += 1;
|
|
3944
|
-
tier.estimatedUSD
|
|
3945
|
-
tier.savingsUSD
|
|
4631
|
+
tier.estimatedUSD = addEstimate2(tier.estimatedUSD, record2.estimatedCostUSD);
|
|
4632
|
+
tier.savingsUSD = addEstimate2(tier.savingsUSD, record2.estimatedSavingsUSD);
|
|
3946
4633
|
}
|
|
3947
|
-
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;
|
|
3948
4635
|
return report;
|
|
3949
4636
|
}
|
|
3950
4637
|
function usd2(value) {
|
|
3951
|
-
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)}%`;
|
|
3952
4642
|
}
|
|
3953
4643
|
function renderReport(report) {
|
|
3954
4644
|
if (report.count === 0) {
|
|
@@ -3959,7 +4649,7 @@ function renderReport(report) {
|
|
|
3959
4649
|
` decisions: ${report.count} (local ${report.localCount} · frontier ${report.frontierCount} · blocked ${report.blockedCount})`,
|
|
3960
4650
|
` real cost: ${usd2(report.totalEstimatedUSD)}`,
|
|
3961
4651
|
` baseline: ${usd2(report.totalBaselineUSD)}`,
|
|
3962
|
-
` savings: ${usd2(report.totalSavingsUSD)} (${report.savingsPct
|
|
4652
|
+
` savings: ${usd2(report.totalSavingsUSD)} (${pct2(report.savingsPct)})`,
|
|
3963
4653
|
` tokens: in ${report.tokensIn} · out ${report.tokensOut}`,
|
|
3964
4654
|
" by tier:"
|
|
3965
4655
|
];
|
|
@@ -4044,16 +4734,16 @@ var DEFAULT_RECENT_ROUTES = 50;
|
|
|
4044
4734
|
var DEFAULT_ACTIVITY_LIMIT = 100;
|
|
4045
4735
|
var DEFAULT_DECISIONS_LIMIT = 100;
|
|
4046
4736
|
var DEFAULT_MEETINGS_LIMIT = 50;
|
|
4047
|
-
function toRouteView(
|
|
4737
|
+
function toRouteView(record2) {
|
|
4048
4738
|
return {
|
|
4049
|
-
ts:
|
|
4050
|
-
tier:
|
|
4051
|
-
routeKind:
|
|
4052
|
-
model: `${
|
|
4053
|
-
promptHash:
|
|
4054
|
-
estimatedCostUSD:
|
|
4055
|
-
estimatedSavingsUSD:
|
|
4056
|
-
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
|
|
4057
4747
|
};
|
|
4058
4748
|
}
|
|
4059
4749
|
function isRoute(event) {
|
|
@@ -4106,7 +4796,7 @@ function activityFeed(aggregate, limit) {
|
|
|
4106
4796
|
function buildSessionTabs(aggregate, costRecords, nowMs, limits, options) {
|
|
4107
4797
|
return aggregate.sessions.map((summary) => {
|
|
4108
4798
|
const id = summary.sessionID;
|
|
4109
|
-
const routes = costRecords.filter((
|
|
4799
|
+
const routes = costRecords.filter((record2) => record2.sessionID === id);
|
|
4110
4800
|
const decisions = aggregate.decisions.filter((decision) => decision.sessionID === id).slice(0, Math.max(0, limits.decisionsLimit));
|
|
4111
4801
|
const meetings = aggregate.meetings.filter((meeting) => meeting.sessionID === id).slice(0, Math.max(0, limits.meetingsLimit));
|
|
4112
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);
|
|
@@ -4284,9 +4974,9 @@ async function handlePermission(request, response, sessionID, deps) {
|
|
|
4284
4974
|
return;
|
|
4285
4975
|
}
|
|
4286
4976
|
const body = await readJsonBody2(request, 4096);
|
|
4287
|
-
const
|
|
4288
|
-
const permissionID =
|
|
4289
|
-
const responseValue =
|
|
4977
|
+
const record2 = typeof body === "object" && body !== null ? body : {};
|
|
4978
|
+
const permissionID = record2.permissionID;
|
|
4979
|
+
const responseValue = record2.response;
|
|
4290
4980
|
if (typeof permissionID !== "string" || permissionID.length === 0 || responseValue !== "once" && responseValue !== "always" && responseValue !== "reject") {
|
|
4291
4981
|
response.writeHead(400, JSON_HEADERS3).end(JSON.stringify({ error: "invalid permission" }));
|
|
4292
4982
|
return;
|
|
@@ -4446,8 +5136,8 @@ async function handleWrite(request, response, op, deps) {
|
|
|
4446
5136
|
return;
|
|
4447
5137
|
}
|
|
4448
5138
|
const body = await readJsonBody3(request, 8 * 1024 * 1024);
|
|
4449
|
-
const
|
|
4450
|
-
const rawPath =
|
|
5139
|
+
const record2 = typeof body === "object" && body !== null ? body : undefined;
|
|
5140
|
+
const rawPath = record2?.path;
|
|
4451
5141
|
if (typeof rawPath !== "string") {
|
|
4452
5142
|
sendJson(response, 400, { error: "invalid path" });
|
|
4453
5143
|
return;
|
|
@@ -4467,7 +5157,7 @@ async function handleWrite(request, response, op, deps) {
|
|
|
4467
5157
|
return;
|
|
4468
5158
|
}
|
|
4469
5159
|
if (op === "write" || op === "append") {
|
|
4470
|
-
const content =
|
|
5160
|
+
const content = record2?.content;
|
|
4471
5161
|
if (typeof content !== "string") {
|
|
4472
5162
|
sendJson(response, 400, { error: "invalid content" });
|
|
4473
5163
|
return;
|
|
@@ -4845,7 +5535,7 @@ function classifyAgent(info, context) {
|
|
|
4845
5535
|
name: info.name,
|
|
4846
5536
|
mode: info.mode,
|
|
4847
5537
|
kind: "inherited",
|
|
4848
|
-
subscription:
|
|
5538
|
+
subscription: executionPolicyMessages.inheritedDefaultUnset
|
|
4849
5539
|
};
|
|
4850
5540
|
}
|
|
4851
5541
|
const { subscription } = subscriptionLabel(context.defaultModel, context.localProviders);
|
|
@@ -4854,7 +5544,7 @@ function classifyAgent(info, context) {
|
|
|
4854
5544
|
mode: info.mode,
|
|
4855
5545
|
kind: "inherited",
|
|
4856
5546
|
model: context.defaultModel,
|
|
4857
|
-
subscription:
|
|
5547
|
+
subscription: executionPolicyMessages.inheritedDefault(subscription)
|
|
4858
5548
|
};
|
|
4859
5549
|
}
|
|
4860
5550
|
function modeRank(mode) {
|
|
@@ -4875,6 +5565,51 @@ function classifyAgents(files, context) {
|
|
|
4875
5565
|
function modelText(classification) {
|
|
4876
5566
|
return classification.model === undefined ? "" : ` (${classification.model.providerID}/${classification.model.modelID})`;
|
|
4877
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
|
+
}
|
|
4878
5613
|
function renderAgentList(classifications, context) {
|
|
4879
5614
|
const lines = ["openteam agents — LLM per agent:", ""];
|
|
4880
5615
|
if (classifications.length === 0) {
|
|
@@ -4901,6 +5636,15 @@ function renderAgentList(classifications, context) {
|
|
|
4901
5636
|
lines.push("opencode.json default: (not set)");
|
|
4902
5637
|
}
|
|
4903
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
|
+
}
|
|
4904
5648
|
if (context.opencodeConfigError !== undefined) {
|
|
4905
5649
|
lines.push("");
|
|
4906
5650
|
lines.push(`⚠ opencode config parse error: ${context.opencodeConfigError}`);
|
|
@@ -4923,22 +5667,22 @@ var KNOWN_FRONTIER_PROVIDER_IDS = new Set([
|
|
|
4923
5667
|
"openrouter"
|
|
4924
5668
|
]);
|
|
4925
5669
|
var VIRTUAL_ROUTER_PROVIDER_ID = "openteam-router";
|
|
4926
|
-
function
|
|
5670
|
+
function isRecord2(value) {
|
|
4927
5671
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4928
5672
|
}
|
|
4929
5673
|
function parseOpencodeProviders(opencodeConfig) {
|
|
4930
5674
|
const result = new Map;
|
|
4931
5675
|
const provider = opencodeConfig?.provider;
|
|
4932
|
-
if (!
|
|
5676
|
+
if (!isRecord2(provider)) {
|
|
4933
5677
|
return result;
|
|
4934
5678
|
}
|
|
4935
5679
|
for (const [id, value] of Object.entries(provider)) {
|
|
4936
|
-
if (!
|
|
5680
|
+
if (!isRecord2(value)) {
|
|
4937
5681
|
result.set(id, {});
|
|
4938
5682
|
continue;
|
|
4939
5683
|
}
|
|
4940
5684
|
const models = value.models;
|
|
4941
|
-
result.set(id,
|
|
5685
|
+
result.set(id, isRecord2(models) ? { models: new Set(Object.keys(models)) } : {});
|
|
4942
5686
|
}
|
|
4943
5687
|
return result;
|
|
4944
5688
|
}
|
|
@@ -5680,8 +6424,8 @@ function canonical(value) {
|
|
|
5680
6424
|
if (value === null || typeof value !== "object") {
|
|
5681
6425
|
return JSON.stringify(value);
|
|
5682
6426
|
}
|
|
5683
|
-
const
|
|
5684
|
-
const entries = Object.keys(
|
|
6427
|
+
const record2 = value;
|
|
6428
|
+
const entries = Object.keys(record2).sort().map((key) => `${JSON.stringify(key)}:${canonical(record2[key])}`);
|
|
5685
6429
|
return `{${entries.join(",")}}`;
|
|
5686
6430
|
}
|
|
5687
6431
|
function frameDigest(prev, event) {
|
|
@@ -5942,7 +6686,7 @@ function createFsGraphJournal(root) {
|
|
|
5942
6686
|
|
|
5943
6687
|
// src/telemetry/graphProjection.ts
|
|
5944
6688
|
function projectGraphEvent(event) {
|
|
5945
|
-
const
|
|
6689
|
+
const record2 = {
|
|
5946
6690
|
v: event.v,
|
|
5947
6691
|
seq: event.seq,
|
|
5948
6692
|
runID: event.runID,
|
|
@@ -5955,8 +6699,8 @@ function projectGraphEvent(event) {
|
|
|
5955
6699
|
..."specDigest" in event ? { specDigest: event.specDigest } : {},
|
|
5956
6700
|
..."artifact" in event ? { artifact: redactedRef(event.artifact) } : {}
|
|
5957
6701
|
};
|
|
5958
|
-
assertReferenceOnly(
|
|
5959
|
-
return
|
|
6702
|
+
assertReferenceOnly(record2);
|
|
6703
|
+
return record2;
|
|
5960
6704
|
}
|
|
5961
6705
|
|
|
5962
6706
|
// src/telemetry/graphRuntimeHealth.ts
|
|
@@ -6047,18 +6791,18 @@ function indexTelemetry(telemetry) {
|
|
|
6047
6791
|
const lastError = new Map;
|
|
6048
6792
|
const lastFailure = new Map;
|
|
6049
6793
|
const lastArtifact = new Map;
|
|
6050
|
-
for (const
|
|
6051
|
-
if (
|
|
6794
|
+
for (const record2 of telemetry) {
|
|
6795
|
+
if (record2.nodeID === undefined) {
|
|
6052
6796
|
continue;
|
|
6053
6797
|
}
|
|
6054
|
-
if (
|
|
6055
|
-
lastError.set(
|
|
6798
|
+
if (record2.type === "node.failed" && record2.errorClass !== undefined) {
|
|
6799
|
+
lastError.set(record2.nodeID, record2.errorClass);
|
|
6056
6800
|
}
|
|
6057
|
-
if (
|
|
6058
|
-
lastFailure.set(
|
|
6801
|
+
if (record2.type === "node.failed" && record2.failureClass !== undefined) {
|
|
6802
|
+
lastFailure.set(record2.nodeID, record2.failureClass);
|
|
6059
6803
|
}
|
|
6060
|
-
if (
|
|
6061
|
-
lastArtifact.set(
|
|
6804
|
+
if (record2.type === "node.succeeded" && record2.artifact !== undefined) {
|
|
6805
|
+
lastArtifact.set(record2.nodeID, record2.artifact);
|
|
6062
6806
|
}
|
|
6063
6807
|
}
|
|
6064
6808
|
return { lastError, lastFailure, lastArtifact };
|
|
@@ -6249,8 +6993,8 @@ async function runConsoleServe(deps, options) {
|
|
|
6249
6993
|
load: () => Promise.resolve(deps.loadConfig(deps.configPath)),
|
|
6250
6994
|
configPath: deps.configPath
|
|
6251
6995
|
});
|
|
6252
|
-
const auditSink = async (
|
|
6253
|
-
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)}
|
|
6254
6998
|
`);
|
|
6255
6999
|
};
|
|
6256
7000
|
const configEditor = createConfigEditor({
|
|
@@ -6343,11 +7087,6 @@ async function runConsoleServe(deps, options) {
|
|
|
6343
7087
|
// src/cli/graphOperator.ts
|
|
6344
7088
|
import { join as join8 } from "node:path";
|
|
6345
7089
|
|
|
6346
|
-
// src/graph/privacyPolicy.ts
|
|
6347
|
-
function frontierAllowed(privacyClass, mode) {
|
|
6348
|
-
return !(privacyClass === "sensitive" && mode === "forceLocalOnSensitive");
|
|
6349
|
-
}
|
|
6350
|
-
|
|
6351
7090
|
// src/graph/langgraph/reducer.ts
|
|
6352
7091
|
function reduceAdapterState(current, update) {
|
|
6353
7092
|
const route = update.route !== undefined ? update.route : current.route;
|
|
@@ -6362,21 +7101,22 @@ function reduceAdapterState(current, update) {
|
|
|
6362
7101
|
|
|
6363
7102
|
// src/graph/langgraph/adapter.ts
|
|
6364
7103
|
var GRAPH_END = "__end__";
|
|
6365
|
-
function resolveNodeRoute(
|
|
6366
|
-
return
|
|
7104
|
+
function resolveNodeRoute(preferredOrPrivacyClass, _legacyMode, legacyPreferred) {
|
|
7105
|
+
return legacyPreferred ?? preferredOrPrivacyClass;
|
|
6367
7106
|
}
|
|
6368
7107
|
var routeNode = (state) => ({
|
|
6369
|
-
route: resolveNodeRoute(state.input.
|
|
7108
|
+
route: resolveNodeRoute(state.input.preferredRoute),
|
|
6370
7109
|
visited: ["route"]
|
|
6371
7110
|
});
|
|
6372
7111
|
var dispatchNode = async (state, deps) => {
|
|
6373
|
-
const route = state.route ?? resolveNodeRoute(state.input.
|
|
6374
|
-
const
|
|
7112
|
+
const route = state.route ?? resolveNodeRoute(state.input.preferredRoute);
|
|
7113
|
+
const request = {
|
|
6375
7114
|
nodeID: state.input.nodeID,
|
|
6376
7115
|
route,
|
|
6377
|
-
privacyClass: state.input.privacyClass,
|
|
6378
|
-
mode: state.input.mode
|
|
6379
|
-
}
|
|
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);
|
|
6380
7120
|
return { receipt, visited: ["dispatch"] };
|
|
6381
7121
|
};
|
|
6382
7122
|
function buildAdapterDefinition() {
|
|
@@ -6441,7 +7181,7 @@ function selectCheckpointer(location) {
|
|
|
6441
7181
|
function resolveGraphEngine(config) {
|
|
6442
7182
|
return config.graph?.engine ?? "builtin";
|
|
6443
7183
|
}
|
|
6444
|
-
function createLangGraphEffects(nodeExecutor) {
|
|
7184
|
+
function createLangGraphEffects(nodeExecutor, defaultRoute = "local") {
|
|
6445
7185
|
const definition = buildAdapterDefinition();
|
|
6446
7186
|
return {
|
|
6447
7187
|
async execute(request) {
|
|
@@ -6450,11 +7190,9 @@ function createLangGraphEffects(nodeExecutor) {
|
|
|
6450
7190
|
}
|
|
6451
7191
|
const input = {
|
|
6452
7192
|
nodeID: request.node.id,
|
|
6453
|
-
|
|
6454
|
-
mode: "forceLocalOnSensitive",
|
|
6455
|
-
preferredRoute: "local"
|
|
7193
|
+
preferredRoute: request.routeKind ?? defaultRoute
|
|
6456
7194
|
};
|
|
6457
|
-
const resolvedRoute = resolveNodeRoute(input.
|
|
7195
|
+
const resolvedRoute = resolveNodeRoute(input.preferredRoute);
|
|
6458
7196
|
let state;
|
|
6459
7197
|
try {
|
|
6460
7198
|
state = await invokeStateGraph(definition, input, {
|
|
@@ -6647,8 +7385,8 @@ function createWorkspaceLease(root, registry = createRunRegistry(root)) {
|
|
|
6647
7385
|
return text === undefined ? undefined : parse2(text);
|
|
6648
7386
|
};
|
|
6649
7387
|
const grant = async (runID, holder, token) => {
|
|
6650
|
-
const
|
|
6651
|
-
writeDurable(leasePath(runID), JSON.stringify(
|
|
7388
|
+
const record2 = { runID, holder, token, released: false };
|
|
7389
|
+
writeDurable(leasePath(runID), JSON.stringify(record2));
|
|
6652
7390
|
await registry.markActive(runID);
|
|
6653
7391
|
return { runID, holder, token };
|
|
6654
7392
|
};
|
|
@@ -7379,19 +8117,19 @@ function createWorktreeAdapter(config) {
|
|
|
7379
8117
|
const base = `${canonicalizePath(worktreesDir)}/`;
|
|
7380
8118
|
return canonicalizePath(worktreePath).startsWith(base);
|
|
7381
8119
|
};
|
|
7382
|
-
const toInfo = (
|
|
7383
|
-
runID: runIDFromPath(
|
|
7384
|
-
path:
|
|
7385
|
-
head:
|
|
7386
|
-
detached:
|
|
7387
|
-
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
|
|
7388
8126
|
});
|
|
7389
8127
|
const list = async () => {
|
|
7390
8128
|
const output = await runGit(["worktree", "list", "--porcelain"]);
|
|
7391
8129
|
if (output.exitCode !== 0) {
|
|
7392
8130
|
throw new WorktreeError("git-failed", `worktree list: ${output.stderr.trim() || output.exitCode}`);
|
|
7393
8131
|
}
|
|
7394
|
-
return parsePorcelain(output.stdout).filter((
|
|
8132
|
+
return parsePorcelain(output.stdout).filter((record2) => isOurs(record2.path)).map(toInfo);
|
|
7395
8133
|
};
|
|
7396
8134
|
const findExisting = async (expected) => (await list()).find((info) => samePath(info.path, expected));
|
|
7397
8135
|
const addWorktree = async (runID, expected) => runGit([
|
|
@@ -7931,7 +8669,7 @@ function buildOrchestratorAgent(model, options = {}) {
|
|
|
7931
8669
|
];
|
|
7932
8670
|
const frontmatter = [
|
|
7933
8671
|
"---",
|
|
7934
|
-
'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."',
|
|
7935
8673
|
"mode: primary",
|
|
7936
8674
|
`model: ${modelSpec}`,
|
|
7937
8675
|
"temperature: 0.2",
|
|
@@ -8053,10 +8791,9 @@ function buildOrchestratorAgent(model, options = {}) {
|
|
|
8053
8791
|
" global one, so an `ask`/`deny` in the subagent reintroduces the",
|
|
8054
8792
|
" prompts. If YOLO is disabled, use minimal permissions (leave `bash`",
|
|
8055
8793
|
" in ask mode).",
|
|
8056
|
-
" - Leave `model` unset
|
|
8057
|
-
"
|
|
8058
|
-
"
|
|
8059
|
-
" 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.",
|
|
8060
8797
|
" - A focused, actionable role prompt.",
|
|
8061
8798
|
"4. Register the cast with the `openteam-register-cast` tool and **hand out",
|
|
8062
8799
|
" the tasks** to the newly created team with the `task` tool.",
|
|
@@ -8070,22 +8807,20 @@ function buildOrchestratorAgent(model, options = {}) {
|
|
|
8070
8807
|
"",
|
|
8071
8808
|
"- **scribe** — the team's silent memory. Records decisions and",
|
|
8072
8809
|
" learnings in a shared log (`.opencode/openteam/decisions.md`) without",
|
|
8073
|
-
" executing code changes.
|
|
8074
|
-
"
|
|
8810
|
+
" executing code changes. Use read-only permissions plus editing of that",
|
|
8811
|
+
" log. **Parseable format** for the console: one decision",
|
|
8075
8812
|
" per line as a Markdown list item, already **redacted** (no prompts or",
|
|
8076
|
-
"
|
|
8813
|
+
" credentials):",
|
|
8077
8814
|
" `- YYYY-MM-DD [agent] Short summary of the decision #tag1 #tag2`",
|
|
8078
8815
|
" The date, the `[agent]` and the `#tags` are optional; the summary is",
|
|
8079
8816
|
" required.",
|
|
8080
8817
|
"- **ralph** — automation and triage. Monitors pending work",
|
|
8081
8818
|
" (issues/tasks), prioritizes it, coordinates execution by delegating to the",
|
|
8082
8819
|
" specialists and **escalates to the human** on blockers, risks or approvals.",
|
|
8083
|
-
"- **guardian** — security
|
|
8084
|
-
"
|
|
8085
|
-
"
|
|
8086
|
-
"
|
|
8087
|
-
" lets sensitive data through to frontier. Reviews the inputs and outputs of the",
|
|
8088
|
-
" 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.",
|
|
8089
8824
|
"",
|
|
8090
8825
|
"## Work planning and parallelization",
|
|
8091
8826
|
"",
|
|
@@ -8105,10 +8840,9 @@ function buildOrchestratorAgent(model, options = {}) {
|
|
|
8105
8840
|
"4. **Do not parallelize work that collides** on the same files or",
|
|
8106
8841
|
" state: those are implicit dependencies; split it into separate waves to",
|
|
8107
8842
|
" avoid write conflicts.",
|
|
8108
|
-
"5. Distribute by **capability
|
|
8109
|
-
"
|
|
8110
|
-
"
|
|
8111
|
-
" 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.",
|
|
8112
8846
|
"",
|
|
8113
8847
|
"Briefly explain your plan to the user (what runs in parallel and what in series, and",
|
|
8114
8848
|
"why) before or while you launch the tasks.",
|
|
@@ -8202,8 +8936,8 @@ function buildOrchestratorAgent(model, options = {}) {
|
|
|
8202
8936
|
"- Create subagents **exactly when the work calls for it**, never “just in case”.",
|
|
8203
8937
|
` Review \`.opencode/agent/\` and the \`${OPENTEAM_ROSTER_FILENAME}\`, and reuse the`,
|
|
8204
8938
|
" existing ones before creating a new one.",
|
|
8205
|
-
"- Respect openteam's
|
|
8206
|
-
"
|
|
8939
|
+
"- Respect openteam's configured execution domains, exact pins and ordered",
|
|
8940
|
+
" fallback chains. Assign `guardian` explicitly when security review is needed.",
|
|
8207
8941
|
"- Summarize for the user which universe you chose, which team you created and why.",
|
|
8208
8942
|
"",
|
|
8209
8943
|
"## Limits",
|
|
@@ -8272,8 +9006,8 @@ function buildOpenteamCommands() {
|
|
|
8272
9006
|
return [
|
|
8273
9007
|
file("openteam", buildOpenteamCommand()),
|
|
8274
9008
|
file("openteam-baseline", buildBaselineCommand()),
|
|
8275
|
-
file("openteam-doctor", renderCommand("openteam · diagnostics for
|
|
8276
|
-
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"))),
|
|
8277
9011
|
file("openteam-console", renderCommand("openteam · status and URL of the multi-session web Console", [
|
|
8278
9012
|
"> ℹ️ The Console interface may still evolve between releases.",
|
|
8279
9013
|
"",
|
|
@@ -8490,7 +9224,7 @@ var CURATED_FRONTIER_PROFILES = [
|
|
|
8490
9224
|
];
|
|
8491
9225
|
|
|
8492
9226
|
// src/capabilities/normalize.ts
|
|
8493
|
-
function
|
|
9227
|
+
function isRecord3(value) {
|
|
8494
9228
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8495
9229
|
}
|
|
8496
9230
|
function stringValue(value) {
|
|
@@ -8511,7 +9245,7 @@ function boolValue(value) {
|
|
|
8511
9245
|
}
|
|
8512
9246
|
function nestedRecord(value, key) {
|
|
8513
9247
|
const nested = value[key];
|
|
8514
|
-
return
|
|
9248
|
+
return isRecord3(nested) ? nested : undefined;
|
|
8515
9249
|
}
|
|
8516
9250
|
function nestedBoolean(value, firstKey, secondKey) {
|
|
8517
9251
|
const nested = nestedRecord(value, firstKey);
|
|
@@ -8524,7 +9258,7 @@ function includesImage(value) {
|
|
|
8524
9258
|
if (Array.isArray(value)) {
|
|
8525
9259
|
return value.some(includesImage);
|
|
8526
9260
|
}
|
|
8527
|
-
if (
|
|
9261
|
+
if (isRecord3(value)) {
|
|
8528
9262
|
return Object.values(value).some(includesImage);
|
|
8529
9263
|
}
|
|
8530
9264
|
return false;
|
|
@@ -8543,7 +9277,7 @@ function tierFromModelName(modelID, reasoning) {
|
|
|
8543
9277
|
if (typeof reasoning === "number") {
|
|
8544
9278
|
return Math.max(0, Math.min(5, Math.trunc(reasoning)));
|
|
8545
9279
|
}
|
|
8546
|
-
if (reasoning === true ||
|
|
9280
|
+
if (reasoning === true || isRecord3(reasoning)) {
|
|
8547
9281
|
return normalized.includes("flash") || normalized.includes("mini") ? 3 : 4;
|
|
8548
9282
|
}
|
|
8549
9283
|
if (normalized.includes("opus") || normalized.includes("codex")) {
|
|
@@ -8573,31 +9307,44 @@ function codeTierFromModelName(modelID) {
|
|
|
8573
9307
|
function availability(value) {
|
|
8574
9308
|
return value === "degraded" || value === "unavailable" ? value : "available";
|
|
8575
9309
|
}
|
|
9310
|
+
function modelAvailability(availabilityValue, status) {
|
|
9311
|
+
if (availabilityValue === "degraded" || status === "deprecated") {
|
|
9312
|
+
return "degraded";
|
|
9313
|
+
}
|
|
9314
|
+
return availability(availabilityValue);
|
|
9315
|
+
}
|
|
8576
9316
|
function modelEntriesFromProvider(providerID, provider) {
|
|
8577
9317
|
const models = provider.models;
|
|
8578
9318
|
if (Array.isArray(models)) {
|
|
8579
|
-
return models.filter(
|
|
8580
|
-
providerID,
|
|
9319
|
+
return models.filter(isRecord3).map((model) => ({
|
|
9320
|
+
providerID: stringValue(model.providerID) ?? providerID,
|
|
8581
9321
|
modelID: stringValue(model.id) ?? stringValue(model.modelID) ?? "",
|
|
8582
9322
|
value: model
|
|
8583
9323
|
})).filter((entry) => entry.modelID.length > 0);
|
|
8584
9324
|
}
|
|
8585
|
-
if (
|
|
8586
|
-
return Object.entries(models).
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
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
|
+
});
|
|
8591
9338
|
}
|
|
8592
9339
|
return [];
|
|
8593
9340
|
}
|
|
8594
9341
|
function catalogEntries(raw) {
|
|
8595
|
-
if (!
|
|
9342
|
+
if (!isRecord3(raw)) {
|
|
8596
9343
|
return [];
|
|
8597
9344
|
}
|
|
8598
|
-
const providers =
|
|
9345
|
+
const providers = isRecord3(raw.providers) ? raw.providers : raw;
|
|
8599
9346
|
return Object.entries(providers).flatMap(([providerKey, provider]) => {
|
|
8600
|
-
if (!
|
|
9347
|
+
if (!isRecord3(provider)) {
|
|
8601
9348
|
return [];
|
|
8602
9349
|
}
|
|
8603
9350
|
const providerID = stringValue(provider.id) ?? stringValue(provider.providerID) ?? providerKey;
|
|
@@ -8606,6 +9353,9 @@ function catalogEntries(raw) {
|
|
|
8606
9353
|
}
|
|
8607
9354
|
function normalizeEntry(entry) {
|
|
8608
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");
|
|
8609
9359
|
const contextWindow = numberValue(entry.value.contextWindow) ?? numberValue(entry.value.context_window) ?? (limit === undefined ? undefined : numberValue(limit.context));
|
|
8610
9360
|
const maxOutputTokens = numberValue(entry.value.maxOutputTokens) ?? numberValue(entry.value.max_output_tokens) ?? (limit === undefined ? undefined : numberValue(limit.output));
|
|
8611
9361
|
const costPer1M = costPair(entry.value);
|
|
@@ -8617,12 +9367,12 @@ function normalizeEntry(entry) {
|
|
|
8617
9367
|
kind: "frontier",
|
|
8618
9368
|
contextWindow,
|
|
8619
9369
|
maxOutputTokens,
|
|
8620
|
-
supportsToolCalling: boolValue(entry.value.tool_call) || boolValue(entry.value.toolCall) || nestedBoolean(entry.value, "features", "tool_call"),
|
|
8621
|
-
supportsVision: boolValue(entry.value.vision) || includesImage(entry.value.modalities) || includesImage(entry.value.input_modalities),
|
|
8622
|
-
reasoningTier: tierFromModelName(
|
|
8623
|
-
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),
|
|
8624
9374
|
costPer1M,
|
|
8625
|
-
availability:
|
|
9375
|
+
availability: modelAvailability(entry.value.availability, entry.value.status)
|
|
8626
9376
|
};
|
|
8627
9377
|
const parsed = ModelCapabilityProfileSchema.safeParse(profile);
|
|
8628
9378
|
return parsed.success ? parsed.data : undefined;
|
|
@@ -9459,123 +10209,6 @@ function isGateApplicable(mode) {
|
|
|
9459
10209
|
return mode === "active";
|
|
9460
10210
|
}
|
|
9461
10211
|
|
|
9462
|
-
// src/messages/commands.ts
|
|
9463
|
-
var clearCacheMessages = {
|
|
9464
|
-
header: "openteam clear-cache — frozen plugin cache entries:",
|
|
9465
|
-
columns: {
|
|
9466
|
-
specDir: "spec dir",
|
|
9467
|
-
pinned: "spec-pinned",
|
|
9468
|
-
installed: "installed",
|
|
9469
|
-
mtime: "mtime"
|
|
9470
|
-
},
|
|
9471
|
-
reparseSkipSuffix: " [SKIP — reparse point]",
|
|
9472
|
-
deletedLabel: "deleted.",
|
|
9473
|
-
lockedLabel: (message) => `[LOCKED] ${message}`,
|
|
9474
|
-
pathOutsideWarning: (specDir, absolutePath) => ` ⚠ ${specDir}: path outside cacheRoot (${absolutePath}), skipped.`,
|
|
9475
|
-
processed: (count) => `${count} entry(ies) processed.`,
|
|
9476
|
-
found: (count) => `${count} entry(ies) found. Use --delete to remove them.`
|
|
9477
|
-
};
|
|
9478
|
-
var baselineMessages = {
|
|
9479
|
-
effectiveAuto: "cheapest-capable (auto)",
|
|
9480
|
-
pinnedSuffix: (ref) => `${ref} (pinned)`,
|
|
9481
|
-
summary: (params) => [
|
|
9482
|
-
"openteam baseline:",
|
|
9483
|
-
` mode: ${params.mode}`,
|
|
9484
|
-
` pinned: ${params.pinned}`,
|
|
9485
|
-
` hardDefault: ${params.hardDefault}`,
|
|
9486
|
-
` effective: ${params.effective}`
|
|
9487
|
-
],
|
|
9488
|
-
invalidModel: (input) => `Invalid model "${input}". Use the provider/model format, e.g. anthropic/claude-sonnet-4-5.`,
|
|
9489
|
-
pinnedTo: (ref) => `Baseline pinned to ${ref} (pinned mode).`,
|
|
9490
|
-
autoMode: "Baseline set to auto mode (cheapest-capable)."
|
|
9491
|
-
};
|
|
9492
|
-
var localMessages = {
|
|
9493
|
-
help: {
|
|
9494
|
-
status: " openteam local status Show the routing mode (local-first / local-only / frontier-only)",
|
|
9495
|
-
off: " openteam local off Use frontier models only (no local runtime)",
|
|
9496
|
-
only: " openteam local only Use local models only (never frontier)",
|
|
9497
|
-
on: " openteam local on Re-enable local-first routing"
|
|
9498
|
-
},
|
|
9499
|
-
noRuntimes: "none",
|
|
9500
|
-
modeFrontierOnly: "frontier-only (frontierOnly)",
|
|
9501
|
-
modeLocalOnly: "local-only (localOnly)",
|
|
9502
|
-
modeLocalFirst: "local-first",
|
|
9503
|
-
summary: (params) => [
|
|
9504
|
-
"openteam local:",
|
|
9505
|
-
` mode: ${params.mode}`,
|
|
9506
|
-
` frontierOnly: ${params.frontierOnly}`,
|
|
9507
|
-
` localOnly: ${params.localOnly}`,
|
|
9508
|
-
` privacy: ${params.privacy}`,
|
|
9509
|
-
` runtimes: ${params.runtimes}`
|
|
9510
|
-
],
|
|
9511
|
-
frontierOnlyNoChange: "No change: already in frontier-only mode.",
|
|
9512
|
-
privacyAdjustedNote: " Privacy adjusted to 'consentBeforeFrontier' (forceLocalOnSensitive requires a local runtime).",
|
|
9513
|
-
frontierOnlyEnabled: "Frontier-only mode enabled (frontierOnly=true, localOnly=false).",
|
|
9514
|
-
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.",
|
|
9515
|
-
localOnlyNoChange: "No change: already in local-only mode.",
|
|
9516
|
-
localOnlyEnabled: "Local-only mode enabled (localOnly=true, frontierOnly=false).",
|
|
9517
|
-
localFirstNoChange: "No change: local-first routing is already active.",
|
|
9518
|
-
localFirstReEnabled: "Local-first routing re-enabled (frontierOnly=false, localOnly=false). Make sure a local runtime is reachable.",
|
|
9519
|
-
unknownSubcommand: (subcommand, help) => `Unknown local subcommand: ${subcommand}
|
|
9520
|
-
|
|
9521
|
-
${help}`
|
|
9522
|
-
};
|
|
9523
|
-
var frontierCatalogMessages = {
|
|
9524
|
-
free: "free",
|
|
9525
|
-
costPerMillion: (inputUSD, outputUSD) => `$${inputUSD}/$${outputUSD} per 1M`
|
|
9526
|
-
};
|
|
9527
|
-
var migrateMessages = {
|
|
9528
|
-
header: "openteam migrate:",
|
|
9529
|
-
nothingToMigrate: " nothing to migrate — the P15b layout is already in place.",
|
|
9530
|
-
counts: (params) => [
|
|
9531
|
-
` moved: ${params.moved} file(s)`,
|
|
9532
|
-
` deduped: ${params.deduped} file(s) (destination already identical)`,
|
|
9533
|
-
` conflicts: ${params.conflicts} file(s)`
|
|
9534
|
-
],
|
|
9535
|
-
relocatedHeader: " relocated:",
|
|
9536
|
-
relocatedEntry: (from, to, deduped) => ` · ${from} → ${to}${deduped ? " (deduped)" : ""}`,
|
|
9537
|
-
conflictsHeader: " ⚠ left in place (destination exists with different content — no data lost):",
|
|
9538
|
-
conflictEntry: (from, to) => ` · ${from} → ${to}`,
|
|
9539
|
-
manualWorktreesHeader: " ⚠ legacy git worktree(s) need a manual move (run `git worktree move`):",
|
|
9540
|
-
manualWorktreeEntry: (label, from, to) => ` · ${label}: ${from} → ${to}`,
|
|
9541
|
-
success: " ✓ machine-local runtime output relocated under .opencode/openteam-local/."
|
|
9542
|
-
};
|
|
9543
|
-
var rolesInitMessages = {
|
|
9544
|
-
header: "openteam roles init:",
|
|
9545
|
-
title: "openteam roles init",
|
|
9546
|
-
noRoster: (path) => ` ✗ no roster at ${path}.`,
|
|
9547
|
-
noRosterRemedy: " Cast the team first: run `openteam setup`, then ask the orchestrator to register the cast.",
|
|
9548
|
-
unparseableRoster: (path, error) => ` ✗ ${path} is present but unparseable: ${error}`,
|
|
9549
|
-
unparseableRosterRemedy: " Run `openteam doctor` for the remedy, then re-run this command.",
|
|
9550
|
-
allProfiled: (count) => ` ✓ ${count} roster role(s) checked; no unprofiled roles — every role resolves a routing profile.`,
|
|
9551
|
-
whyTitle: "Why you are being asked",
|
|
9552
|
-
why: (count) => [
|
|
9553
|
-
`${count} roster role(s) resolve no routing profile.`,
|
|
9554
|
-
"Until a profile exists they are frontier-eligible: their work can escalate",
|
|
9555
|
-
"to a frontier model. A 'local only' note in the roster prose does not",
|
|
9556
|
-
"prevent this — prose outside the JSON fence is never read."
|
|
9557
|
-
].join(`
|
|
9558
|
-
`),
|
|
9559
|
-
policyQuestion: (roleID, agentName) => `Routing policy for "${roleID}" (${agentName})`,
|
|
9560
|
-
localOnlyLabel: (roleID, agentName) => `Local only — ${roleID} (${agentName}) never leaves this machine`,
|
|
9561
|
-
localOnlyHint: "sets requiresLocalRuntime; the role fails closed when no local runtime is reachable",
|
|
9562
|
-
frontierOkLabel: "Local first, frontier allowed",
|
|
9563
|
-
frontierOkHint: "records today's implicit behaviour: cheapest-capable frontier when the task warrants it",
|
|
9564
|
-
skipLabel: "Skip for now",
|
|
9565
|
-
skipHint: "leaves the role unprofiled, so it stays frontier-eligible and doctor keeps reporting it",
|
|
9566
|
-
runtimeQuestion: (roleID) => `Preferred local runtime(s) for "${roleID}" (in failover order)`,
|
|
9567
|
-
runtimeHint: "machine-local — written to the git-ignored overlay",
|
|
9568
|
-
cancelled: (reason) => ` ✗ cancelled (${reason}); nothing was written.`,
|
|
9569
|
-
invalidResult: (reason) => ` ✗ refusing to write: the resulting config is invalid — ${reason}`,
|
|
9570
|
-
nothingWritten: (skipped) => ` · skipped ${skipped}; nothing written.`,
|
|
9571
|
-
nothingToWriteOutro: "Nothing to write.",
|
|
9572
|
-
pinnedLocal: (roleIDs, path) => ` ✓ pinned local-only: ${roleIDs} → ${path} (tracked, travels with the roster)`,
|
|
9573
|
-
frontierAllowed: (roleIDs, path) => ` ✓ frontier allowed: ${roleIDs} → ${path} (model choice stays "auto": cheapest-capable per task)`,
|
|
9574
|
-
runtimeBinding: (path) => ` ✓ runtime binding: ${path} (git-ignored, machine-local)`,
|
|
9575
|
-
skippedSummary: (roleIDs) => ` · skipped: ${roleIDs} — still frontier-eligible; re-run this command to revisit.`,
|
|
9576
|
-
outro: "Role policy updated."
|
|
9577
|
-
};
|
|
9578
|
-
|
|
9579
10212
|
// src/orchestrator/worktreeReconciler.ts
|
|
9580
10213
|
function planOne(wt) {
|
|
9581
10214
|
const base = { runID: wt.runID, path: wt.path };
|
|
@@ -10131,8 +10764,7 @@ function rosterHealthSection(audit, loadError, rosterRoleCount, rosterMissing) {
|
|
|
10131
10764
|
if (unprofiled.length > 0) {
|
|
10132
10765
|
const roleIDs = unprofiled.map((entry) => entry.roleID).join(", ");
|
|
10133
10766
|
lines.push(` · unprofiled roleID(s): ${roleIDs}`);
|
|
10134
|
-
lines.push(
|
|
10135
|
-
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);
|
|
10136
10768
|
}
|
|
10137
10769
|
return lines;
|
|
10138
10770
|
}
|
|
@@ -10174,10 +10806,7 @@ function agentModelsSection(diagnostics, searchedPaths, localOnly) {
|
|
|
10174
10806
|
}
|
|
10175
10807
|
const localOnlyGap = localOnly ? diagnostics.find((d) => d.mode === "primary" && d.resolution?.status === "unresolvable" && d.resolution.reason === "provider-not-configured") : undefined;
|
|
10176
10808
|
if (localOnlyGap?.model !== undefined) {
|
|
10177
|
-
lines.push(
|
|
10178
|
-
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.");
|
|
10179
|
-
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.");
|
|
10180
|
-
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));
|
|
10181
10810
|
}
|
|
10182
10811
|
lines.push(" note: 'declared' = model set in .opencode/agent/<name>.md frontmatter; 'inherited' = opencode.json default.");
|
|
10183
10812
|
lines.push(" note: static checks are offline and deterministic; live checks need a reachable local runtime and can be inconclusive.");
|
|
@@ -10186,10 +10815,16 @@ function agentModelsSection(diagnostics, searchedPaths, localOnly) {
|
|
|
10186
10815
|
function renderDoctor(input) {
|
|
10187
10816
|
const enabledRuntimes = input.config.local.runtimes.filter((r) => r.enabled);
|
|
10188
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;
|
|
10189
10824
|
const lines = [
|
|
10190
10825
|
"openteam doctor:",
|
|
10191
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})` : ""),
|
|
10192
|
-
|
|
10827
|
+
executionPolicyMessages.globalMode(executionMode),
|
|
10193
10828
|
` local runtimes (${reachable}/${enabledRuntimes.length} reachable):`
|
|
10194
10829
|
];
|
|
10195
10830
|
if (input.snapshots.length === 0) {
|
|
@@ -10217,6 +10852,11 @@ function renderDoctor(input) {
|
|
|
10217
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.");
|
|
10218
10853
|
}
|
|
10219
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));
|
|
10220
10860
|
if (input.queueWait !== undefined) {
|
|
10221
10861
|
lines.push(...queueWaitSection(input.queueWait));
|
|
10222
10862
|
}
|
|
@@ -10244,8 +10884,10 @@ function renderDoctor(input) {
|
|
|
10244
10884
|
lines.push(" console:");
|
|
10245
10885
|
lines.push(" command: openteam console");
|
|
10246
10886
|
lines.push(" launches: the multi-session web Console (status and URL)");
|
|
10247
|
-
if (reachable === 0) {
|
|
10248
|
-
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);
|
|
10249
10891
|
}
|
|
10250
10892
|
if (input.graphHealth !== undefined) {
|
|
10251
10893
|
const h = input.graphHealth;
|
|
@@ -10267,7 +10909,7 @@ function renderDoctor(input) {
|
|
|
10267
10909
|
lines.push(" Run `openteam clear-cache --delete` to clean up.");
|
|
10268
10910
|
}
|
|
10269
10911
|
if (input.agentModels !== undefined) {
|
|
10270
|
-
lines.push(...agentModelsSection(input.agentModels, input.opencodeConfigPaths ?? [],
|
|
10912
|
+
lines.push(...agentModelsSection(input.agentModels, input.opencodeConfigPaths ?? [], executionMode === "local"));
|
|
10271
10913
|
}
|
|
10272
10914
|
if (input.opencodeConfigError !== undefined) {
|
|
10273
10915
|
lines.push(` ⚠ opencode config parse error: ${input.opencodeConfigError}`);
|
|
@@ -10392,6 +11034,47 @@ function renderWorktreeReconciliation(report) {
|
|
|
10392
11034
|
function hasEnabledLocalRuntime(config) {
|
|
10393
11035
|
return config.local.runtimes.some((runtime) => runtime.enabled);
|
|
10394
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
|
+
}
|
|
10395
11078
|
function enabledRuntimesSummary(config) {
|
|
10396
11079
|
const enabled = config.local.runtimes.filter((runtime) => runtime.enabled);
|
|
10397
11080
|
if (enabled.length === 0) {
|
|
@@ -10404,59 +11087,44 @@ function enabledRuntimesSummary(config) {
|
|
|
10404
11087
|
}).join(", ");
|
|
10405
11088
|
}
|
|
10406
11089
|
function showLocal(config) {
|
|
10407
|
-
const mode = config
|
|
11090
|
+
const mode = executionMode(config);
|
|
11091
|
+
const modeLabel = mode === "local" ? localMessages.modeLocal : mode === "frontier" ? localMessages.modeFrontier : localMessages.modeMixed;
|
|
10408
11092
|
const lines = localMessages.summary({
|
|
10409
|
-
mode,
|
|
10410
|
-
frontierOnly: config.router.frontierOnly,
|
|
10411
|
-
localOnly: config.router.localOnly,
|
|
10412
|
-
privacy: config.privacyMode,
|
|
11093
|
+
mode: modeLabel,
|
|
10413
11094
|
runtimes: enabledRuntimesSummary(config)
|
|
10414
11095
|
});
|
|
10415
11096
|
return { message: lines.join(`
|
|
10416
11097
|
`) };
|
|
10417
11098
|
}
|
|
10418
11099
|
function setFrontierOnly(config) {
|
|
10419
|
-
if (config
|
|
11100
|
+
if (executionMode(config) === "frontier" && !requiresExecutionMigration(config)) {
|
|
11101
|
+
assertEffectiveConfigModelDomains(config);
|
|
10420
11102
|
return { message: localMessages.frontierOnlyNoChange };
|
|
10421
11103
|
}
|
|
10422
|
-
const flipsPrivacy = config.privacyMode === "forceLocalOnSensitive";
|
|
10423
|
-
const next = {
|
|
10424
|
-
...config,
|
|
10425
|
-
router: { ...config.router, frontierOnly: true, localOnly: false },
|
|
10426
|
-
privacyMode: flipsPrivacy ? "consentBeforeFrontier" : config.privacyMode
|
|
10427
|
-
};
|
|
10428
|
-
const privacyNote = flipsPrivacy ? localMessages.privacyAdjustedNote : "";
|
|
10429
11104
|
return {
|
|
10430
|
-
config:
|
|
10431
|
-
message:
|
|
11105
|
+
config: withExecutionMode(config, "frontier"),
|
|
11106
|
+
message: localMessages.frontierOnlyEnabled
|
|
10432
11107
|
};
|
|
10433
11108
|
}
|
|
10434
11109
|
function setLocalOnly(config) {
|
|
10435
11110
|
if (!hasEnabledLocalRuntime(config)) {
|
|
10436
11111
|
return { message: localMessages.localOnlyNoRuntimes };
|
|
10437
11112
|
}
|
|
10438
|
-
if (config
|
|
11113
|
+
if (executionMode(config) === "local" && !requiresExecutionMigration(config)) {
|
|
11114
|
+
assertEffectiveConfigModelDomains(config);
|
|
10439
11115
|
return { message: localMessages.localOnlyNoChange };
|
|
10440
11116
|
}
|
|
10441
|
-
const next = {
|
|
10442
|
-
...config,
|
|
10443
|
-
router: { ...config.router, frontierOnly: false, localOnly: true }
|
|
10444
|
-
};
|
|
10445
11117
|
return {
|
|
10446
|
-
config:
|
|
11118
|
+
config: withExecutionMode(config, "local"),
|
|
10447
11119
|
message: localMessages.localOnlyEnabled
|
|
10448
11120
|
};
|
|
10449
11121
|
}
|
|
10450
11122
|
function setLocalFirst(config) {
|
|
10451
|
-
if (
|
|
11123
|
+
if (executionMode(config) === "mixed" && !requiresExecutionMigration(config)) {
|
|
10452
11124
|
return { message: localMessages.localFirstNoChange };
|
|
10453
11125
|
}
|
|
10454
|
-
const next = {
|
|
10455
|
-
...config,
|
|
10456
|
-
router: { ...config.router, frontierOnly: false, localOnly: false }
|
|
10457
|
-
};
|
|
10458
11126
|
return {
|
|
10459
|
-
config:
|
|
11127
|
+
config: withExecutionMode(config, "mixed"),
|
|
10460
11128
|
message: localMessages.localFirstReEnabled
|
|
10461
11129
|
};
|
|
10462
11130
|
}
|
|
@@ -10557,7 +11225,7 @@ function stripJsoncComments(src) {
|
|
|
10557
11225
|
}
|
|
10558
11226
|
return out.join("");
|
|
10559
11227
|
}
|
|
10560
|
-
function
|
|
11228
|
+
function isRecord4(v) {
|
|
10561
11229
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
10562
11230
|
}
|
|
10563
11231
|
var OPENCODE_CONFIG_CANDIDATES = [
|
|
@@ -10568,7 +11236,7 @@ function mergeOpencodeConfigs(base, override) {
|
|
|
10568
11236
|
const result = { ...base };
|
|
10569
11237
|
for (const [key, value] of Object.entries(override)) {
|
|
10570
11238
|
const baseVal = result[key];
|
|
10571
|
-
if (
|
|
11239
|
+
if (isRecord4(baseVal) && isRecord4(value)) {
|
|
10572
11240
|
result[key] = mergeOpencodeConfigs(baseVal, value);
|
|
10573
11241
|
} else {
|
|
10574
11242
|
result[key] = value;
|
|
@@ -10769,17 +11437,17 @@ var GENERATED_OPENTEAM_STATE_GROUPS = [
|
|
|
10769
11437
|
var OPENCODE_DIR = ".opencode";
|
|
10770
11438
|
var AGENT_DIR = `${OPENCODE_DIR}/agent`;
|
|
10771
11439
|
var ROLE_AGENT_DESCRIPTIONS = {
|
|
10772
|
-
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.",
|
|
10773
11441
|
integration: "openteam integration subagent: wires components together, owns end-to-end flows and cross-module glue.",
|
|
10774
|
-
"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.",
|
|
10775
11443
|
"routing-cost": "openteam routing-cost subagent: cost-aware routing analysis and token/budget optimisation.",
|
|
10776
|
-
scribe: "openteam scribe subagent: the team's silent memory. Records decisions and learnings in .opencode/openteam/decisions.md without changing code.
|
|
10777
|
-
ralph: "openteam ralph subagent: automation and triage driver. Monitors pending work, prioritises it and coordinates execution, escalating blockers to the human.
|
|
10778
|
-
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.",
|
|
10779
11447
|
tester: "openteam tester subagent: writes and runs tests, reproduces bugs and validates fixes.",
|
|
10780
11448
|
reviewer: "openteam reviewer subagent: reviews diffs for correctness, security and quality."
|
|
10781
11449
|
};
|
|
10782
|
-
function buildRoleAgent(agentName, description
|
|
11450
|
+
function buildRoleAgent(agentName, description) {
|
|
10783
11451
|
const frontmatter = [
|
|
10784
11452
|
"---",
|
|
10785
11453
|
`description: ${JSON.stringify(description)}`,
|
|
@@ -10793,7 +11461,7 @@ function buildRoleAgent(agentName, description, localOnly) {
|
|
|
10793
11461
|
"",
|
|
10794
11462
|
description,
|
|
10795
11463
|
"",
|
|
10796
|
-
|
|
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.",
|
|
10797
11465
|
"",
|
|
10798
11466
|
"Stay focused on your role. Keep changes surgical and report what you did."
|
|
10799
11467
|
].join(`
|
|
@@ -10803,19 +11471,19 @@ function buildRoleAgent(agentName, description, localOnly) {
|
|
|
10803
11471
|
${body}
|
|
10804
11472
|
`;
|
|
10805
11473
|
}
|
|
10806
|
-
function buildRoleAgentFiles() {
|
|
10807
|
-
const seen = new
|
|
11474
|
+
function buildRoleAgentFiles(_config) {
|
|
11475
|
+
const seen = new Set;
|
|
10808
11476
|
for (const role of Object.values(TEAM_ROLES)) {
|
|
10809
11477
|
const agentName = role.opencodeAgent;
|
|
10810
11478
|
if (agentName === undefined || seen.has(agentName)) {
|
|
10811
11479
|
continue;
|
|
10812
11480
|
}
|
|
10813
|
-
seen.
|
|
11481
|
+
seen.add(agentName);
|
|
10814
11482
|
}
|
|
10815
|
-
return [...seen
|
|
11483
|
+
return [...seen].sort((a, b) => a.localeCompare(b)).map((agentName) => ({
|
|
10816
11484
|
agentName,
|
|
10817
11485
|
path: `${AGENT_DIR}/${agentName}.md`,
|
|
10818
|
-
contents: buildRoleAgent(agentName, ROLE_AGENT_DESCRIPTIONS[agentName] ?? `openteam ${agentName} subagent
|
|
11486
|
+
contents: buildRoleAgent(agentName, ROLE_AGENT_DESCRIPTIONS[agentName] ?? `openteam ${agentName} subagent.`)
|
|
10819
11487
|
}));
|
|
10820
11488
|
}
|
|
10821
11489
|
function splitSetupPath(path2) {
|
|
@@ -10961,40 +11629,42 @@ function buildOpenTeamConfig(answers) {
|
|
|
10961
11629
|
return runtime;
|
|
10962
11630
|
});
|
|
10963
11631
|
const primaryLocal = firstEnabled(answers.runtimes);
|
|
11632
|
+
if (answers.executionMode === "local" && primaryLocal === undefined) {
|
|
11633
|
+
throw new Error(executionSetupMessages.localRequiredError);
|
|
11634
|
+
}
|
|
10964
11635
|
const localDefault = primaryLocal !== undefined ? {
|
|
10965
11636
|
providerID: choiceProviderID(primaryLocal),
|
|
10966
11637
|
modelID: primaryLocal.defaultModelID
|
|
10967
11638
|
} : null;
|
|
10968
|
-
const
|
|
10969
|
-
|
|
11639
|
+
const roles = Object.fromEntries(Object.keys(TEAM_ROLES).sort().map((roleID) => [roleID, { model: "auto" }]));
|
|
11640
|
+
const config = OpenTeamConfigSchema.parse({
|
|
11641
|
+
configVersion: 2,
|
|
10970
11642
|
baseline: {
|
|
10971
11643
|
mode: "auto",
|
|
10972
11644
|
pinnedModel: null,
|
|
10973
11645
|
hardDefault: answers.frontier
|
|
10974
11646
|
},
|
|
10975
11647
|
router: {
|
|
10976
|
-
|
|
10977
|
-
localDefault
|
|
10978
|
-
frontierOnly: primaryLocal === undefined,
|
|
10979
|
-
localOnly
|
|
11648
|
+
executionMode: answers.executionMode,
|
|
11649
|
+
localDefault
|
|
10980
11650
|
},
|
|
10981
11651
|
local: { runtimes },
|
|
10982
|
-
|
|
11652
|
+
orchestrator: {
|
|
11653
|
+
primary: {
|
|
11654
|
+
model: answers.primaryModel,
|
|
11655
|
+
fallbacks: []
|
|
11656
|
+
},
|
|
11657
|
+
roles,
|
|
11658
|
+
maxRetries: 2,
|
|
11659
|
+
maxModelsPerNode: 2
|
|
11660
|
+
},
|
|
10983
11661
|
graph: { mode: "active" }
|
|
10984
11662
|
});
|
|
11663
|
+
assertEffectiveConfigModelDomains(config);
|
|
11664
|
+
return config;
|
|
10985
11665
|
}
|
|
10986
11666
|
function orchestratorAgentModel(answers) {
|
|
10987
|
-
|
|
10988
|
-
if (answers.localOnly === true && primaryLocal !== undefined) {
|
|
10989
|
-
return {
|
|
10990
|
-
providerID: choiceProviderID(primaryLocal),
|
|
10991
|
-
modelID: primaryLocal.thinkingModelID ?? primaryLocal.defaultModelID
|
|
10992
|
-
};
|
|
10993
|
-
}
|
|
10994
|
-
return {
|
|
10995
|
-
providerID: answers.frontier.providerID,
|
|
10996
|
-
modelID: answers.frontier.modelID
|
|
10997
|
-
};
|
|
11667
|
+
return { ...answers.primaryModel };
|
|
10998
11668
|
}
|
|
10999
11669
|
function buildOpencodeConfig(answers) {
|
|
11000
11670
|
const provider = {};
|
|
@@ -11008,12 +11678,22 @@ function buildOpencodeConfig(answers) {
|
|
|
11008
11678
|
if (baseURL === undefined) {
|
|
11009
11679
|
continue;
|
|
11010
11680
|
}
|
|
11011
|
-
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
|
+
}
|
|
11012
11693
|
const models = {};
|
|
11013
11694
|
for (const modelID of modelIDs) {
|
|
11014
11695
|
models[modelID] = { name: modelID };
|
|
11015
11696
|
}
|
|
11016
|
-
const providerID = choiceProviderID(choice);
|
|
11017
11697
|
const owner = providerOwner.get(providerID);
|
|
11018
11698
|
if (owner !== undefined) {
|
|
11019
11699
|
if (owner.baseURL !== baseURL) {
|
|
@@ -11038,8 +11718,7 @@ function buildOpencodeConfig(answers) {
|
|
|
11038
11718
|
providerOwner.set(providerID, { instanceId: choice.id, baseURL });
|
|
11039
11719
|
}
|
|
11040
11720
|
const primaryLocal = firstEnabled(answers.runtimes);
|
|
11041
|
-
const
|
|
11042
|
-
const mainModel = localOnly && primaryLocal !== undefined ? `${choiceProviderID(primaryLocal)}/${primaryLocal.defaultModelID}` : `${answers.frontier.providerID}/${answers.frontier.modelID}`;
|
|
11721
|
+
const mainModel = `${answers.primaryModel.providerID}/${answers.primaryModel.modelID}`;
|
|
11043
11722
|
const config = {
|
|
11044
11723
|
$schema: "https://opencode.ai/config.json",
|
|
11045
11724
|
plugin: [OPENTEAM_PLUGIN_SPEC],
|
|
@@ -11119,7 +11798,7 @@ async function chooseFrontier(prompt, profiles) {
|
|
|
11119
11798
|
const sorted = sortFrontierByCost(profiles);
|
|
11120
11799
|
const first = sorted[0];
|
|
11121
11800
|
const selected = await prompt.autocomplete({
|
|
11122
|
-
message:
|
|
11801
|
+
message: executionSetupMessages.frontierModelQuestion,
|
|
11123
11802
|
placeholder: "search by provider or model…",
|
|
11124
11803
|
choices: [
|
|
11125
11804
|
...sorted.map((profile) => ({
|
|
@@ -11405,7 +12084,7 @@ async function resolveRuntimeInstances(deps, meta, info, usedIds) {
|
|
|
11405
12084
|
async function runSetup(deps) {
|
|
11406
12085
|
const { prompt } = deps;
|
|
11407
12086
|
try {
|
|
11408
|
-
prompt.intro(
|
|
12087
|
+
prompt.intro(executionSetupMessages.setupIntro);
|
|
11409
12088
|
const detected = await deps.detect();
|
|
11410
12089
|
const detectedById = new Map(detected.map((item) => [item.id, item]));
|
|
11411
12090
|
const runtimeChoices = KNOWN_RUNTIMES.map((meta) => {
|
|
@@ -11419,11 +12098,11 @@ async function runSetup(deps) {
|
|
|
11419
12098
|
});
|
|
11420
12099
|
runtimeChoices.push({
|
|
11421
12100
|
value: RUNTIME_NONE,
|
|
11422
|
-
label:
|
|
11423
|
-
hint:
|
|
12101
|
+
label: executionSetupMessages.runtimeNoneLabel,
|
|
12102
|
+
hint: executionSetupMessages.runtimeNoneHint
|
|
11424
12103
|
});
|
|
11425
12104
|
const enabledIds = await prompt.multiselect({
|
|
11426
|
-
message:
|
|
12105
|
+
message: executionSetupMessages.runtimeQuestion,
|
|
11427
12106
|
choices: runtimeChoices,
|
|
11428
12107
|
required: true
|
|
11429
12108
|
});
|
|
@@ -11450,10 +12129,27 @@ async function runSetup(deps) {
|
|
|
11450
12129
|
runtimes.push(...await resolveRuntimeInstances(deps, meta, info, usedIds));
|
|
11451
12130
|
}
|
|
11452
12131
|
const hasLocalEnabled = enabled.size > 0;
|
|
11453
|
-
const
|
|
11454
|
-
message:
|
|
11455
|
-
|
|
11456
|
-
|
|
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";
|
|
11457
12153
|
let frontierProfiles;
|
|
11458
12154
|
try {
|
|
11459
12155
|
const loaded = await deps.loadFrontierProfiles();
|
|
@@ -11461,68 +12157,55 @@ async function runSetup(deps) {
|
|
|
11461
12157
|
} catch {
|
|
11462
12158
|
frontierProfiles = [...CURATED_FRONTIER_PROFILES];
|
|
11463
12159
|
}
|
|
11464
|
-
const
|
|
11465
|
-
|
|
11466
|
-
|
|
11467
|
-
|
|
11468
|
-
|
|
11469
|
-
|
|
11470
|
-
|
|
11471
|
-
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
|
|
11475
|
-
|
|
11476
|
-
|
|
11477
|
-
|
|
11478
|
-
|
|
11479
|
-
|
|
11480
|
-
|
|
11481
|
-
|
|
11482
|
-
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
|
|
11488
|
-
|
|
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
|
+
}
|
|
11489
12196
|
if (!hasLocalEnabled) {
|
|
11490
|
-
prompt.note(
|
|
11491
|
-
|
|
11492
|
-
"frontier models (the configured cheapest-capable).",
|
|
11493
|
-
"",
|
|
11494
|
-
"For sensitive prompts, 'forceLocalOnSensitive' would have no local",
|
|
11495
|
-
"target, so 'consentBeforeFrontier' is recommended."
|
|
11496
|
-
].join(`
|
|
11497
|
-
`), "Frontier-only mode");
|
|
11498
|
-
}
|
|
11499
|
-
const privacyMode = localOnly ? "forceLocalOnSensitive" : await prompt.select({
|
|
11500
|
-
message: "Privacy for sensitive prompts",
|
|
11501
|
-
choices: [
|
|
11502
|
-
{
|
|
11503
|
-
value: "forceLocalOnSensitive",
|
|
11504
|
-
label: "forceLocalOnSensitive (never sends sensitive data to frontier)",
|
|
11505
|
-
...hasLocalEnabled ? {} : { hint: "requires an enabled local runtime" }
|
|
11506
|
-
},
|
|
11507
|
-
{
|
|
11508
|
-
value: "consentBeforeFrontier",
|
|
11509
|
-
label: "consentBeforeFrontier (asks for consent)"
|
|
11510
|
-
},
|
|
11511
|
-
{ value: "off", label: "off (no protection)" }
|
|
11512
|
-
],
|
|
11513
|
-
initial: hasLocalEnabled ? "forceLocalOnSensitive" : "consentBeforeFrontier"
|
|
11514
|
-
});
|
|
12197
|
+
prompt.note(executionSetupMessages.frontierOnlyNote, executionSetupMessages.frontierOnlyTitle);
|
|
12198
|
+
}
|
|
11515
12199
|
const yolo = await prompt.confirm({
|
|
11516
|
-
message:
|
|
12200
|
+
message: executionSetupMessages.yoloQuestion,
|
|
11517
12201
|
initial: false
|
|
11518
12202
|
});
|
|
11519
12203
|
const answers = {
|
|
11520
12204
|
runtimes,
|
|
11521
12205
|
frontier,
|
|
11522
|
-
|
|
11523
|
-
|
|
11524
|
-
yolo
|
|
11525
|
-
localOnly
|
|
12206
|
+
executionMode: executionMode2,
|
|
12207
|
+
primaryModel,
|
|
12208
|
+
yolo
|
|
11526
12209
|
};
|
|
11527
12210
|
const openTeamConfig = buildOpenTeamConfig(answers);
|
|
11528
12211
|
const opencodeConfig = buildOpencodeConfig(answers);
|
|
@@ -11532,7 +12215,7 @@ async function runSetup(deps) {
|
|
|
11532
12215
|
const existingOpencodePath = await deps.resolveOpencodeWriteTarget(OPENCODE_CONFIG_CANDIDATES);
|
|
11533
12216
|
const opencodePath = existingOpencodePath ?? OPENCODE_CONFIG_CANDIDATES[0];
|
|
11534
12217
|
const agentPath = ORCHESTRATOR_AGENT_PATH;
|
|
11535
|
-
const roleAgentFiles = buildRoleAgentFiles();
|
|
12218
|
+
const roleAgentFiles = buildRoleAgentFiles(openTeamConfig);
|
|
11536
12219
|
const opencodeGitignorePath = OPENCODE_GITIGNORE_PATH;
|
|
11537
12220
|
const existingGitignore = await deps.fileExists(opencodeGitignorePath) ? await deps.readFile(opencodeGitignorePath) ?? "" : "";
|
|
11538
12221
|
const opencodeGitignore = buildOpencodeGitignore(existingGitignore);
|
|
@@ -11589,27 +12272,30 @@ async function runSetup(deps) {
|
|
|
11589
12272
|
const remote = runtime.baseURL !== undefined && !runtime.baseURL.includes("localhost") && !runtime.baseURL.includes("127.0.0.1") ? ` @ ${runtime.baseURL}` : "";
|
|
11590
12273
|
return `${runtime.id} (${runtime.defaultModelID})${remote}`;
|
|
11591
12274
|
}).join(", ");
|
|
11592
|
-
prompt.note(
|
|
11593
|
-
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
|
|
11597
|
-
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
11601
|
-
|
|
11602
|
-
|
|
11603
|
-
|
|
11604
|
-
|
|
11605
|
-
|
|
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);
|
|
11606
12292
|
return {
|
|
11607
12293
|
exitCode: 0,
|
|
11608
|
-
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}`)
|
|
11609
12295
|
};
|
|
11610
12296
|
} catch (error) {
|
|
11611
12297
|
const message = error instanceof Error ? error.message : String(error);
|
|
11612
|
-
return { exitCode: 1, stdout:
|
|
12298
|
+
return { exitCode: 1, stdout: executionSetupMessages.error(message) };
|
|
11613
12299
|
}
|
|
11614
12300
|
}
|
|
11615
12301
|
|
|
@@ -11879,8 +12565,8 @@ function stripOpenteamFromOpencodeConfig(raw) {
|
|
|
11879
12565
|
}
|
|
11880
12566
|
return { status: "not-present" };
|
|
11881
12567
|
}
|
|
11882
|
-
const
|
|
11883
|
-
const plugins =
|
|
12568
|
+
const record2 = parsed;
|
|
12569
|
+
const plugins = record2.plugin;
|
|
11884
12570
|
if (!Array.isArray(plugins)) {
|
|
11885
12571
|
if (raw.includes(OPENTEAM_PLUGIN_SPEC)) {
|
|
11886
12572
|
return {
|
|
@@ -12294,10 +12980,10 @@ var HELP = [
|
|
|
12294
12980
|
localMessages.help.off,
|
|
12295
12981
|
localMessages.help.only,
|
|
12296
12982
|
localMessages.help.on,
|
|
12297
|
-
|
|
12298
|
-
|
|
12983
|
+
executionPolicyMessages.doctorHelp,
|
|
12984
|
+
rolesInitMessages.help,
|
|
12299
12985
|
" openteam migrate Relocate pre-P15b artifacts to their split homes (idempotent)",
|
|
12300
|
-
|
|
12986
|
+
executionPolicyMessages.agentsHelp,
|
|
12301
12987
|
" openteam console Launch the multi-session web Console (Ctrl+C to stop; --open opens the browser)",
|
|
12302
12988
|
" openteam console --status Show the Console config without launching it",
|
|
12303
12989
|
" openteam graph status Show the Graph journal and lease health",
|
|
@@ -12515,9 +13201,7 @@ ${HELP}`
|
|
|
12515
13201
|
const agentNote = await regenerateOrchestratorAgent(deps, config, enable);
|
|
12516
13202
|
return {
|
|
12517
13203
|
exitCode: 0,
|
|
12518
|
-
stdout:
|
|
12519
|
-
Note: YOLO does not disable openteam privacy; sensitive prompts still stay off frontier with forceLocalOnSensitive.` : ""}
|
|
12520
|
-
Restart opencode (or reload) so agents pick up the new permissions.`
|
|
13204
|
+
stdout: yoloMessages.updated(writePath, renderYoloStatus(enable), agentNote)
|
|
12521
13205
|
};
|
|
12522
13206
|
}
|
|
12523
13207
|
async function regenerateOrchestratorAgent(deps, config, enable) {
|
|
@@ -12577,6 +13261,11 @@ async function runAgents(deps, configPath, opencodeConfigPaths, configResolution
|
|
|
12577
13261
|
exitCode: 0,
|
|
12578
13262
|
stdout: renderAgentList(classifications, {
|
|
12579
13263
|
...context,
|
|
13264
|
+
executionPolicies: executionPolicySummaries(config),
|
|
13265
|
+
retryLimits: {
|
|
13266
|
+
maxRetries: config.orchestrator?.maxRetries ?? 2,
|
|
13267
|
+
maxModelsPerNode: config.orchestrator?.maxModelsPerNode ?? 2
|
|
13268
|
+
},
|
|
12580
13269
|
...opencodeConfigError !== undefined ? { opencodeConfigError } : {}
|
|
12581
13270
|
})
|
|
12582
13271
|
};
|
|
@@ -13163,9 +13852,12 @@ function auditParsedRoster(roster, configuredRoles) {
|
|
|
13163
13852
|
}
|
|
13164
13853
|
|
|
13165
13854
|
// src/commands/rolesInit.ts
|
|
13166
|
-
var
|
|
13167
|
-
var
|
|
13855
|
+
var ROLES_INIT_AUTO = "auto";
|
|
13856
|
+
var ROLES_INIT_PIN = "pin";
|
|
13168
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";
|
|
13169
13861
|
function isJsonRecord2(value) {
|
|
13170
13862
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13171
13863
|
}
|
|
@@ -13198,29 +13890,15 @@ function withRole(config, roleID, profile) {
|
|
|
13198
13890
|
}
|
|
13199
13891
|
};
|
|
13200
13892
|
}
|
|
13201
|
-
function
|
|
13202
|
-
const base = synthesiseFallbackProfile(roleID);
|
|
13203
|
-
return {
|
|
13204
|
-
...base,
|
|
13205
|
-
preferredFrontierModels: [],
|
|
13206
|
-
localFirst: true,
|
|
13207
|
-
requiresLocalRuntime: true
|
|
13208
|
-
};
|
|
13209
|
-
}
|
|
13210
|
-
function frontierEligibleProfile(roleID) {
|
|
13211
|
-
return { ...synthesiseFallbackProfile(roleID) };
|
|
13212
|
-
}
|
|
13213
|
-
function policyChoices(roleID, agentName) {
|
|
13893
|
+
function policyChoices() {
|
|
13214
13894
|
return [
|
|
13215
13895
|
{
|
|
13216
|
-
value:
|
|
13217
|
-
label:
|
|
13218
|
-
hint: rolesInitMessages.localOnlyHint
|
|
13896
|
+
value: ROLES_INIT_AUTO,
|
|
13897
|
+
label: executionSetupMessages.rolePolicy.modelAutoLabel
|
|
13219
13898
|
},
|
|
13220
13899
|
{
|
|
13221
|
-
value:
|
|
13222
|
-
label:
|
|
13223
|
-
hint: rolesInitMessages.frontierOkHint
|
|
13900
|
+
value: ROLES_INIT_PIN,
|
|
13901
|
+
label: executionSetupMessages.rolePolicy.modelExactLabel
|
|
13224
13902
|
},
|
|
13225
13903
|
{
|
|
13226
13904
|
value: ROLES_INIT_SKIP,
|
|
@@ -13229,6 +13907,56 @@ function policyChoices(roleID, agentName) {
|
|
|
13229
13907
|
}
|
|
13230
13908
|
];
|
|
13231
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
|
+
}
|
|
13232
13960
|
async function runRolesInit(deps) {
|
|
13233
13961
|
const lines = [rolesInitMessages.header];
|
|
13234
13962
|
const rosterContent = await deps.readFile(OPENTEAM_ROSTER_PATH);
|
|
@@ -13243,8 +13971,18 @@ async function runRolesInit(deps) {
|
|
|
13243
13971
|
return { stdout: lines.join(`
|
|
13244
13972
|
`), exitCode: 1 };
|
|
13245
13973
|
}
|
|
13246
|
-
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
|
+
}
|
|
13247
13984
|
const configuredRoles = existingRoles(baseConfig);
|
|
13985
|
+
const globalExecutionMode = rawExecutionMode(baseConfig);
|
|
13248
13986
|
const unprofiled = parsed.roster.entries.filter((entry) => getRoleProfile(entry.roleID, configuredRoles) === undefined && !isKnownNonWorkerRole(entry.roleID));
|
|
13249
13987
|
if (unprofiled.length === 0) {
|
|
13250
13988
|
lines.push(rolesInitMessages.allProfiled(parsed.roster.entries.length));
|
|
@@ -13252,32 +13990,44 @@ async function runRolesInit(deps) {
|
|
|
13252
13990
|
`), exitCode: 0 };
|
|
13253
13991
|
}
|
|
13254
13992
|
deps.prompt.intro(rolesInitMessages.title);
|
|
13255
|
-
deps.prompt.note(
|
|
13993
|
+
deps.prompt.note(executionSetupMessages.rolePolicy.why(unprofiled.length), executionSetupMessages.rolePolicy.whyTitle);
|
|
13256
13994
|
const runtimes = (await deps.detectRuntimes?.())?.filter((runtime) => runtime.available);
|
|
13257
13995
|
let nextConfig = baseConfig;
|
|
13258
13996
|
let nextOverlay;
|
|
13259
|
-
const
|
|
13260
|
-
const relaxed = [];
|
|
13997
|
+
const configured = [];
|
|
13261
13998
|
const skipped = [];
|
|
13262
13999
|
try {
|
|
13263
14000
|
for (const entry of unprofiled) {
|
|
13264
14001
|
const choice = await deps.prompt.select({
|
|
13265
|
-
message:
|
|
13266
|
-
choices: policyChoices(
|
|
13267
|
-
initial:
|
|
14002
|
+
message: executionSetupMessages.rolePolicy.modelQuestion(entry.roleID),
|
|
14003
|
+
choices: policyChoices(),
|
|
14004
|
+
initial: ROLES_INIT_AUTO
|
|
13268
14005
|
});
|
|
13269
14006
|
if (choice === ROLES_INIT_SKIP) {
|
|
13270
14007
|
skipped.push(entry.roleID);
|
|
13271
14008
|
continue;
|
|
13272
14009
|
}
|
|
13273
|
-
|
|
13274
|
-
|
|
13275
|
-
|
|
13276
|
-
|
|
13277
|
-
}
|
|
13278
|
-
|
|
13279
|
-
|
|
13280
|
-
|
|
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) {
|
|
13281
14031
|
const selected = await deps.prompt.multiselect({
|
|
13282
14032
|
message: rolesInitMessages.runtimeQuestion(entry.roleID),
|
|
13283
14033
|
choices: runtimes.map((runtime) => ({
|
|
@@ -13299,15 +14049,21 @@ async function runRolesInit(deps) {
|
|
|
13299
14049
|
return { stdout: lines.join(`
|
|
13300
14050
|
`), exitCode: 1 };
|
|
13301
14051
|
}
|
|
13302
|
-
if (
|
|
14052
|
+
if (configured.length === 0) {
|
|
13303
14053
|
lines.push(rolesInitMessages.nothingWritten(skipped.join(", ")));
|
|
13304
14054
|
deps.prompt.outro(rolesInitMessages.nothingToWriteOutro);
|
|
13305
14055
|
return { stdout: lines.join(`
|
|
13306
14056
|
`), exitCode: 0 };
|
|
13307
14057
|
}
|
|
13308
|
-
const merged = mergeConfigInputs(nextConfig, nextOverlay);
|
|
13309
14058
|
try {
|
|
13310
|
-
|
|
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
|
+
}
|
|
13311
14067
|
} catch (error) {
|
|
13312
14068
|
lines.push(rolesInitMessages.invalidResult(error instanceof Error ? error.message : String(error)));
|
|
13313
14069
|
return { stdout: lines.join(`
|
|
@@ -13317,12 +14073,7 @@ async function runRolesInit(deps) {
|
|
|
13317
14073
|
if (nextOverlay !== undefined) {
|
|
13318
14074
|
await deps.writeFile(DEFAULT_LOCAL_OVERLAY_PATH, serialize(nextOverlay));
|
|
13319
14075
|
}
|
|
13320
|
-
|
|
13321
|
-
lines.push(rolesInitMessages.pinnedLocal(pinned.join(", "), DEFAULT_CONFIG_PATH));
|
|
13322
|
-
}
|
|
13323
|
-
if (relaxed.length > 0) {
|
|
13324
|
-
lines.push(rolesInitMessages.frontierAllowed(relaxed.join(", "), DEFAULT_CONFIG_PATH));
|
|
13325
|
-
}
|
|
14076
|
+
lines.push(executionSetupMessages.rolePolicy.configured(configured.join(", "), DEFAULT_CONFIG_PATH));
|
|
13326
14077
|
if (nextOverlay !== undefined) {
|
|
13327
14078
|
lines.push(rolesInitMessages.runtimeBinding(DEFAULT_LOCAL_OVERLAY_PATH));
|
|
13328
14079
|
}
|
|
@@ -14285,21 +15036,21 @@ var DEFAULT_RANK_SIGNALS = {
|
|
|
14285
15036
|
accessWeight: 0.1,
|
|
14286
15037
|
recencyHalfLifeMs: 14 * DAY_MS
|
|
14287
15038
|
};
|
|
14288
|
-
function recencyTimestamp(
|
|
14289
|
-
if (
|
|
14290
|
-
return
|
|
15039
|
+
function recencyTimestamp(record2) {
|
|
15040
|
+
if (record2.kind === "preference" && record2.lastAccessedAt !== null) {
|
|
15041
|
+
return record2.lastAccessedAt;
|
|
14291
15042
|
}
|
|
14292
|
-
return
|
|
15043
|
+
return record2.createdAt;
|
|
14293
15044
|
}
|
|
14294
|
-
function recencyScore(
|
|
14295
|
-
const ageMs = Math.max(0, signals.now - recencyTimestamp(
|
|
15045
|
+
function recencyScore(record2, signals) {
|
|
15046
|
+
const ageMs = Math.max(0, signals.now - recencyTimestamp(record2));
|
|
14296
15047
|
return 0.5 ** (ageMs / signals.recencyHalfLifeMs);
|
|
14297
15048
|
}
|
|
14298
|
-
function accessScore(
|
|
14299
|
-
if (
|
|
15049
|
+
function accessScore(record2) {
|
|
15050
|
+
if (record2.kind !== "preference") {
|
|
14300
15051
|
return 0;
|
|
14301
15052
|
}
|
|
14302
|
-
return 1 - 1 / (1 +
|
|
15053
|
+
return 1 - 1 / (1 + record2.accessCount);
|
|
14303
15054
|
}
|
|
14304
15055
|
function scoreCandidate(candidate, signals) {
|
|
14305
15056
|
return signals.similarityWeight * candidate.similarity + signals.recencyWeight * recencyScore(candidate.record, signals) + signals.accessWeight * accessScore(candidate.record);
|
|
@@ -14317,20 +15068,20 @@ function rankRecall(candidates, signals) {
|
|
|
14317
15068
|
}
|
|
14318
15069
|
|
|
14319
15070
|
// src/memory/temporal.ts
|
|
14320
|
-
function isTransactionActiveAt(
|
|
14321
|
-
return
|
|
15071
|
+
function isTransactionActiveAt(record2, asOf) {
|
|
15072
|
+
return record2.invalidatedAt === null || record2.invalidatedAt > asOf;
|
|
14322
15073
|
}
|
|
14323
|
-
function isValidAt(
|
|
14324
|
-
if (
|
|
15074
|
+
function isValidAt(record2, asOf) {
|
|
15075
|
+
if (record2.validFrom > asOf) {
|
|
14325
15076
|
return false;
|
|
14326
15077
|
}
|
|
14327
|
-
return
|
|
15078
|
+
return record2.validUntil === null || record2.validUntil > asOf;
|
|
14328
15079
|
}
|
|
14329
|
-
function isActiveAt(
|
|
14330
|
-
return isTransactionActiveAt(
|
|
15080
|
+
function isActiveAt(record2, asOf) {
|
|
15081
|
+
return isTransactionActiveAt(record2, asOf) && isValidAt(record2, asOf);
|
|
14331
15082
|
}
|
|
14332
15083
|
function filterActive(records, asOf) {
|
|
14333
|
-
return records.filter((
|
|
15084
|
+
return records.filter((record2) => isActiveAt(record2, asOf));
|
|
14334
15085
|
}
|
|
14335
15086
|
function coalesceWindow(a, b) {
|
|
14336
15087
|
const validFrom = Math.min(a.validFrom, b.validFrom);
|
|
@@ -14366,21 +15117,21 @@ function cosineSimilarity(a, b) {
|
|
|
14366
15117
|
}
|
|
14367
15118
|
|
|
14368
15119
|
// src/memory/recall.ts
|
|
14369
|
-
function matchesScope(
|
|
14370
|
-
if (
|
|
15120
|
+
function matchesScope(record2, query) {
|
|
15121
|
+
if (record2.ownerKey === query.ownerKey) {
|
|
14371
15122
|
return true;
|
|
14372
15123
|
}
|
|
14373
|
-
return query.includeShared &&
|
|
15124
|
+
return query.includeShared && record2.ownerKey === SHARED_OWNER_KEY;
|
|
14374
15125
|
}
|
|
14375
15126
|
function recall(records, queryEmbedding, query, deps) {
|
|
14376
15127
|
const asOf = query.asOf ?? deps.now;
|
|
14377
15128
|
const kinds = new Set(query.kinds);
|
|
14378
15129
|
const candidates = [];
|
|
14379
|
-
for (const
|
|
14380
|
-
if (!kinds.has(
|
|
15130
|
+
for (const record2 of filterActive(records, asOf)) {
|
|
15131
|
+
if (!kinds.has(record2.kind) || !matchesScope(record2, query)) {
|
|
14381
15132
|
continue;
|
|
14382
15133
|
}
|
|
14383
|
-
const embedding = deps.embeddings.get(
|
|
15134
|
+
const embedding = deps.embeddings.get(record2.id);
|
|
14384
15135
|
if (embedding === undefined) {
|
|
14385
15136
|
continue;
|
|
14386
15137
|
}
|
|
@@ -14388,7 +15139,7 @@ function recall(records, queryEmbedding, query, deps) {
|
|
|
14388
15139
|
if (similarity < query.minSimilarity) {
|
|
14389
15140
|
continue;
|
|
14390
15141
|
}
|
|
14391
|
-
candidates.push({ record, similarity });
|
|
15142
|
+
candidates.push({ record: record2, similarity });
|
|
14392
15143
|
}
|
|
14393
15144
|
const signals = {
|
|
14394
15145
|
now: deps.now,
|
|
@@ -14578,11 +15329,11 @@ function emptyState() {
|
|
|
14578
15329
|
return { facts: [], preferences: [], entities: [], relations: [] };
|
|
14579
15330
|
}
|
|
14580
15331
|
function applyTombstone(state, tombstone) {
|
|
14581
|
-
const mark = (records) => records.map((
|
|
14582
|
-
...
|
|
15332
|
+
const mark = (records) => records.map((record2) => record2.id === tombstone.id ? {
|
|
15333
|
+
...record2,
|
|
14583
15334
|
invalidatedAt: tombstone.invalidatedAt,
|
|
14584
15335
|
supersededBy: tombstone.supersededBy
|
|
14585
|
-
} :
|
|
15336
|
+
} : record2);
|
|
14586
15337
|
if (tombstone.kind === "fact") {
|
|
14587
15338
|
state.facts = mark(state.facts);
|
|
14588
15339
|
} else if (tombstone.kind === "preference") {
|
|
@@ -14622,8 +15373,8 @@ function allRecords(state) {
|
|
|
14622
15373
|
}
|
|
14623
15374
|
|
|
14624
15375
|
// src/memory/log.ts
|
|
14625
|
-
function serializeAssertion(
|
|
14626
|
-
return JSON.stringify(
|
|
15376
|
+
function serializeAssertion(record2) {
|
|
15377
|
+
return JSON.stringify(record2);
|
|
14627
15378
|
}
|
|
14628
15379
|
function parseMemoryLog(text2) {
|
|
14629
15380
|
const records = [];
|
|
@@ -14685,8 +15436,8 @@ function insertRecords(db, records) {
|
|
|
14685
15436
|
const stmt = db.query(INSERT_SQL2);
|
|
14686
15437
|
db.run("BEGIN");
|
|
14687
15438
|
try {
|
|
14688
|
-
for (const
|
|
14689
|
-
stmt.run(
|
|
15439
|
+
for (const record2 of records) {
|
|
15440
|
+
stmt.run(record2.id, record2.kind, record2.ownerKey, record2.createdAt, record2.invalidatedAt, JSON.stringify(record2));
|
|
14690
15441
|
}
|
|
14691
15442
|
db.run("COMMIT");
|
|
14692
15443
|
} catch (error) {
|
|
@@ -14739,15 +15490,15 @@ function createMemoryIndex(db, warn) {
|
|
|
14739
15490
|
const preferences = [];
|
|
14740
15491
|
const entities = [];
|
|
14741
15492
|
const relations = [];
|
|
14742
|
-
for (const
|
|
14743
|
-
if (
|
|
14744
|
-
facts.push(
|
|
14745
|
-
} else if (
|
|
14746
|
-
preferences.push(
|
|
14747
|
-
} else if (
|
|
14748
|
-
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);
|
|
14749
15500
|
} else {
|
|
14750
|
-
relations.push(
|
|
15501
|
+
relations.push(record2);
|
|
14751
15502
|
}
|
|
14752
15503
|
}
|
|
14753
15504
|
return { facts, preferences, entities, relations };
|
|
@@ -14886,31 +15637,31 @@ function createLocalEmbedder(request, deps) {
|
|
|
14886
15637
|
function normalize(parts) {
|
|
14887
15638
|
return parts.map((part) => part.trim()).filter((part) => part.length > 0).join(" ");
|
|
14888
15639
|
}
|
|
14889
|
-
function recordEmbeddingText(
|
|
14890
|
-
if (
|
|
15640
|
+
function recordEmbeddingText(record2) {
|
|
15641
|
+
if (record2.kind === "fact") {
|
|
14891
15642
|
return normalize([
|
|
14892
|
-
|
|
14893
|
-
|
|
14894
|
-
|
|
14895
|
-
|
|
15643
|
+
record2.subject,
|
|
15644
|
+
record2.predicate,
|
|
15645
|
+
record2.object,
|
|
15646
|
+
record2.category ?? ""
|
|
14896
15647
|
]);
|
|
14897
15648
|
}
|
|
14898
|
-
if (
|
|
15649
|
+
if (record2.kind === "preference") {
|
|
14899
15650
|
return normalize([
|
|
14900
|
-
|
|
14901
|
-
|
|
14902
|
-
|
|
15651
|
+
record2.category,
|
|
15652
|
+
record2.preference,
|
|
15653
|
+
record2.context ?? ""
|
|
14903
15654
|
]);
|
|
14904
15655
|
}
|
|
14905
|
-
if (
|
|
15656
|
+
if (record2.kind === "relation") {
|
|
14906
15657
|
return normalize([
|
|
14907
|
-
|
|
14908
|
-
|
|
14909
|
-
|
|
14910
|
-
|
|
15658
|
+
record2.from,
|
|
15659
|
+
record2.predicate,
|
|
15660
|
+
record2.to,
|
|
15661
|
+
record2.annotation ?? ""
|
|
14911
15662
|
]);
|
|
14912
15663
|
}
|
|
14913
|
-
return normalize([
|
|
15664
|
+
return normalize([record2.canonicalName, record2.type, ...record2.aliases]);
|
|
14914
15665
|
}
|
|
14915
15666
|
|
|
14916
15667
|
// src/storage/index/memoryEmbeddings.ts
|
|
@@ -14926,7 +15677,7 @@ async function ensureMemoryEmbeddings(deps) {
|
|
|
14926
15677
|
const records = index.allRecords();
|
|
14927
15678
|
index.pruneEmbeddings();
|
|
14928
15679
|
const have = modelChanged ? new Set : new Set(index.embeddingMap().keys());
|
|
14929
|
-
const todo = records.filter((
|
|
15680
|
+
const todo = records.filter((record2) => !have.has(record2.id));
|
|
14930
15681
|
if (todo.length === 0) {
|
|
14931
15682
|
return { embedded: 0, failed: 0, modelChanged };
|
|
14932
15683
|
}
|
|
@@ -14942,8 +15693,8 @@ async function ensureMemoryEmbeddings(deps) {
|
|
|
14942
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.`);
|
|
14943
15694
|
return { embedded: 0, failed: todo.length, modelChanged };
|
|
14944
15695
|
}
|
|
14945
|
-
const entries = todo.map((
|
|
14946
|
-
recordId:
|
|
15696
|
+
const entries = todo.map((record2, position) => ({
|
|
15697
|
+
recordId: record2.id,
|
|
14947
15698
|
vector: vectors[position]
|
|
14948
15699
|
}));
|
|
14949
15700
|
index.putEmbeddings(entries);
|
|
@@ -14992,7 +15743,7 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
|
|
|
14992
15743
|
// package.json
|
|
14993
15744
|
var package_default = {
|
|
14994
15745
|
name: "@jmanuelcorral/openteam",
|
|
14995
|
-
version: "0.
|
|
15746
|
+
version: "0.19.0",
|
|
14996
15747
|
packageManager: "bun@1.3.14",
|
|
14997
15748
|
description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
|
|
14998
15749
|
license: "MIT",
|
|
@@ -15183,30 +15934,30 @@ function isMissingFile(error) {
|
|
|
15183
15934
|
}
|
|
15184
15935
|
|
|
15185
15936
|
// src/memory/traverse.ts
|
|
15186
|
-
function inOwnerScope(
|
|
15187
|
-
if (
|
|
15937
|
+
function inOwnerScope(record2, ownerKey, includeShared) {
|
|
15938
|
+
if (record2.ownerKey === ownerKey) {
|
|
15188
15939
|
return true;
|
|
15189
15940
|
}
|
|
15190
|
-
return includeShared &&
|
|
15941
|
+
return includeShared && record2.ownerKey === SHARED_OWNER_KEY;
|
|
15191
15942
|
}
|
|
15192
15943
|
function traverseRelations(records, query) {
|
|
15193
15944
|
const includeShared = query.includeShared ?? true;
|
|
15194
15945
|
const adjacency = new Map;
|
|
15195
|
-
for (const
|
|
15196
|
-
if (
|
|
15946
|
+
for (const record2 of records) {
|
|
15947
|
+
if (record2.kind !== "relation") {
|
|
15197
15948
|
continue;
|
|
15198
15949
|
}
|
|
15199
|
-
if (!isActiveAt(
|
|
15950
|
+
if (!isActiveAt(record2, query.asOf)) {
|
|
15200
15951
|
continue;
|
|
15201
15952
|
}
|
|
15202
|
-
if (!inOwnerScope(
|
|
15953
|
+
if (!inOwnerScope(record2, query.ownerKey, includeShared)) {
|
|
15203
15954
|
continue;
|
|
15204
15955
|
}
|
|
15205
|
-
const list = adjacency.get(
|
|
15956
|
+
const list = adjacency.get(record2.from);
|
|
15206
15957
|
if (list === undefined) {
|
|
15207
|
-
adjacency.set(
|
|
15958
|
+
adjacency.set(record2.from, [record2]);
|
|
15208
15959
|
} else {
|
|
15209
|
-
list.push(
|
|
15960
|
+
list.push(record2);
|
|
15210
15961
|
}
|
|
15211
15962
|
}
|
|
15212
15963
|
const visited = new Set([query.seed]);
|
|
@@ -15237,8 +15988,8 @@ function traverseRelations(records, query) {
|
|
|
15237
15988
|
}
|
|
15238
15989
|
return { seed: query.seed, nodes, edges };
|
|
15239
15990
|
}
|
|
15240
|
-
function seedNodeId(
|
|
15241
|
-
return
|
|
15991
|
+
function seedNodeId(record2) {
|
|
15992
|
+
return record2.kind === "entity" ? record2.canonicalName : undefined;
|
|
15242
15993
|
}
|
|
15243
15994
|
function recallGraph(records, queryEmbedding, query, options, deps) {
|
|
15244
15995
|
const seeds = recall(records, queryEmbedding, query, deps);
|
|
@@ -15534,32 +16285,32 @@ function projectRelationGraph(graph) {
|
|
|
15534
16285
|
};
|
|
15535
16286
|
}
|
|
15536
16287
|
function projectRecall(results) {
|
|
15537
|
-
return results.map(({ record, similarity, score }) => {
|
|
16288
|
+
return results.map(({ record: record2, similarity, score }) => {
|
|
15538
16289
|
const base = {
|
|
15539
|
-
id:
|
|
15540
|
-
kind:
|
|
15541
|
-
ownerKey:
|
|
15542
|
-
confidence:
|
|
16290
|
+
id: record2.id,
|
|
16291
|
+
kind: record2.kind,
|
|
16292
|
+
ownerKey: record2.ownerKey,
|
|
16293
|
+
confidence: record2.confidence,
|
|
15543
16294
|
similarity,
|
|
15544
16295
|
score
|
|
15545
16296
|
};
|
|
15546
|
-
if (
|
|
16297
|
+
if (record2.kind === "fact") {
|
|
15547
16298
|
return {
|
|
15548
16299
|
...base,
|
|
15549
|
-
subject:
|
|
15550
|
-
predicate:
|
|
15551
|
-
object:
|
|
15552
|
-
category:
|
|
16300
|
+
subject: record2.subject,
|
|
16301
|
+
predicate: record2.predicate,
|
|
16302
|
+
object: record2.object,
|
|
16303
|
+
category: record2.category
|
|
15553
16304
|
};
|
|
15554
16305
|
}
|
|
15555
|
-
if (
|
|
16306
|
+
if (record2.kind === "preference") {
|
|
15556
16307
|
return {
|
|
15557
16308
|
...base,
|
|
15558
|
-
category:
|
|
15559
|
-
preference:
|
|
16309
|
+
category: record2.category,
|
|
16310
|
+
preference: record2.preference
|
|
15560
16311
|
};
|
|
15561
16312
|
}
|
|
15562
|
-
const entity =
|
|
16313
|
+
const entity = record2;
|
|
15563
16314
|
return { ...base, canonicalName: entity.canonicalName, type: entity.type };
|
|
15564
16315
|
});
|
|
15565
16316
|
}
|
|
@@ -15734,24 +16485,24 @@ var DEFAULT_DECAY_PARAMS = {
|
|
|
15734
16485
|
halfLifeMs: 90 * DAY_MS2,
|
|
15735
16486
|
forgetThreshold: 0.05
|
|
15736
16487
|
};
|
|
15737
|
-
function lastReinforcedAt(
|
|
15738
|
-
if (
|
|
15739
|
-
return
|
|
16488
|
+
function lastReinforcedAt(record2) {
|
|
16489
|
+
if (record2.kind === "preference" && record2.lastAccessedAt !== null) {
|
|
16490
|
+
return record2.lastAccessedAt;
|
|
15740
16491
|
}
|
|
15741
|
-
return
|
|
16492
|
+
return record2.createdAt;
|
|
15742
16493
|
}
|
|
15743
|
-
function decayedConfidence(
|
|
15744
|
-
const ageMs = Math.max(0, now - lastReinforcedAt(
|
|
15745
|
-
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);
|
|
15746
16497
|
}
|
|
15747
16498
|
function consolidate(records, now, params = DEFAULT_DECAY_PARAMS) {
|
|
15748
16499
|
const tombstones = [];
|
|
15749
|
-
for (const
|
|
15750
|
-
if (!isTransactionActiveAt(
|
|
16500
|
+
for (const record2 of records) {
|
|
16501
|
+
if (!isTransactionActiveAt(record2, now)) {
|
|
15751
16502
|
continue;
|
|
15752
16503
|
}
|
|
15753
|
-
if (decayedConfidence(
|
|
15754
|
-
tombstones.push({ ...
|
|
16504
|
+
if (decayedConfidence(record2, now, params) < params.forgetThreshold) {
|
|
16505
|
+
tombstones.push({ ...record2, invalidatedAt: now, supersededBy: null });
|
|
15755
16506
|
}
|
|
15756
16507
|
}
|
|
15757
16508
|
return tombstones;
|
|
@@ -15866,17 +16617,21 @@ async function loadConfig(path4, resolution = { explicit: false }) {
|
|
|
15866
16617
|
read: (requestedPath) => requestedPath === path4 ? readConfigFile(path4, resolution) : storage.read(requestedPath)
|
|
15867
16618
|
} : storage;
|
|
15868
16619
|
const overlayPath = resolution.explicit ? null : DEFAULT_LOCAL_OVERLAY_PATH;
|
|
15869
|
-
|
|
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;
|
|
15870
16625
|
}
|
|
15871
16626
|
async function saveConfig(config, path4, resolution = { explicit: false }) {
|
|
15872
16627
|
if (!resolution.explicit) {
|
|
15873
16628
|
await writeOpenTeamConfigFile(config, path4, storage);
|
|
15874
16629
|
return;
|
|
15875
16630
|
}
|
|
16631
|
+
const contents = serializeOpenTeamConfig(config);
|
|
15876
16632
|
const diskPath = resolve6(path4);
|
|
15877
16633
|
await mkdir2(dirname6(diskPath), { recursive: true });
|
|
15878
|
-
await writeFile2(diskPath,
|
|
15879
|
-
`, "utf8");
|
|
16634
|
+
await writeFile2(diskPath, contents, "utf8");
|
|
15880
16635
|
}
|
|
15881
16636
|
function parseConfigSelection(argv) {
|
|
15882
16637
|
for (let i = 0;i < argv.length; i += 1) {
|