@jmanuelcorral/openteam 0.9.4 → 0.11.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/AGENTS.md +1 -1
- package/README.es.md +8 -5
- package/README.md +16 -5
- package/dist/cli.js +475 -102
- package/dist/commands/dispatch.d.ts.map +1 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/local.d.ts +13 -0
- package/dist/commands/local.d.ts.map +1 -1
- package/dist/commands/rolesInit.d.ts +55 -0
- package/dist/commands/rolesInit.d.ts.map +1 -0
- package/dist/commands/setup.d.ts +17 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/config/errors.d.ts.map +1 -1
- package/dist/config/schema.d.ts +2 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +222 -92
- package/dist/messages/commands.d.ts +50 -2
- package/dist/messages/commands.d.ts.map +1 -1
- package/dist/orchestrator/roles.d.ts +20 -0
- package/dist/orchestrator/roles.d.ts.map +1 -1
- package/dist/orchestrator/runtimeSelection.d.ts.map +1 -1
- package/dist/plugin/registerCastTool.d.ts +19 -0
- package/dist/plugin/registerCastTool.d.ts.map +1 -1
- package/dist/router/chooseModel.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -101,7 +101,8 @@ var AgentRoleProfileSchema = z.object({
|
|
|
101
101
|
preferredFrontierModels: z.array(z.union([modelSelectionSchema, z.literal("auto")])),
|
|
102
102
|
localFirst: z.boolean(),
|
|
103
103
|
requiresLocalRuntime: z.boolean().optional(),
|
|
104
|
-
localRuntimes: z.array(z.enum(["ollama", "lmstudio", "foundry-local", "lemonade"])).optional()
|
|
104
|
+
localRuntimes: z.array(z.enum(["ollama", "lmstudio", "foundry-local", "lemonade"])).optional(),
|
|
105
|
+
localModels: z.partialRecord(z.enum(["ollama", "lmstudio", "foundry-local", "lemonade"]), z.string().min(1)).optional()
|
|
105
106
|
}).strict();
|
|
106
107
|
var TEAM_ROLES = {
|
|
107
108
|
architect: {
|
|
@@ -392,6 +393,9 @@ var OrchestratorRolesSchema = z2.record(z2.string().min(1), z2.unknown()).defaul
|
|
|
392
393
|
if (parsed.data.localRuntimes !== undefined) {
|
|
393
394
|
profile.localRuntimes = parsed.data.localRuntimes;
|
|
394
395
|
}
|
|
396
|
+
if (parsed.data.localModels !== undefined) {
|
|
397
|
+
profile.localModels = parsed.data.localModels;
|
|
398
|
+
}
|
|
395
399
|
parsedRoles[roleID] = profile;
|
|
396
400
|
}
|
|
397
401
|
return parsedRoles;
|
|
@@ -403,6 +407,7 @@ var defaultFrontierModel = {
|
|
|
403
407
|
providerID: "anthropic",
|
|
404
408
|
modelID: "claude-sonnet-4-5"
|
|
405
409
|
};
|
|
410
|
+
var DEFAULT_LOCAL_MAX_CONCURRENCY = 4;
|
|
406
411
|
var LocalRuntimeSchema = z2.object({
|
|
407
412
|
id: z2.enum(["ollama", "lmstudio", "foundry-local", "lemonade"]),
|
|
408
413
|
enabled: z2.boolean().default(true),
|
|
@@ -473,13 +478,23 @@ var OpenTeamConfigObjectSchema = z2.object({
|
|
|
473
478
|
localDefault: ModelRefSchema.nullable().default(null),
|
|
474
479
|
trivialPromptMaxChars: z2.number().int().positive().default(280),
|
|
475
480
|
frontierPromptMinChars: z2.number().int().positive().default(2000),
|
|
476
|
-
frontierOnly: z2.boolean().default(false)
|
|
477
|
-
|
|
481
|
+
frontierOnly: z2.boolean().default(false),
|
|
482
|
+
localOnly: z2.boolean().default(false)
|
|
483
|
+
}).strict().superRefine((router, ctx) => {
|
|
484
|
+
if (router.frontierOnly && router.localOnly) {
|
|
485
|
+
ctx.addIssue({
|
|
486
|
+
code: "custom",
|
|
487
|
+
path: ["localOnly"],
|
|
488
|
+
message: "router.localOnly and router.frontierOnly cannot both be true"
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}).default({
|
|
478
492
|
mode: "balanced",
|
|
479
493
|
localDefault: null,
|
|
480
494
|
trivialPromptMaxChars: 280,
|
|
481
495
|
frontierPromptMinChars: 2000,
|
|
482
|
-
frontierOnly: false
|
|
496
|
+
frontierOnly: false,
|
|
497
|
+
localOnly: false
|
|
483
498
|
}),
|
|
484
499
|
local: z2.object({
|
|
485
500
|
runtimes: z2.array(LocalRuntimeSchema).default([])
|
|
@@ -863,6 +878,16 @@ function chooseModel(input) {
|
|
|
863
878
|
if (override === "alwaysFrontier") {
|
|
864
879
|
return finalizeDecision(input, tier, frontier, budgetAction, decision(frontier.model, "frontier", ["explicit-always-frontier"], []));
|
|
865
880
|
}
|
|
881
|
+
if (input.config.router.localOnly) {
|
|
882
|
+
if (local === null) {
|
|
883
|
+
return finalizeDecision(input, tier, frontier, budgetAction, blockedDecision(frontier.model, ["router-local-only", "forced-local-none-configured"], "forced-local-none-configured"));
|
|
884
|
+
}
|
|
885
|
+
return finalizeDecision(input, tier, frontier, budgetAction, resolveLocalPrimary(input, local, frontier.model, {
|
|
886
|
+
baseRationale: ["router-local-only"],
|
|
887
|
+
allowFrontier: false,
|
|
888
|
+
noLocalRationale: "forced-local-unavailable"
|
|
889
|
+
}));
|
|
890
|
+
}
|
|
866
891
|
if (input.config.router.frontierOnly) {
|
|
867
892
|
return finalizeDecision(input, tier, frontier, budgetAction, decision(frontier.model, "frontier", ["router-frontier-only"], []));
|
|
868
893
|
}
|
|
@@ -1485,6 +1510,10 @@ function configuredRuntime(runtimeId, config) {
|
|
|
1485
1510
|
function runtimeIdOfModel(model, config) {
|
|
1486
1511
|
return config.local.runtimes.find((runtime) => sameModel(runtime.defaultModel, model))?.id;
|
|
1487
1512
|
}
|
|
1513
|
+
function roleLocalModel(role, runtimeId, fallback) {
|
|
1514
|
+
const pinned = role.localModels?.[runtimeId];
|
|
1515
|
+
return pinned === undefined ? fallback : { providerID: runtimeId, modelID: pinned };
|
|
1516
|
+
}
|
|
1488
1517
|
function selectDispatchRuntime(role, decision2, reachable, config) {
|
|
1489
1518
|
if (decision2.routeKind !== "local") {
|
|
1490
1519
|
return { runtimeId: undefined, decision: decision2 };
|
|
@@ -1499,15 +1528,25 @@ function selectDispatchRuntime(role, decision2, reachable, config) {
|
|
|
1499
1528
|
if (runtime === undefined) {
|
|
1500
1529
|
continue;
|
|
1501
1530
|
}
|
|
1502
|
-
const model = runtime.defaultModel;
|
|
1531
|
+
const model = roleLocalModel(role, runtimeId, runtime.defaultModel);
|
|
1503
1532
|
return {
|
|
1504
1533
|
runtimeId,
|
|
1505
1534
|
decision: { ...decision2, selected: model }
|
|
1506
1535
|
};
|
|
1507
1536
|
}
|
|
1508
1537
|
}
|
|
1538
|
+
const resolvedRuntimeId = runtimeIdOfModel(decision2.selected, config);
|
|
1539
|
+
if (resolvedRuntimeId !== undefined) {
|
|
1540
|
+
const pinned = roleLocalModel(role, resolvedRuntimeId, decision2.selected);
|
|
1541
|
+
if (pinned !== decision2.selected) {
|
|
1542
|
+
return {
|
|
1543
|
+
runtimeId: resolvedRuntimeId,
|
|
1544
|
+
decision: { ...decision2, selected: pinned }
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1509
1548
|
return {
|
|
1510
|
-
runtimeId:
|
|
1549
|
+
runtimeId: resolvedRuntimeId,
|
|
1511
1550
|
decision: decision2
|
|
1512
1551
|
};
|
|
1513
1552
|
}
|
|
@@ -2877,7 +2916,8 @@ var KNOWN_CONFIG_KEYS_BY_PATH = new Map([
|
|
|
2877
2916
|
"localDefault",
|
|
2878
2917
|
"trivialPromptMaxChars",
|
|
2879
2918
|
"frontierPromptMinChars",
|
|
2880
|
-
"frontierOnly"
|
|
2919
|
+
"frontierOnly",
|
|
2920
|
+
"localOnly"
|
|
2881
2921
|
]
|
|
2882
2922
|
],
|
|
2883
2923
|
["router.localDefault", MODEL_REF_KEYS],
|
|
@@ -9899,6 +9939,123 @@ function isGateApplicable(mode) {
|
|
|
9899
9939
|
return mode === "active";
|
|
9900
9940
|
}
|
|
9901
9941
|
|
|
9942
|
+
// src/messages/commands.ts
|
|
9943
|
+
var clearCacheMessages = {
|
|
9944
|
+
header: "openteam clear-cache — frozen plugin cache entries:",
|
|
9945
|
+
columns: {
|
|
9946
|
+
specDir: "spec dir",
|
|
9947
|
+
pinned: "spec-pinned",
|
|
9948
|
+
installed: "installed",
|
|
9949
|
+
mtime: "mtime"
|
|
9950
|
+
},
|
|
9951
|
+
reparseSkipSuffix: " [SKIP — reparse point]",
|
|
9952
|
+
deletedLabel: "deleted.",
|
|
9953
|
+
lockedLabel: (message) => `[LOCKED] ${message}`,
|
|
9954
|
+
pathOutsideWarning: (specDir, absolutePath) => ` ⚠ ${specDir}: path outside cacheRoot (${absolutePath}), skipped.`,
|
|
9955
|
+
processed: (count) => `${count} entry(ies) processed.`,
|
|
9956
|
+
found: (count) => `${count} entry(ies) found. Use --delete to remove them.`
|
|
9957
|
+
};
|
|
9958
|
+
var baselineMessages = {
|
|
9959
|
+
effectiveAuto: "cheapest-capable (auto)",
|
|
9960
|
+
pinnedSuffix: (ref) => `${ref} (pinned)`,
|
|
9961
|
+
summary: (params) => [
|
|
9962
|
+
"openteam baseline:",
|
|
9963
|
+
` mode: ${params.mode}`,
|
|
9964
|
+
` pinned: ${params.pinned}`,
|
|
9965
|
+
` hardDefault: ${params.hardDefault}`,
|
|
9966
|
+
` effective: ${params.effective}`
|
|
9967
|
+
],
|
|
9968
|
+
invalidModel: (input) => `Invalid model "${input}". Use the provider/model format, e.g. anthropic/claude-sonnet-4-5.`,
|
|
9969
|
+
pinnedTo: (ref) => `Baseline pinned to ${ref} (pinned mode).`,
|
|
9970
|
+
autoMode: "Baseline set to auto mode (cheapest-capable)."
|
|
9971
|
+
};
|
|
9972
|
+
var localMessages = {
|
|
9973
|
+
help: {
|
|
9974
|
+
status: " openteam local status Show the routing mode (local-first / local-only / frontier-only)",
|
|
9975
|
+
off: " openteam local off Use frontier models only (no local runtime)",
|
|
9976
|
+
only: " openteam local only Use local models only (never frontier)",
|
|
9977
|
+
on: " openteam local on Re-enable local-first routing"
|
|
9978
|
+
},
|
|
9979
|
+
noRuntimes: "none",
|
|
9980
|
+
modeFrontierOnly: "frontier-only (frontierOnly)",
|
|
9981
|
+
modeLocalOnly: "local-only (localOnly)",
|
|
9982
|
+
modeLocalFirst: "local-first",
|
|
9983
|
+
summary: (params) => [
|
|
9984
|
+
"openteam local:",
|
|
9985
|
+
` mode: ${params.mode}`,
|
|
9986
|
+
` frontierOnly: ${params.frontierOnly}`,
|
|
9987
|
+
` localOnly: ${params.localOnly}`,
|
|
9988
|
+
` privacy: ${params.privacy}`,
|
|
9989
|
+
` runtimes: ${params.runtimes}`
|
|
9990
|
+
],
|
|
9991
|
+
frontierOnlyNoChange: "No change: already in frontier-only mode.",
|
|
9992
|
+
privacyAdjustedNote: " Privacy adjusted to 'consentBeforeFrontier' (forceLocalOnSensitive requires a local runtime).",
|
|
9993
|
+
frontierOnlyEnabled: "Frontier-only mode enabled (frontierOnly=true, localOnly=false).",
|
|
9994
|
+
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.",
|
|
9995
|
+
localOnlyNoChange: "No change: already in local-only mode.",
|
|
9996
|
+
localOnlyEnabled: "Local-only mode enabled (localOnly=true, frontierOnly=false).",
|
|
9997
|
+
localFirstNoChange: "No change: local-first routing is already active.",
|
|
9998
|
+
localFirstReEnabled: "Local-first routing re-enabled (frontierOnly=false, localOnly=false). Make sure a local runtime is reachable.",
|
|
9999
|
+
unknownSubcommand: (subcommand, help) => `Unknown local subcommand: ${subcommand}
|
|
10000
|
+
|
|
10001
|
+
${help}`
|
|
10002
|
+
};
|
|
10003
|
+
var frontierCatalogMessages = {
|
|
10004
|
+
free: "free",
|
|
10005
|
+
costPerMillion: (inputUSD, outputUSD) => `$${inputUSD}/$${outputUSD} per 1M`
|
|
10006
|
+
};
|
|
10007
|
+
var migrateMessages = {
|
|
10008
|
+
header: "openteam migrate:",
|
|
10009
|
+
nothingToMigrate: " nothing to migrate — the P15b layout is already in place.",
|
|
10010
|
+
counts: (params) => [
|
|
10011
|
+
` moved: ${params.moved} file(s)`,
|
|
10012
|
+
` deduped: ${params.deduped} file(s) (destination already identical)`,
|
|
10013
|
+
` conflicts: ${params.conflicts} file(s)`
|
|
10014
|
+
],
|
|
10015
|
+
relocatedHeader: " relocated:",
|
|
10016
|
+
relocatedEntry: (from, to, deduped) => ` · ${from} → ${to}${deduped ? " (deduped)" : ""}`,
|
|
10017
|
+
conflictsHeader: " ⚠ left in place (destination exists with different content — no data lost):",
|
|
10018
|
+
conflictEntry: (from, to) => ` · ${from} → ${to}`,
|
|
10019
|
+
manualWorktreesHeader: " ⚠ legacy git worktree(s) need a manual move (run `git worktree move`):",
|
|
10020
|
+
manualWorktreeEntry: (label, from, to) => ` · ${label}: ${from} → ${to}`,
|
|
10021
|
+
success: " ✓ machine-local runtime output relocated under .opencode/openteam-local/."
|
|
10022
|
+
};
|
|
10023
|
+
var rolesInitMessages = {
|
|
10024
|
+
header: "openteam roles init:",
|
|
10025
|
+
title: "openteam roles init",
|
|
10026
|
+
noRoster: (path) => ` ✗ no roster at ${path}.`,
|
|
10027
|
+
noRosterRemedy: " Cast the team first: run `openteam setup`, then ask the orchestrator to register the cast.",
|
|
10028
|
+
unparseableRoster: (path, error) => ` ✗ ${path} is present but unparseable: ${error}`,
|
|
10029
|
+
unparseableRosterRemedy: " Run `openteam doctor` for the remedy, then re-run this command.",
|
|
10030
|
+
allProfiled: (count) => ` ✓ ${count} roster role(s) checked; no unprofiled roles — every role resolves a routing profile.`,
|
|
10031
|
+
whyTitle: "Why you are being asked",
|
|
10032
|
+
why: (count) => [
|
|
10033
|
+
`${count} roster role(s) resolve no routing profile.`,
|
|
10034
|
+
"Until a profile exists they are frontier-eligible: their work can escalate",
|
|
10035
|
+
"to a frontier model. A 'local only' note in the roster prose does not",
|
|
10036
|
+
"prevent this — prose outside the JSON fence is never read."
|
|
10037
|
+
].join(`
|
|
10038
|
+
`),
|
|
10039
|
+
policyQuestion: (roleID, agentName) => `Routing policy for "${roleID}" (${agentName})`,
|
|
10040
|
+
localOnlyLabel: (roleID, agentName) => `Local only — ${roleID} (${agentName}) never leaves this machine`,
|
|
10041
|
+
localOnlyHint: "sets requiresLocalRuntime; the role fails closed when no local runtime is reachable",
|
|
10042
|
+
frontierOkLabel: "Local first, frontier allowed",
|
|
10043
|
+
frontierOkHint: "records today's implicit behaviour: cheapest-capable frontier when the task warrants it",
|
|
10044
|
+
skipLabel: "Skip for now",
|
|
10045
|
+
skipHint: "leaves the role unprofiled, so it stays frontier-eligible and doctor keeps reporting it",
|
|
10046
|
+
runtimeQuestion: (roleID) => `Preferred local runtime(s) for "${roleID}" (in failover order)`,
|
|
10047
|
+
runtimeHint: "machine-local — written to the git-ignored overlay",
|
|
10048
|
+
cancelled: (reason) => ` ✗ cancelled (${reason}); nothing was written.`,
|
|
10049
|
+
invalidResult: (reason) => ` ✗ refusing to write: the resulting config is invalid — ${reason}`,
|
|
10050
|
+
nothingWritten: (skipped) => ` · skipped ${skipped}; nothing written.`,
|
|
10051
|
+
nothingToWriteOutro: "Nothing to write.",
|
|
10052
|
+
pinnedLocal: (roleIDs, path) => ` ✓ pinned local-only: ${roleIDs} → ${path} (tracked, travels with the roster)`,
|
|
10053
|
+
frontierAllowed: (roleIDs, path) => ` ✓ frontier allowed: ${roleIDs} → ${path} (model choice stays "auto": cheapest-capable per task)`,
|
|
10054
|
+
runtimeBinding: (path) => ` ✓ runtime binding: ${path} (git-ignored, machine-local)`,
|
|
10055
|
+
skippedSummary: (roleIDs) => ` · skipped: ${roleIDs} — still frontier-eligible; re-run this command to revisit.`,
|
|
10056
|
+
outro: "Role policy updated."
|
|
10057
|
+
};
|
|
10058
|
+
|
|
9902
10059
|
// src/orchestrator/ralphLoop.ts
|
|
9903
10060
|
var RALPH_ROLE_ID = "ralph";
|
|
9904
10061
|
var DEFAULT_RALPH_MAX_ITERATIONS = 10;
|
|
@@ -10253,74 +10410,6 @@ function describeOtelBackendForDoctor(config, env) {
|
|
|
10253
10410
|
};
|
|
10254
10411
|
}
|
|
10255
10412
|
|
|
10256
|
-
// src/messages/commands.ts
|
|
10257
|
-
var clearCacheMessages = {
|
|
10258
|
-
header: "openteam clear-cache — frozen plugin cache entries:",
|
|
10259
|
-
columns: {
|
|
10260
|
-
specDir: "spec dir",
|
|
10261
|
-
pinned: "spec-pinned",
|
|
10262
|
-
installed: "installed",
|
|
10263
|
-
mtime: "mtime"
|
|
10264
|
-
},
|
|
10265
|
-
reparseSkipSuffix: " [SKIP — reparse point]",
|
|
10266
|
-
deletedLabel: "deleted.",
|
|
10267
|
-
lockedLabel: (message) => `[LOCKED] ${message}`,
|
|
10268
|
-
pathOutsideWarning: (specDir, absolutePath) => ` ⚠ ${specDir}: path outside cacheRoot (${absolutePath}), skipped.`,
|
|
10269
|
-
processed: (count) => `${count} entry(ies) processed.`,
|
|
10270
|
-
found: (count) => `${count} entry(ies) found. Use --delete to remove them.`
|
|
10271
|
-
};
|
|
10272
|
-
var baselineMessages = {
|
|
10273
|
-
effectiveAuto: "cheapest-capable (auto)",
|
|
10274
|
-
pinnedSuffix: (ref) => `${ref} (pinned)`,
|
|
10275
|
-
summary: (params) => [
|
|
10276
|
-
"openteam baseline:",
|
|
10277
|
-
` mode: ${params.mode}`,
|
|
10278
|
-
` pinned: ${params.pinned}`,
|
|
10279
|
-
` hardDefault: ${params.hardDefault}`,
|
|
10280
|
-
` effective: ${params.effective}`
|
|
10281
|
-
],
|
|
10282
|
-
invalidModel: (input) => `Invalid model "${input}". Use the provider/model format, e.g. anthropic/claude-sonnet-4-5.`,
|
|
10283
|
-
pinnedTo: (ref) => `Baseline pinned to ${ref} (pinned mode).`,
|
|
10284
|
-
autoMode: "Baseline set to auto mode (cheapest-capable)."
|
|
10285
|
-
};
|
|
10286
|
-
var localMessages = {
|
|
10287
|
-
noRuntimes: "none",
|
|
10288
|
-
modeFrontierOnly: "frontier-only (frontierOnly)",
|
|
10289
|
-
modeLocalFirst: "local-first",
|
|
10290
|
-
summary: (params) => [
|
|
10291
|
-
"openteam local:",
|
|
10292
|
-
` mode: ${params.mode}`,
|
|
10293
|
-
` frontierOnly: ${params.frontierOnly}`,
|
|
10294
|
-
` privacy: ${params.privacy}`,
|
|
10295
|
-
` runtimes: ${params.runtimes}`
|
|
10296
|
-
],
|
|
10297
|
-
frontierOnlyNoChange: "No change: already in frontier-only mode.",
|
|
10298
|
-
privacyAdjustedNote: " Privacy adjusted to 'consentBeforeFrontier' (forceLocalOnSensitive requires a local runtime).",
|
|
10299
|
-
frontierOnlyEnabled: "Frontier-only mode enabled (frontierOnly=true).",
|
|
10300
|
-
localFirstNoChange: "No change: local-first routing is already active.",
|
|
10301
|
-
localFirstReEnabled: "Local-first routing re-enabled (frontierOnly=false). Make sure a local runtime is reachable."
|
|
10302
|
-
};
|
|
10303
|
-
var frontierCatalogMessages = {
|
|
10304
|
-
free: "free",
|
|
10305
|
-
costPerMillion: (inputUSD, outputUSD) => `$${inputUSD}/$${outputUSD} per 1M`
|
|
10306
|
-
};
|
|
10307
|
-
var migrateMessages = {
|
|
10308
|
-
header: "openteam migrate:",
|
|
10309
|
-
nothingToMigrate: " nothing to migrate — the P15b layout is already in place.",
|
|
10310
|
-
counts: (params) => [
|
|
10311
|
-
` moved: ${params.moved} file(s)`,
|
|
10312
|
-
` deduped: ${params.deduped} file(s) (destination already identical)`,
|
|
10313
|
-
` conflicts: ${params.conflicts} file(s)`
|
|
10314
|
-
],
|
|
10315
|
-
relocatedHeader: " relocated:",
|
|
10316
|
-
relocatedEntry: (from, to, deduped) => ` · ${from} → ${to}${deduped ? " (deduped)" : ""}`,
|
|
10317
|
-
conflictsHeader: " ⚠ left in place (destination exists with different content — no data lost):",
|
|
10318
|
-
conflictEntry: (from, to) => ` · ${from} → ${to}`,
|
|
10319
|
-
manualWorktreesHeader: " ⚠ legacy git worktree(s) need a manual move (run `git worktree move`):",
|
|
10320
|
-
manualWorktreeEntry: (label, from, to) => ` · ${label}: ${from} → ${to}`,
|
|
10321
|
-
success: " ✓ machine-local runtime output relocated under .opencode/openteam-local/."
|
|
10322
|
-
};
|
|
10323
|
-
|
|
10324
10413
|
// src/commands/baseline.ts
|
|
10325
10414
|
function formatRef(ref) {
|
|
10326
10415
|
return ref === null ? "—" : `${ref.providerID}/${ref.modelID}`;
|
|
@@ -10463,10 +10552,23 @@ function renderConsoleStatus(console_) {
|
|
|
10463
10552
|
}
|
|
10464
10553
|
|
|
10465
10554
|
// src/commands/doctor.ts
|
|
10466
|
-
function runtimeLine(snapshot) {
|
|
10555
|
+
function runtimeLine(snapshot, runtime) {
|
|
10467
10556
|
const mark = snapshot.reachable ? "✓" : "✗";
|
|
10468
10557
|
const detail = snapshot.reachable ? `${snapshot.models.length} model(s)` : snapshot.error ?? "unreachable";
|
|
10469
|
-
|
|
10558
|
+
const declared = runtime?.maxConcurrency;
|
|
10559
|
+
const slots = declared === undefined ? ` · ${DEFAULT_LOCAL_MAX_CONCURRENCY} slot(s) (default)` : ` · ${declared} slot(s) (declared)`;
|
|
10560
|
+
return ` ${mark} ${snapshot.id.padEnd(14)} ${snapshot.baseURL || "(no baseURL)"} — ${detail}${slots}`;
|
|
10561
|
+
}
|
|
10562
|
+
function isNetworkRuntime(baseURL) {
|
|
10563
|
+
if (baseURL.length === 0) {
|
|
10564
|
+
return false;
|
|
10565
|
+
}
|
|
10566
|
+
try {
|
|
10567
|
+
const host = new URL(baseURL).hostname.toLowerCase();
|
|
10568
|
+
return host !== "localhost" && host !== "127.0.0.1" && host !== "::1" && host !== "[::1]" && !host.endsWith(".localhost");
|
|
10569
|
+
} catch {
|
|
10570
|
+
return false;
|
|
10571
|
+
}
|
|
10470
10572
|
}
|
|
10471
10573
|
function failureReasonText(reason, searchedPaths) {
|
|
10472
10574
|
switch (reason) {
|
|
@@ -10622,7 +10724,8 @@ function rosterHealthSection(audit, loadError, rosterRoleCount) {
|
|
|
10622
10724
|
if (unprofiled.length > 0) {
|
|
10623
10725
|
const roleIDs = unprofiled.map((entry) => entry.roleID).join(", ");
|
|
10624
10726
|
lines.push(` · unprofiled roleID(s): ${roleIDs}`);
|
|
10625
|
-
lines.push("
|
|
10727
|
+
lines.push(" routing: no profile means frontier-eligible — work for these roles can escalate to a frontier model. A 'local only' note in the roster prose does NOT prevent this: prose outside the JSON fence is never read.");
|
|
10728
|
+
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.');
|
|
10626
10729
|
}
|
|
10627
10730
|
return lines;
|
|
10628
10731
|
}
|
|
@@ -10657,8 +10760,16 @@ function renderDoctor(input) {
|
|
|
10657
10760
|
lines.push(" (no runtime enabled)");
|
|
10658
10761
|
} else {
|
|
10659
10762
|
for (const snapshot of input.snapshots) {
|
|
10660
|
-
lines.push(runtimeLine(snapshot));
|
|
10763
|
+
lines.push(runtimeLine(snapshot, enabledRuntimes.find((r) => r.id === snapshot.id)));
|
|
10764
|
+
}
|
|
10765
|
+
}
|
|
10766
|
+
for (const snapshot of input.snapshots) {
|
|
10767
|
+
if (!isNetworkRuntime(snapshot.baseURL)) {
|
|
10768
|
+
continue;
|
|
10661
10769
|
}
|
|
10770
|
+
const runtime = enabledRuntimes.find((r) => r.id === snapshot.id);
|
|
10771
|
+
const cap = runtime?.maxConcurrency ?? DEFAULT_LOCAL_MAX_CONCURRENCY;
|
|
10772
|
+
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.");
|
|
10662
10773
|
}
|
|
10663
10774
|
lines.push(` telemetry: ${input.telemetryPath} — ${input.telemetryRecords} record(s)`);
|
|
10664
10775
|
if (input.diagnostics !== undefined) {
|
|
@@ -10800,6 +10911,9 @@ function renderWorktreeReconciliation(report) {
|
|
|
10800
10911
|
}
|
|
10801
10912
|
|
|
10802
10913
|
// src/commands/local.ts
|
|
10914
|
+
function hasEnabledLocalRuntime(config) {
|
|
10915
|
+
return config.local.runtimes.some((runtime) => runtime.enabled);
|
|
10916
|
+
}
|
|
10803
10917
|
function enabledRuntimesSummary(config) {
|
|
10804
10918
|
const enabled = config.local.runtimes.filter((runtime) => runtime.enabled);
|
|
10805
10919
|
if (enabled.length === 0) {
|
|
@@ -10812,10 +10926,11 @@ function enabledRuntimesSummary(config) {
|
|
|
10812
10926
|
}).join(", ");
|
|
10813
10927
|
}
|
|
10814
10928
|
function showLocal(config) {
|
|
10815
|
-
const mode = config.router.frontierOnly ? localMessages.modeFrontierOnly : localMessages.modeLocalFirst;
|
|
10929
|
+
const mode = config.router.localOnly ? localMessages.modeLocalOnly : config.router.frontierOnly ? localMessages.modeFrontierOnly : localMessages.modeLocalFirst;
|
|
10816
10930
|
const lines = localMessages.summary({
|
|
10817
10931
|
mode,
|
|
10818
10932
|
frontierOnly: config.router.frontierOnly,
|
|
10933
|
+
localOnly: config.router.localOnly,
|
|
10819
10934
|
privacy: config.privacyMode,
|
|
10820
10935
|
runtimes: enabledRuntimesSummary(config)
|
|
10821
10936
|
});
|
|
@@ -10823,13 +10938,13 @@ function showLocal(config) {
|
|
|
10823
10938
|
`) };
|
|
10824
10939
|
}
|
|
10825
10940
|
function setFrontierOnly(config) {
|
|
10826
|
-
if (config.router.frontierOnly) {
|
|
10941
|
+
if (config.router.frontierOnly && !config.router.localOnly) {
|
|
10827
10942
|
return { message: localMessages.frontierOnlyNoChange };
|
|
10828
10943
|
}
|
|
10829
10944
|
const flipsPrivacy = config.privacyMode === "forceLocalOnSensitive";
|
|
10830
10945
|
const next = {
|
|
10831
10946
|
...config,
|
|
10832
|
-
router: { ...config.router, frontierOnly: true },
|
|
10947
|
+
router: { ...config.router, frontierOnly: true, localOnly: false },
|
|
10833
10948
|
privacyMode: flipsPrivacy ? "consentBeforeFrontier" : config.privacyMode
|
|
10834
10949
|
};
|
|
10835
10950
|
const privacyNote = flipsPrivacy ? localMessages.privacyAdjustedNote : "";
|
|
@@ -10838,13 +10953,29 @@ function setFrontierOnly(config) {
|
|
|
10838
10953
|
message: `${localMessages.frontierOnlyEnabled}${privacyNote}`
|
|
10839
10954
|
};
|
|
10840
10955
|
}
|
|
10956
|
+
function setLocalOnly(config) {
|
|
10957
|
+
if (!hasEnabledLocalRuntime(config)) {
|
|
10958
|
+
return { message: localMessages.localOnlyNoRuntimes };
|
|
10959
|
+
}
|
|
10960
|
+
if (config.router.localOnly && !config.router.frontierOnly) {
|
|
10961
|
+
return { message: localMessages.localOnlyNoChange };
|
|
10962
|
+
}
|
|
10963
|
+
const next = {
|
|
10964
|
+
...config,
|
|
10965
|
+
router: { ...config.router, frontierOnly: false, localOnly: true }
|
|
10966
|
+
};
|
|
10967
|
+
return {
|
|
10968
|
+
config: next,
|
|
10969
|
+
message: localMessages.localOnlyEnabled
|
|
10970
|
+
};
|
|
10971
|
+
}
|
|
10841
10972
|
function setLocalFirst(config) {
|
|
10842
|
-
if (!config.router.frontierOnly) {
|
|
10973
|
+
if (!config.router.frontierOnly && !config.router.localOnly) {
|
|
10843
10974
|
return { message: localMessages.localFirstNoChange };
|
|
10844
10975
|
}
|
|
10845
10976
|
const next = {
|
|
10846
10977
|
...config,
|
|
10847
|
-
router: { ...config.router, frontierOnly: false }
|
|
10978
|
+
router: { ...config.router, frontierOnly: false, localOnly: false }
|
|
10848
10979
|
};
|
|
10849
10980
|
return {
|
|
10850
10981
|
config: next,
|
|
@@ -11162,7 +11293,8 @@ function buildOpenTeamConfig(answers) {
|
|
|
11162
11293
|
const runtime = {
|
|
11163
11294
|
id: choice.id,
|
|
11164
11295
|
enabled: choice.enabled,
|
|
11165
|
-
defaultModel: { providerID: choice.id, modelID: choice.defaultModelID }
|
|
11296
|
+
defaultModel: { providerID: choice.id, modelID: choice.defaultModelID },
|
|
11297
|
+
maxConcurrency: choice.maxConcurrency ?? DEFAULT_LOCAL_MAX_CONCURRENCY
|
|
11166
11298
|
};
|
|
11167
11299
|
if (choice.baseURL !== undefined) {
|
|
11168
11300
|
runtime.baseURL = choice.baseURL;
|
|
@@ -11173,6 +11305,7 @@ function buildOpenTeamConfig(answers) {
|
|
|
11173
11305
|
});
|
|
11174
11306
|
const primaryLocal = firstEnabled(answers.runtimes);
|
|
11175
11307
|
const localDefault2 = primaryLocal !== undefined ? { providerID: primaryLocal.id, modelID: primaryLocal.defaultModelID } : null;
|
|
11308
|
+
const localOnly = answers.localOnly === true && primaryLocal !== undefined;
|
|
11176
11309
|
return OpenTeamConfigSchema.parse({
|
|
11177
11310
|
baseline: {
|
|
11178
11311
|
mode: "auto",
|
|
@@ -11182,7 +11315,8 @@ function buildOpenTeamConfig(answers) {
|
|
|
11182
11315
|
router: {
|
|
11183
11316
|
mode: answers.routerMode,
|
|
11184
11317
|
localDefault: localDefault2,
|
|
11185
|
-
frontierOnly: primaryLocal === undefined
|
|
11318
|
+
frontierOnly: primaryLocal === undefined,
|
|
11319
|
+
localOnly
|
|
11186
11320
|
},
|
|
11187
11321
|
local: { runtimes },
|
|
11188
11322
|
privacyMode: answers.privacyMode,
|
|
@@ -11212,15 +11346,17 @@ function buildOpencodeConfig(answers) {
|
|
|
11212
11346
|
models
|
|
11213
11347
|
};
|
|
11214
11348
|
}
|
|
11349
|
+
const primaryLocal = firstEnabled(answers.runtimes);
|
|
11350
|
+
const localOnly = answers.localOnly === true && primaryLocal !== undefined;
|
|
11351
|
+
const mainModel = localOnly && primaryLocal !== undefined ? `${primaryLocal.id}/${primaryLocal.defaultModelID}` : `${answers.frontier.providerID}/${answers.frontier.modelID}`;
|
|
11215
11352
|
const config = {
|
|
11216
11353
|
$schema: "https://opencode.ai/config.json",
|
|
11217
11354
|
plugin: [OPENTEAM_PLUGIN_SPEC],
|
|
11218
|
-
model:
|
|
11355
|
+
model: mainModel
|
|
11219
11356
|
};
|
|
11220
11357
|
if (Object.keys(provider).length > 0) {
|
|
11221
11358
|
config.provider = provider;
|
|
11222
11359
|
}
|
|
11223
|
-
const primaryLocal = firstEnabled(answers.runtimes);
|
|
11224
11360
|
if (primaryLocal !== undefined) {
|
|
11225
11361
|
config.small_model = `${primaryLocal.id}/${primaryLocal.defaultModelID}`;
|
|
11226
11362
|
}
|
|
@@ -11281,6 +11417,13 @@ async function browseFrontier(prompt, profiles) {
|
|
|
11281
11417
|
const modelID = await prompt.select(opts);
|
|
11282
11418
|
return { providerID, modelID };
|
|
11283
11419
|
}
|
|
11420
|
+
function cheapestFrontier(profiles) {
|
|
11421
|
+
const first = sortFrontierByCost(profiles)[0];
|
|
11422
|
+
if (first === undefined) {
|
|
11423
|
+
throw new Error("no frontier profile available for the baseline");
|
|
11424
|
+
}
|
|
11425
|
+
return { providerID: first.ref.providerID, modelID: first.ref.modelID };
|
|
11426
|
+
}
|
|
11284
11427
|
async function chooseFrontier(prompt, profiles) {
|
|
11285
11428
|
const sorted = sortFrontierByCost(profiles);
|
|
11286
11429
|
const first = sorted[0];
|
|
@@ -11467,6 +11610,10 @@ async function runSetup(deps) {
|
|
|
11467
11610
|
runtimes.push(await resolveEnabledRuntime(deps, meta, info));
|
|
11468
11611
|
}
|
|
11469
11612
|
const hasLocalEnabled = enabled.size > 0;
|
|
11613
|
+
const localOnly = hasLocalEnabled ? await prompt.confirm({
|
|
11614
|
+
message: "Run without frontier models? (local runtimes only; openteam never escalates)",
|
|
11615
|
+
initial: false
|
|
11616
|
+
}) : false;
|
|
11470
11617
|
let frontierProfiles;
|
|
11471
11618
|
try {
|
|
11472
11619
|
const loaded = await deps.loadFrontierProfiles();
|
|
@@ -11474,7 +11621,18 @@ async function runSetup(deps) {
|
|
|
11474
11621
|
} catch {
|
|
11475
11622
|
frontierProfiles = [...CURATED_FRONTIER_PROFILES];
|
|
11476
11623
|
}
|
|
11477
|
-
const frontier = await chooseFrontier(prompt, frontierProfiles);
|
|
11624
|
+
const frontier = localOnly ? cheapestFrontier(frontierProfiles) : await chooseFrontier(prompt, frontierProfiles);
|
|
11625
|
+
if (localOnly) {
|
|
11626
|
+
prompt.note([
|
|
11627
|
+
"openteam will never escalate to a frontier model.",
|
|
11628
|
+
"opencode's main model is pointed at your local runtime too, so the",
|
|
11629
|
+
"session itself does not reach a frontier provider either.",
|
|
11630
|
+
"",
|
|
11631
|
+
`A baseline is still recorded (${frontier.providerID}/${frontier.modelID})`,
|
|
11632
|
+
"but stays unused. Set router.localOnly to false to re-enable it."
|
|
11633
|
+
].join(`
|
|
11634
|
+
`), "Local-only mode");
|
|
11635
|
+
}
|
|
11478
11636
|
const routerMode = await prompt.select({
|
|
11479
11637
|
message: "Routing mode",
|
|
11480
11638
|
choices: [
|
|
@@ -11519,7 +11677,8 @@ async function runSetup(deps) {
|
|
|
11519
11677
|
frontier,
|
|
11520
11678
|
routerMode,
|
|
11521
11679
|
privacyMode,
|
|
11522
|
-
yolo
|
|
11680
|
+
yolo,
|
|
11681
|
+
localOnly
|
|
11523
11682
|
};
|
|
11524
11683
|
const openTeamConfig = buildOpenTeamConfig(answers);
|
|
11525
11684
|
const opencodeConfig = buildOpencodeConfig(answers);
|
|
@@ -11587,14 +11746,14 @@ async function runSetup(deps) {
|
|
|
11587
11746
|
return `${runtime.id} (${runtime.defaultModelID})${remote}`;
|
|
11588
11747
|
}).join(", ");
|
|
11589
11748
|
prompt.note([
|
|
11590
|
-
`Baseline frontier: ${frontier.providerID}/${frontier.modelID}`,
|
|
11749
|
+
`Baseline frontier: ${localOnly ? `${frontier.providerID}/${frontier.modelID} (recorded but unused — local-only)` : `${frontier.providerID}/${frontier.modelID}`}`,
|
|
11591
11750
|
`Local runtimes: ${enabledSummary || "none (frontier-only)"}`,
|
|
11592
11751
|
`YOLO mode: ${yolo ? "enabled (auto-approves permissions)" : "disabled"}`,
|
|
11593
11752
|
"Web Console: launched separately from the CLI with 'openteam console' (multi-session, loopback)",
|
|
11594
11753
|
`Wrote: ${opencodePath}, ${DEFAULT_CONFIG_PATH}, ${ORCHESTRATOR_AGENT_PATH}, ${AGENT_DIR}/*.md (${roleAgentFiles.length} standard subagents), ${OPENTEAM_COMMAND_DIR}/*.md (${slashCommands.length} commands); ensured ${OPENCODE_GITIGNORE_PATH}`,
|
|
11595
11754
|
"",
|
|
11596
11755
|
"Next steps:",
|
|
11597
|
-
` 1. Authenticate the frontier provider: opencode auth login`,
|
|
11756
|
+
localOnly ? " 1. No frontier auth needed; openteam runs entirely on your local runtimes" : ` 1. Authenticate the frontier provider: opencode auth login`,
|
|
11598
11757
|
hasLocalEnabled ? " 2. Make sure your local runtime (local or on the network) is reachable; openteam routes local-first automatically" : " 2. Open opencode in this repo; openteam will use frontier models only",
|
|
11599
11758
|
" 3. Press Tab and pick the 'openteam' agent, or type / and pick an /openteam… command"
|
|
11600
11759
|
].join(`
|
|
@@ -12281,10 +12440,12 @@ var HELP = [
|
|
|
12281
12440
|
" openteam baseline show Show the effective baseline",
|
|
12282
12441
|
" openteam baseline set <p/model> Pin the baseline (pinned mode)",
|
|
12283
12442
|
" openteam baseline auto Cheapest-capable baseline (auto mode)",
|
|
12284
|
-
|
|
12285
|
-
|
|
12286
|
-
|
|
12443
|
+
localMessages.help.status,
|
|
12444
|
+
localMessages.help.off,
|
|
12445
|
+
localMessages.help.only,
|
|
12446
|
+
localMessages.help.on,
|
|
12287
12447
|
" openteam doctor Diagnose runtimes and config",
|
|
12448
|
+
" openteam roles init Compose per-role routing policy for unprofiled roster roles",
|
|
12288
12449
|
" openteam migrate Relocate pre-P15b artifacts to their split homes (idempotent)",
|
|
12289
12450
|
" openteam agents List agents and each one's LLM (local/frontier)",
|
|
12290
12451
|
" openteam console Launch the multi-session web Console (Ctrl+C to stop; --open opens the browser)",
|
|
@@ -12453,23 +12614,40 @@ ${HELP}`
|
|
|
12453
12614
|
}
|
|
12454
12615
|
async function runLocal(positionals, deps, configPath, configResolution) {
|
|
12455
12616
|
const sub = positionals[1] ?? "status";
|
|
12456
|
-
if (sub !== "status" && sub !== "off" && sub !== "on") {
|
|
12617
|
+
if (sub !== "status" && sub !== "off" && sub !== "only" && sub !== "on") {
|
|
12457
12618
|
return {
|
|
12458
12619
|
exitCode: 1,
|
|
12459
|
-
stdout:
|
|
12460
|
-
|
|
12461
|
-
${HELP}`
|
|
12620
|
+
stdout: localMessages.unknownSubcommand(sub, HELP)
|
|
12462
12621
|
};
|
|
12463
12622
|
}
|
|
12464
12623
|
const config = await deps.loadConfig(configPath, configResolution);
|
|
12465
12624
|
if (sub === "status") {
|
|
12466
12625
|
return { exitCode: 0, stdout: showLocal(config).message };
|
|
12467
12626
|
}
|
|
12468
|
-
|
|
12627
|
+
let outcome;
|
|
12628
|
+
switch (sub) {
|
|
12629
|
+
case "off":
|
|
12630
|
+
outcome = setFrontierOnly(config);
|
|
12631
|
+
break;
|
|
12632
|
+
case "only":
|
|
12633
|
+
outcome = setLocalOnly(config);
|
|
12634
|
+
break;
|
|
12635
|
+
case "on":
|
|
12636
|
+
outcome = setLocalFirst(config);
|
|
12637
|
+
break;
|
|
12638
|
+
default:
|
|
12639
|
+
return {
|
|
12640
|
+
exitCode: 1,
|
|
12641
|
+
stdout: localMessages.unknownSubcommand(sub, HELP)
|
|
12642
|
+
};
|
|
12643
|
+
}
|
|
12469
12644
|
if (outcome.config !== undefined) {
|
|
12470
12645
|
await deps.saveConfig(outcome.config, configPath, configResolution);
|
|
12471
12646
|
}
|
|
12472
|
-
return {
|
|
12647
|
+
return {
|
|
12648
|
+
exitCode: sub === "only" && !hasEnabledLocalRuntime(config) ? 1 : 0,
|
|
12649
|
+
stdout: outcome.message
|
|
12650
|
+
};
|
|
12473
12651
|
}
|
|
12474
12652
|
async function runYolo(positionals, deps, opencodeConfigPaths) {
|
|
12475
12653
|
const sub = positionals[1] ?? "status";
|
|
@@ -13216,6 +13394,178 @@ ${HELP}`
|
|
|
13216
13394
|
}
|
|
13217
13395
|
}
|
|
13218
13396
|
|
|
13397
|
+
// src/commands/rolesInit.ts
|
|
13398
|
+
var ROLES_INIT_LOCAL_ONLY = "local-only";
|
|
13399
|
+
var ROLES_INIT_FRONTIER_OK = "frontier-ok";
|
|
13400
|
+
var ROLES_INIT_SKIP = "skip";
|
|
13401
|
+
function isJsonRecord2(value) {
|
|
13402
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13403
|
+
}
|
|
13404
|
+
function serialize(value) {
|
|
13405
|
+
return `${JSON.stringify(value, null, 2)}
|
|
13406
|
+
`;
|
|
13407
|
+
}
|
|
13408
|
+
async function readJsonFile(deps, path3) {
|
|
13409
|
+
const content = await deps.readFile(path3);
|
|
13410
|
+
if (content === undefined)
|
|
13411
|
+
return {};
|
|
13412
|
+
const raw = parseConfigFileJson(path3, content);
|
|
13413
|
+
return isJsonRecord2(raw) ? raw : {};
|
|
13414
|
+
}
|
|
13415
|
+
function existingRoles(config) {
|
|
13416
|
+
const orchestrator = config.orchestrator;
|
|
13417
|
+
if (!isJsonRecord2(orchestrator))
|
|
13418
|
+
return {};
|
|
13419
|
+
const roles = orchestrator.roles;
|
|
13420
|
+
return isJsonRecord2(roles) ? roles : {};
|
|
13421
|
+
}
|
|
13422
|
+
function withRole(config, roleID, profile) {
|
|
13423
|
+
const orchestrator = isJsonRecord2(config.orchestrator) ? config.orchestrator : {};
|
|
13424
|
+
const roles = isJsonRecord2(orchestrator.roles) ? orchestrator.roles : {};
|
|
13425
|
+
return {
|
|
13426
|
+
...config,
|
|
13427
|
+
orchestrator: {
|
|
13428
|
+
...orchestrator,
|
|
13429
|
+
roles: { ...roles, [roleID]: profile }
|
|
13430
|
+
}
|
|
13431
|
+
};
|
|
13432
|
+
}
|
|
13433
|
+
function localOnlyProfile(roleID) {
|
|
13434
|
+
const base = synthesiseFallbackProfile(roleID);
|
|
13435
|
+
return {
|
|
13436
|
+
...base,
|
|
13437
|
+
preferredFrontierModels: [],
|
|
13438
|
+
localFirst: true,
|
|
13439
|
+
requiresLocalRuntime: true
|
|
13440
|
+
};
|
|
13441
|
+
}
|
|
13442
|
+
function frontierEligibleProfile(roleID) {
|
|
13443
|
+
return { ...synthesiseFallbackProfile(roleID) };
|
|
13444
|
+
}
|
|
13445
|
+
function policyChoices(roleID, agentName) {
|
|
13446
|
+
return [
|
|
13447
|
+
{
|
|
13448
|
+
value: ROLES_INIT_LOCAL_ONLY,
|
|
13449
|
+
label: rolesInitMessages.localOnlyLabel(roleID, agentName),
|
|
13450
|
+
hint: rolesInitMessages.localOnlyHint
|
|
13451
|
+
},
|
|
13452
|
+
{
|
|
13453
|
+
value: ROLES_INIT_FRONTIER_OK,
|
|
13454
|
+
label: rolesInitMessages.frontierOkLabel,
|
|
13455
|
+
hint: rolesInitMessages.frontierOkHint
|
|
13456
|
+
},
|
|
13457
|
+
{
|
|
13458
|
+
value: ROLES_INIT_SKIP,
|
|
13459
|
+
label: rolesInitMessages.skipLabel,
|
|
13460
|
+
hint: rolesInitMessages.skipHint
|
|
13461
|
+
}
|
|
13462
|
+
];
|
|
13463
|
+
}
|
|
13464
|
+
async function runRolesInit(deps) {
|
|
13465
|
+
const lines = [rolesInitMessages.header];
|
|
13466
|
+
const rosterContent = await deps.readFile(OPENTEAM_ROSTER_PATH);
|
|
13467
|
+
if (rosterContent === undefined) {
|
|
13468
|
+
lines.push(rolesInitMessages.noRoster(OPENTEAM_ROSTER_PATH), rolesInitMessages.noRosterRemedy);
|
|
13469
|
+
return { stdout: lines.join(`
|
|
13470
|
+
`), exitCode: 1 };
|
|
13471
|
+
}
|
|
13472
|
+
const parsed = parseRosterResult(rosterContent);
|
|
13473
|
+
if (!parsed.ok) {
|
|
13474
|
+
lines.push(rolesInitMessages.unparseableRoster(OPENTEAM_ROSTER_PATH, parsed.error.message), rolesInitMessages.unparseableRosterRemedy);
|
|
13475
|
+
return { stdout: lines.join(`
|
|
13476
|
+
`), exitCode: 1 };
|
|
13477
|
+
}
|
|
13478
|
+
const baseConfig = await readJsonFile(deps, DEFAULT_CONFIG_PATH);
|
|
13479
|
+
const configuredRoles = existingRoles(baseConfig);
|
|
13480
|
+
const unprofiled = parsed.roster.entries.filter((entry) => getRoleProfile(entry.roleID, configuredRoles) === undefined && !isKnownNonWorkerRole(entry.roleID));
|
|
13481
|
+
if (unprofiled.length === 0) {
|
|
13482
|
+
lines.push(rolesInitMessages.allProfiled(parsed.roster.entries.length));
|
|
13483
|
+
return { stdout: lines.join(`
|
|
13484
|
+
`), exitCode: 0 };
|
|
13485
|
+
}
|
|
13486
|
+
deps.prompt.intro(rolesInitMessages.title);
|
|
13487
|
+
deps.prompt.note(rolesInitMessages.why(unprofiled.length), rolesInitMessages.whyTitle);
|
|
13488
|
+
const runtimes = (await deps.detectRuntimes?.())?.filter((runtime) => runtime.available);
|
|
13489
|
+
let nextConfig = baseConfig;
|
|
13490
|
+
let nextOverlay;
|
|
13491
|
+
const pinned = [];
|
|
13492
|
+
const relaxed = [];
|
|
13493
|
+
const skipped = [];
|
|
13494
|
+
try {
|
|
13495
|
+
for (const entry of unprofiled) {
|
|
13496
|
+
const choice = await deps.prompt.select({
|
|
13497
|
+
message: rolesInitMessages.policyQuestion(entry.roleID, entry.agentName),
|
|
13498
|
+
choices: policyChoices(entry.roleID, entry.agentName),
|
|
13499
|
+
initial: ROLES_INIT_LOCAL_ONLY
|
|
13500
|
+
});
|
|
13501
|
+
if (choice === ROLES_INIT_SKIP) {
|
|
13502
|
+
skipped.push(entry.roleID);
|
|
13503
|
+
continue;
|
|
13504
|
+
}
|
|
13505
|
+
if (choice === ROLES_INIT_FRONTIER_OK) {
|
|
13506
|
+
nextConfig = withRole(nextConfig, entry.roleID, frontierEligibleProfile(entry.roleID));
|
|
13507
|
+
relaxed.push(entry.roleID);
|
|
13508
|
+
continue;
|
|
13509
|
+
}
|
|
13510
|
+
nextConfig = withRole(nextConfig, entry.roleID, localOnlyProfile(entry.roleID));
|
|
13511
|
+
pinned.push(entry.roleID);
|
|
13512
|
+
if (runtimes !== undefined && runtimes.length > 0) {
|
|
13513
|
+
const selected = await deps.prompt.multiselect({
|
|
13514
|
+
message: rolesInitMessages.runtimeQuestion(entry.roleID),
|
|
13515
|
+
choices: runtimes.map((runtime) => ({
|
|
13516
|
+
value: runtime.id,
|
|
13517
|
+
label: runtime.id,
|
|
13518
|
+
hint: rolesInitMessages.runtimeHint
|
|
13519
|
+
}))
|
|
13520
|
+
});
|
|
13521
|
+
if (selected.length > 0) {
|
|
13522
|
+
nextOverlay = withRole(nextOverlay ?? {}, entry.roleID, {
|
|
13523
|
+
localRuntimes: selected
|
|
13524
|
+
});
|
|
13525
|
+
}
|
|
13526
|
+
}
|
|
13527
|
+
}
|
|
13528
|
+
} catch (error) {
|
|
13529
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
13530
|
+
lines.push(rolesInitMessages.cancelled(message));
|
|
13531
|
+
return { stdout: lines.join(`
|
|
13532
|
+
`), exitCode: 1 };
|
|
13533
|
+
}
|
|
13534
|
+
if (pinned.length === 0 && relaxed.length === 0) {
|
|
13535
|
+
lines.push(rolesInitMessages.nothingWritten(skipped.join(", ")));
|
|
13536
|
+
deps.prompt.outro(rolesInitMessages.nothingToWriteOutro);
|
|
13537
|
+
return { stdout: lines.join(`
|
|
13538
|
+
`), exitCode: 0 };
|
|
13539
|
+
}
|
|
13540
|
+
const merged = mergeConfigInputs(nextConfig, nextOverlay);
|
|
13541
|
+
try {
|
|
13542
|
+
loadOpenTeamConfig(merged);
|
|
13543
|
+
} catch (error) {
|
|
13544
|
+
lines.push(rolesInitMessages.invalidResult(error instanceof Error ? error.message : String(error)));
|
|
13545
|
+
return { stdout: lines.join(`
|
|
13546
|
+
`), exitCode: 1 };
|
|
13547
|
+
}
|
|
13548
|
+
await deps.writeFile(DEFAULT_CONFIG_PATH, serialize(nextConfig));
|
|
13549
|
+
if (nextOverlay !== undefined) {
|
|
13550
|
+
await deps.writeFile(DEFAULT_LOCAL_OVERLAY_PATH, serialize(nextOverlay));
|
|
13551
|
+
}
|
|
13552
|
+
if (pinned.length > 0) {
|
|
13553
|
+
lines.push(rolesInitMessages.pinnedLocal(pinned.join(", "), DEFAULT_CONFIG_PATH));
|
|
13554
|
+
}
|
|
13555
|
+
if (relaxed.length > 0) {
|
|
13556
|
+
lines.push(rolesInitMessages.frontierAllowed(relaxed.join(", "), DEFAULT_CONFIG_PATH));
|
|
13557
|
+
}
|
|
13558
|
+
if (nextOverlay !== undefined) {
|
|
13559
|
+
lines.push(rolesInitMessages.runtimeBinding(DEFAULT_LOCAL_OVERLAY_PATH));
|
|
13560
|
+
}
|
|
13561
|
+
if (skipped.length > 0) {
|
|
13562
|
+
lines.push(rolesInitMessages.skippedSummary(skipped.join(", ")));
|
|
13563
|
+
}
|
|
13564
|
+
deps.prompt.outro(rolesInitMessages.outro);
|
|
13565
|
+
return { stdout: lines.join(`
|
|
13566
|
+
`), exitCode: 0 };
|
|
13567
|
+
}
|
|
13568
|
+
|
|
13219
13569
|
// src/config/legacyMigration.ts
|
|
13220
13570
|
async function relocateNode(from, to, port, files) {
|
|
13221
13571
|
if (!await port.exists(from)) {
|
|
@@ -14965,7 +15315,7 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
|
|
|
14965
15315
|
// package.json
|
|
14966
15316
|
var package_default = {
|
|
14967
15317
|
name: "@jmanuelcorral/openteam",
|
|
14968
|
-
version: "0.
|
|
15318
|
+
version: "0.11.0",
|
|
14969
15319
|
packageManager: "bun@1.3.14",
|
|
14970
15320
|
description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
|
|
14971
15321
|
license: "MIT",
|
|
@@ -15382,6 +15732,20 @@ function createSetupDeps() {
|
|
|
15382
15732
|
}
|
|
15383
15733
|
};
|
|
15384
15734
|
}
|
|
15735
|
+
function createRolesInitDeps() {
|
|
15736
|
+
const detect = createDetect(nodeExec2);
|
|
15737
|
+
return {
|
|
15738
|
+
prompt: clackPrompter,
|
|
15739
|
+
readFile: (path4) => storage.read(path4),
|
|
15740
|
+
writeFile: async (path4, contents) => {
|
|
15741
|
+
await storage.write(path4, contents);
|
|
15742
|
+
},
|
|
15743
|
+
detectRuntimes: async () => (await detect()).map((runtime) => ({
|
|
15744
|
+
id: runtime.id,
|
|
15745
|
+
available: runtime.reachable
|
|
15746
|
+
}))
|
|
15747
|
+
};
|
|
15748
|
+
}
|
|
15385
15749
|
async function main() {
|
|
15386
15750
|
const argv = process.argv.slice(2);
|
|
15387
15751
|
const configSelection = parseConfigSelection(argv);
|
|
@@ -15389,6 +15753,15 @@ async function main() {
|
|
|
15389
15753
|
const result2 = await runSetup(createSetupDeps());
|
|
15390
15754
|
if (result2.stdout.length > 0) {
|
|
15391
15755
|
process.stdout.write(`${result2.stdout}
|
|
15756
|
+
`);
|
|
15757
|
+
}
|
|
15758
|
+
process.exitCode = result2.exitCode;
|
|
15759
|
+
return;
|
|
15760
|
+
}
|
|
15761
|
+
if (argv[0] === "roles" && argv[1] === "init") {
|
|
15762
|
+
const result2 = await runRolesInit(createRolesInitDeps());
|
|
15763
|
+
if (result2.stdout.length > 0) {
|
|
15764
|
+
process.stdout.write(`${result2.stdout}
|
|
15392
15765
|
`);
|
|
15393
15766
|
}
|
|
15394
15767
|
process.exitCode = result2.exitCode;
|