teamai-cli 0.23.0-beta.7 → 0.23.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/index.js +350 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -195,7 +195,7 @@ Task: Fix duplicate project-level Hook injection
|
|
|
195
195
|
Consider running /teamai-share-learnings to summarize what you learned and share it with your team.
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
-
The hint names the non-zero friction signals that triggered it and, when available, includes a redacted, single-line summary of the first task. The `/teamai-share-learnings` skill summarizes the session and pushes a learning document directly to the team repo. Each session is prompted at most once.
|
|
198
|
+
The hint names the non-zero friction signals that triggered it and, when available, includes a redacted, single-line summary of the first task. The `/teamai-share-learnings` skill summarizes the session and pushes a learning document directly to the team repo. Each session is prompted at most once. Teams can switch the hint off with `sharing.contributeHint.enabled: false` in `teamai.yaml` (members: `contributeHintEnabled` in local config) while keeping the rest of the Stop hook.
|
|
199
199
|
|
|
200
200
|
### Team Knowledge Recall
|
|
201
201
|
|
package/README.zh-CN.md
CHANGED
|
@@ -195,7 +195,7 @@ Task: Fix duplicate project-level Hook injection
|
|
|
195
195
|
Consider running /teamai-share-learnings to summarize what you learned and share it with your team.
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
-
提示会列出实际触发它的非零摩擦信号;如果能取得首个任务摘要,还会在脱敏、单行化后附上任务上下文。`/teamai-share-learnings` skill 自动总结 session 经验并推送到团队仓库。每个 session
|
|
198
|
+
提示会列出实际触发它的非零摩擦信号;如果能取得首个任务摘要,还会在脱敏、单行化后附上任务上下文。`/teamai-share-learnings` skill 自动总结 session 经验并推送到团队仓库。每个 session 最多提示一次。团队可在 `teamai.yaml` 设置 `sharing.contributeHint.enabled: false` 关闭该提示(成员可用本地配置 `contributeHintEnabled` 覆盖),Stop hook 的其余功能不受影响。
|
|
199
199
|
|
|
200
200
|
### 团队知识检索
|
|
201
201
|
|
package/dist/index.js
CHANGED
|
@@ -250,6 +250,7 @@ __export(types_exports, {
|
|
|
250
250
|
getStatePath: () => getStatePath,
|
|
251
251
|
getTeamaiHome: () => getTeamaiHome,
|
|
252
252
|
isAgentDisabled: () => isAgentDisabled,
|
|
253
|
+
isContributeHintEnabled: () => isContributeHintEnabled,
|
|
253
254
|
isRecallEnabled: () => isRecallEnabled,
|
|
254
255
|
isSelfMode: () => isSelfMode,
|
|
255
256
|
legacyManagedMcpManifestPath: () => legacyManagedMcpManifestPath,
|
|
@@ -288,6 +289,11 @@ function isRecallEnabled(localConfig, teamConfig) {
|
|
|
288
289
|
if (localConfig.recallEnabled !== void 0) return localConfig.recallEnabled;
|
|
289
290
|
return getRecallSharing(teamConfig).enabled;
|
|
290
291
|
}
|
|
292
|
+
function isContributeHintEnabled(localConfig, teamConfig, env = process.env) {
|
|
293
|
+
if (env.TEAMAI_CONTRIBUTE_HINT_DISABLED === "1") return false;
|
|
294
|
+
if (localConfig.contributeHintEnabled !== void 0) return localConfig.contributeHintEnabled;
|
|
295
|
+
return teamConfig.sharing?.contributeHint?.enabled ?? true;
|
|
296
|
+
}
|
|
291
297
|
function resolveCoAuthor(localConfig, teamConfig) {
|
|
292
298
|
if (localConfig.coAuthorEnabled !== void 0) return localConfig.coAuthorEnabled;
|
|
293
299
|
return teamConfig.sharing?.coAuthor?.enabled;
|
|
@@ -478,6 +484,16 @@ var init_types = __esm({
|
|
|
478
484
|
recall: z.object({
|
|
479
485
|
enabled: z.boolean().default(false)
|
|
480
486
|
}).optional(),
|
|
487
|
+
// Optional (not .default) so existing TeamaiConfig literals stay valid; use
|
|
488
|
+
// isContributeHintEnabled() for the resolved view.
|
|
489
|
+
contributeHint: z.object({
|
|
490
|
+
/** Team default: whether the Stop hook nudges members to run
|
|
491
|
+
* /teamai-share-learnings after a high-friction session. Teams that route
|
|
492
|
+
* knowledge sharing through their own review flow can turn the nudge off
|
|
493
|
+
* without disabling the rest of the Stop hook (update check, votes sync,
|
|
494
|
+
* dashboard reporting). */
|
|
495
|
+
enabled: z.boolean().default(true)
|
|
496
|
+
}).optional(),
|
|
481
497
|
// Optional (not .default) so existing TeamaiConfig literals stay valid, AND so
|
|
482
498
|
// "team has no opinion" (block absent) stays distinct from "team says off"
|
|
483
499
|
// (enabled: false). Only the former is a no-op; see resolveCoAuthor().
|
|
@@ -638,6 +654,8 @@ var init_types = __esm({
|
|
|
638
654
|
excludedSkills: z.array(z.string()).optional(),
|
|
639
655
|
/** User-level override for recall feature. When set, takes precedence over team config. */
|
|
640
656
|
recallEnabled: z.boolean().optional(),
|
|
657
|
+
/** User-level override for the share-learnings hint. When set, takes precedence over team config. */
|
|
658
|
+
contributeHintEnabled: z.boolean().optional(),
|
|
641
659
|
/** Per-machine override for the co-author trailer in AI-tool commits. When set,
|
|
642
660
|
* takes precedence over the team `sharing.coAuthor` default. Undefined means
|
|
643
661
|
* "defer to the team" (see resolveCoAuthor). */
|
|
@@ -880,15 +898,17 @@ async function readJson(filePath) {
|
|
|
880
898
|
async function writeJson(filePath, data) {
|
|
881
899
|
await writeFile(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
882
900
|
}
|
|
883
|
-
async function writeJsonAtomic(filePath, data) {
|
|
901
|
+
async function writeJsonAtomic(filePath, data, options) {
|
|
884
902
|
const expanded = expandHome(filePath);
|
|
885
903
|
await fse.ensureDir(path3.dirname(expanded));
|
|
886
904
|
const content = JSON.stringify(data, null, 2) + "\n";
|
|
887
|
-
let mode = 384;
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
905
|
+
let mode = options?.mode ?? 384;
|
|
906
|
+
if (options?.mode === void 0) {
|
|
907
|
+
try {
|
|
908
|
+
mode = (await fse.stat(expanded)).mode & 511;
|
|
909
|
+
} catch (error) {
|
|
910
|
+
if (error.code !== "ENOENT") throw error;
|
|
911
|
+
}
|
|
892
912
|
}
|
|
893
913
|
const tmp = `${expanded}.${process.pid}.${crypto.randomBytes(6).toString("hex")}.tmp`;
|
|
894
914
|
try {
|
|
@@ -8426,6 +8446,12 @@ import path26 from "path";
|
|
|
8426
8446
|
import { execFile as execFile2 } from "child_process";
|
|
8427
8447
|
import { promisify } from "util";
|
|
8428
8448
|
import fse4 from "fs-extra";
|
|
8449
|
+
function modelAgentKind(tool) {
|
|
8450
|
+
const normalized = normalizeAgentType(tool ?? "");
|
|
8451
|
+
if (normalized === "codebuddy" || normalized === "codebuddy-internal") return "codebuddy";
|
|
8452
|
+
if (normalized === "claude") return "claude";
|
|
8453
|
+
return void 0;
|
|
8454
|
+
}
|
|
8429
8455
|
function isUnimplementedCommand(command) {
|
|
8430
8456
|
const type = command.type ?? "";
|
|
8431
8457
|
if (IMPLEMENTED_HOOK_COMMAND_TYPES.has(type)) return false;
|
|
@@ -8443,6 +8469,9 @@ function getConfigPath2() {
|
|
|
8443
8469
|
function getManifestPath() {
|
|
8444
8470
|
return path26.join(getLocalAgentHome(), MANIFEST_FILE);
|
|
8445
8471
|
}
|
|
8472
|
+
function getModelManifestPath() {
|
|
8473
|
+
return path26.join(getLocalAgentHome(), MODEL_MANIFEST_FILE);
|
|
8474
|
+
}
|
|
8446
8475
|
function getErrorLogPath() {
|
|
8447
8476
|
return path26.join(getTeamaiHomePath(), REPORTER_ERROR_LOG);
|
|
8448
8477
|
}
|
|
@@ -9150,7 +9179,7 @@ function collectManifestSlugs(manifest) {
|
|
|
9150
9179
|
}
|
|
9151
9180
|
return { skills, rules };
|
|
9152
9181
|
}
|
|
9153
|
-
async function scanMcpFromManifest(scope, projectRoot) {
|
|
9182
|
+
async function scanMcpFromManifest(scope, tool, projectRoot) {
|
|
9154
9183
|
const { resolveDataHomeForScope: resolveDataHomeForScope2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
9155
9184
|
const dataHome = await resolveDataHomeForScope2(scope, projectRoot);
|
|
9156
9185
|
let manifest;
|
|
@@ -9160,18 +9189,65 @@ async function scanMcpFromManifest(scope, projectRoot) {
|
|
|
9160
9189
|
} else {
|
|
9161
9190
|
manifest = await readJson(managedMcpManifestPath(dataHome)) ?? {};
|
|
9162
9191
|
}
|
|
9192
|
+
const manifestKey = `${tool}${scope === "project" ? ":project" : ""}`;
|
|
9193
|
+
const records = manifest[manifestKey];
|
|
9194
|
+
if (!Array.isArray(records)) return [];
|
|
9163
9195
|
const seen = /* @__PURE__ */ new Set();
|
|
9164
9196
|
const results = [];
|
|
9165
|
-
for (const
|
|
9166
|
-
if (!
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
seen.add(rec.name);
|
|
9170
|
-
results.push({ slug: rec.name, source: "enterprise" });
|
|
9171
|
-
}
|
|
9197
|
+
for (const rec of records) {
|
|
9198
|
+
if (!rec.name || seen.has(rec.name)) continue;
|
|
9199
|
+
seen.add(rec.name);
|
|
9200
|
+
results.push({ slug: rec.name, source: "enterprise" });
|
|
9172
9201
|
}
|
|
9173
9202
|
return results.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
9174
9203
|
}
|
|
9204
|
+
async function scanModelsFromDisk(tool) {
|
|
9205
|
+
const manifest = await readJson(getModelManifestPath()) ?? {};
|
|
9206
|
+
const agentKind = modelAgentKind(tool);
|
|
9207
|
+
const providers = (agentKind && manifest.providersByAgent?.[agentKind]) ?? manifest.providers ?? {};
|
|
9208
|
+
if (agentKind === "codebuddy") {
|
|
9209
|
+
const doc = await readJson(
|
|
9210
|
+
path26.join(getUserHome(), ".codebuddy", "models.json")
|
|
9211
|
+
);
|
|
9212
|
+
const entries = Array.isArray(doc?.models) ? doc.models : [];
|
|
9213
|
+
const owned = manifest.codebuddy ?? {};
|
|
9214
|
+
const results = [];
|
|
9215
|
+
for (const entry of entries) {
|
|
9216
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
9217
|
+
const { id, vendor, name } = entry;
|
|
9218
|
+
if (typeof id !== "string" || !id) continue;
|
|
9219
|
+
if (typeof vendor !== "string" || !vendor) continue;
|
|
9220
|
+
if (owned[id] === void 0 || providers[id] !== vendor) continue;
|
|
9221
|
+
results.push({
|
|
9222
|
+
provider: vendor,
|
|
9223
|
+
model_id: id,
|
|
9224
|
+
...typeof name === "string" && name ? { name } : {},
|
|
9225
|
+
source: "enterprise"
|
|
9226
|
+
});
|
|
9227
|
+
}
|
|
9228
|
+
return results;
|
|
9229
|
+
}
|
|
9230
|
+
if (agentKind === "claude") {
|
|
9231
|
+
const settings = await readJson(
|
|
9232
|
+
path26.join(getUserHome(), ".claude", "settings.json")
|
|
9233
|
+
);
|
|
9234
|
+
const env = settings?.env;
|
|
9235
|
+
if (typeof env !== "object" || env === null || Array.isArray(env)) return [];
|
|
9236
|
+
const { ANTHROPIC_CUSTOM_MODEL_OPTION: modelId, ANTHROPIC_CUSTOM_MODEL_OPTION_NAME: name } = env;
|
|
9237
|
+
if (typeof modelId !== "string" || !modelId) return [];
|
|
9238
|
+
const managed = manifest.claudeEnv?.ANTHROPIC_CUSTOM_MODEL_OPTION;
|
|
9239
|
+
if (managed === void 0 || entryHash(modelId) !== managed) return [];
|
|
9240
|
+
const provider = providers[modelId];
|
|
9241
|
+
if (!provider) return [];
|
|
9242
|
+
return [{
|
|
9243
|
+
provider,
|
|
9244
|
+
model_id: modelId,
|
|
9245
|
+
...typeof name === "string" && name ? { name } : {},
|
|
9246
|
+
source: "enterprise"
|
|
9247
|
+
}];
|
|
9248
|
+
}
|
|
9249
|
+
return [];
|
|
9250
|
+
}
|
|
9175
9251
|
async function pruneDeadWorkspaceBindings(config) {
|
|
9176
9252
|
let changed = false;
|
|
9177
9253
|
for (const workspacePath of Object.keys(config.workspaceBindings)) {
|
|
@@ -9224,8 +9300,10 @@ async function buildReportPayload(config, context) {
|
|
|
9224
9300
|
const userLevel = { group_id: config.userGroupId };
|
|
9225
9301
|
if (userScope.skills.length > 0) userLevel.skills = userScope.skills;
|
|
9226
9302
|
if (userScope.rules.length > 0) userLevel.rules = userScope.rules;
|
|
9227
|
-
const userMcps = await scanMcpFromManifest("user");
|
|
9303
|
+
const userMcps = await scanMcpFromManifest("user", tool);
|
|
9228
9304
|
if (userMcps.length > 0) userLevel.mcps = userMcps;
|
|
9305
|
+
const userModels = await scanModelsFromDisk(tool);
|
|
9306
|
+
if (userModels.length > 0) userLevel.models = userModels;
|
|
9229
9307
|
const payload = {
|
|
9230
9308
|
agent_type: normalizeAgentType(tool),
|
|
9231
9309
|
agent_version: await getAgentVersion(tool),
|
|
@@ -9256,7 +9334,7 @@ async function buildReportPayload(config, context) {
|
|
|
9256
9334
|
};
|
|
9257
9335
|
if (wsScope.skills.length > 0) workspace.skills = wsScope.skills;
|
|
9258
9336
|
if (wsScope.rules.length > 0) workspace.rules = wsScope.rules;
|
|
9259
|
-
const wsMcps = await scanMcpFromManifest("project", wsPath);
|
|
9337
|
+
const wsMcps = await scanMcpFromManifest("project", tool, wsPath);
|
|
9260
9338
|
if (wsMcps.length > 0) workspace.mcps = wsMcps;
|
|
9261
9339
|
return workspace;
|
|
9262
9340
|
})
|
|
@@ -9640,6 +9718,226 @@ async function ackCommand(config, tag, command, status2, version2, error) {
|
|
|
9640
9718
|
})
|
|
9641
9719
|
});
|
|
9642
9720
|
}
|
|
9721
|
+
function requireModelString(value, field) {
|
|
9722
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
9723
|
+
throw new Error(`apply_model_config: ${field} must be a non-empty string`);
|
|
9724
|
+
}
|
|
9725
|
+
return value.trim();
|
|
9726
|
+
}
|
|
9727
|
+
function optionalPositiveInteger(value, field) {
|
|
9728
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
9729
|
+
const normalized = typeof value === "string" && /^\d+$/.test(value) ? Number(value) : value;
|
|
9730
|
+
if (!Number.isSafeInteger(normalized) || normalized < 0) {
|
|
9731
|
+
throw new Error(`apply_model_config: ${field} must be a positive integer`);
|
|
9732
|
+
}
|
|
9733
|
+
if (normalized === 0) return void 0;
|
|
9734
|
+
return normalized;
|
|
9735
|
+
}
|
|
9736
|
+
function parseDeliveredModels(raw) {
|
|
9737
|
+
if (!raw) throw new Error("apply_model_config: missing cmd");
|
|
9738
|
+
let parsed;
|
|
9739
|
+
try {
|
|
9740
|
+
parsed = JSON.parse(raw);
|
|
9741
|
+
} catch {
|
|
9742
|
+
throw new Error("apply_model_config: cmd must be valid JSON");
|
|
9743
|
+
}
|
|
9744
|
+
const fullSnapshot = typeof parsed === "object" && parsed !== null && "models" in parsed;
|
|
9745
|
+
const values = fullSnapshot ? parsed.models : [parsed];
|
|
9746
|
+
if (!Array.isArray(values)) {
|
|
9747
|
+
throw new Error("apply_model_config: models must be an array");
|
|
9748
|
+
}
|
|
9749
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9750
|
+
const models = values.map((value) => {
|
|
9751
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
9752
|
+
throw new Error("apply_model_config: each model must be an object");
|
|
9753
|
+
}
|
|
9754
|
+
const input = value;
|
|
9755
|
+
const model = {
|
|
9756
|
+
provider: requireModelString(input.provider, "provider"),
|
|
9757
|
+
model_id: requireModelString(input.model_id, "model_id"),
|
|
9758
|
+
name: requireModelString(input.name, "name"),
|
|
9759
|
+
base_url: requireModelString(input.base_url, "base_url"),
|
|
9760
|
+
api_key: requireModelString(input.api_key, "api_key"),
|
|
9761
|
+
max_tokens: optionalPositiveInteger(input.max_tokens, "max_tokens") ?? DEFAULT_MAX_TOKENS,
|
|
9762
|
+
context_window: optionalPositiveInteger(input.context_window, "context_window")
|
|
9763
|
+
};
|
|
9764
|
+
let parsedUrl;
|
|
9765
|
+
try {
|
|
9766
|
+
parsedUrl = new URL(model.base_url);
|
|
9767
|
+
} catch {
|
|
9768
|
+
throw new Error("apply_model_config: base_url must be a valid URL");
|
|
9769
|
+
}
|
|
9770
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
9771
|
+
throw new Error("apply_model_config: base_url must use http or https");
|
|
9772
|
+
}
|
|
9773
|
+
if (seen.has(model.model_id)) {
|
|
9774
|
+
throw new Error(`apply_model_config: duplicate model_id "${model.model_id}"`);
|
|
9775
|
+
}
|
|
9776
|
+
seen.add(model.model_id);
|
|
9777
|
+
return model;
|
|
9778
|
+
});
|
|
9779
|
+
return { models, fullSnapshot };
|
|
9780
|
+
}
|
|
9781
|
+
function codebuddyModelEntry(model) {
|
|
9782
|
+
const baseUrl = model.base_url.replace(/\/+$/, "");
|
|
9783
|
+
return {
|
|
9784
|
+
id: model.model_id,
|
|
9785
|
+
name: model.name,
|
|
9786
|
+
vendor: model.provider,
|
|
9787
|
+
apiKey: model.api_key,
|
|
9788
|
+
...model.context_window === void 0 ? {} : { maxInputTokens: model.context_window },
|
|
9789
|
+
...model.max_tokens === void 0 ? {} : { maxOutputTokens: model.max_tokens },
|
|
9790
|
+
url: baseUrl.endsWith("/chat/completions") ? baseUrl : `${baseUrl}/chat/completions`,
|
|
9791
|
+
supportsToolCall: true
|
|
9792
|
+
};
|
|
9793
|
+
}
|
|
9794
|
+
async function readJsonObject(filePath) {
|
|
9795
|
+
const source = await readFileSafe(filePath);
|
|
9796
|
+
if (source === null) return {};
|
|
9797
|
+
try {
|
|
9798
|
+
const parsed = JSON.parse(source);
|
|
9799
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
9800
|
+
throw new Error("root must be an object");
|
|
9801
|
+
}
|
|
9802
|
+
return parsed;
|
|
9803
|
+
} catch (error) {
|
|
9804
|
+
throw new Error(`apply_model_config: cannot parse ${filePath}: ${error.message}`);
|
|
9805
|
+
}
|
|
9806
|
+
}
|
|
9807
|
+
async function writeModelJson(filePath, data) {
|
|
9808
|
+
let targetPath = filePath;
|
|
9809
|
+
try {
|
|
9810
|
+
if ((await fs12.promises.lstat(filePath)).isSymbolicLink()) {
|
|
9811
|
+
targetPath = await fs12.promises.realpath(filePath);
|
|
9812
|
+
}
|
|
9813
|
+
} catch (error) {
|
|
9814
|
+
if (error.code !== "ENOENT") throw error;
|
|
9815
|
+
}
|
|
9816
|
+
await writeJsonAtomic(targetPath, data, { mode: 384 });
|
|
9817
|
+
}
|
|
9818
|
+
async function reconcileCodebuddyModels(models, fullSnapshot, manifest) {
|
|
9819
|
+
const targetFile = path26.join(getUserHome(), ".codebuddy", "models.json");
|
|
9820
|
+
const doc = await readJsonObject(targetFile);
|
|
9821
|
+
const existing = doc.models === void 0 ? [] : doc.models;
|
|
9822
|
+
if (!Array.isArray(existing)) {
|
|
9823
|
+
throw new Error(`apply_model_config: models must be an array in ${targetFile}`);
|
|
9824
|
+
}
|
|
9825
|
+
const previouslyManaged = manifest.codebuddy ?? {};
|
|
9826
|
+
const nextManaged = fullSnapshot ? {} : { ...previouslyManaged };
|
|
9827
|
+
const incomingIds = new Set(models.map((model) => model.model_id));
|
|
9828
|
+
const removedManaged = /* @__PURE__ */ new Set();
|
|
9829
|
+
const preserved = [];
|
|
9830
|
+
const occupiedIds = /* @__PURE__ */ new Set();
|
|
9831
|
+
for (const entry of existing) {
|
|
9832
|
+
const id = typeof entry === "object" && entry !== null && typeof entry.id === "string" ? entry.id : void 0;
|
|
9833
|
+
if (id && previouslyManaged[id] && entryHash(entry) === previouslyManaged[id]) {
|
|
9834
|
+
if (fullSnapshot || incomingIds.has(id)) {
|
|
9835
|
+
removedManaged.add(id);
|
|
9836
|
+
continue;
|
|
9837
|
+
}
|
|
9838
|
+
preserved.push(entry);
|
|
9839
|
+
occupiedIds.add(id);
|
|
9840
|
+
continue;
|
|
9841
|
+
}
|
|
9842
|
+
preserved.push(entry);
|
|
9843
|
+
if (id) occupiedIds.add(id);
|
|
9844
|
+
if (id && previouslyManaged[id]) delete nextManaged[id];
|
|
9845
|
+
}
|
|
9846
|
+
for (const model of models) {
|
|
9847
|
+
if (occupiedIds.has(model.model_id)) continue;
|
|
9848
|
+
const entry = codebuddyModelEntry(model);
|
|
9849
|
+
preserved.push(entry);
|
|
9850
|
+
nextManaged[model.model_id] = entryHash(entry);
|
|
9851
|
+
}
|
|
9852
|
+
doc.models = preserved;
|
|
9853
|
+
if (Array.isArray(doc.availableModels) && doc.availableModels.length > 0) {
|
|
9854
|
+
const available = doc.availableModels.filter(
|
|
9855
|
+
(id) => typeof id === "string" && !removedManaged.has(id)
|
|
9856
|
+
);
|
|
9857
|
+
for (const id of Object.keys(nextManaged)) {
|
|
9858
|
+
if (!available.includes(id)) available.push(id);
|
|
9859
|
+
}
|
|
9860
|
+
doc.availableModels = available;
|
|
9861
|
+
}
|
|
9862
|
+
await writeModelJson(targetFile, doc);
|
|
9863
|
+
manifest.codebuddy = nextManaged;
|
|
9864
|
+
}
|
|
9865
|
+
function claudeEnvForModel(model) {
|
|
9866
|
+
const baseUrl = model.base_url.replace(/\/+$/, "").replace(/\/v1$/, "");
|
|
9867
|
+
return {
|
|
9868
|
+
ANTHROPIC_BASE_URL: baseUrl,
|
|
9869
|
+
ANTHROPIC_AUTH_TOKEN: model.api_key,
|
|
9870
|
+
ANTHROPIC_CUSTOM_MODEL_OPTION: model.model_id,
|
|
9871
|
+
ANTHROPIC_CUSTOM_MODEL_OPTION_NAME: model.name
|
|
9872
|
+
};
|
|
9873
|
+
}
|
|
9874
|
+
async function reconcileClaudeModels(models, manifest) {
|
|
9875
|
+
const settingsPath = path26.join(getUserHome(), ".claude", "settings.json");
|
|
9876
|
+
const profilePath = path26.join(getUserHome(), ".claude", "teamai-models.json");
|
|
9877
|
+
const previousHashes = manifest.claudeEnv ?? {};
|
|
9878
|
+
const settings = await readJsonObject(settingsPath);
|
|
9879
|
+
const rawEnv = settings.env === void 0 ? {} : settings.env;
|
|
9880
|
+
if (typeof rawEnv !== "object" || rawEnv === null || Array.isArray(rawEnv)) {
|
|
9881
|
+
throw new Error(`apply_model_config: env must be an object in ${settingsPath}`);
|
|
9882
|
+
}
|
|
9883
|
+
const env = { ...rawEnv };
|
|
9884
|
+
if (models.length === 0) {
|
|
9885
|
+
const canRemoveGateway = Object.entries(previousHashes).every(
|
|
9886
|
+
([key, hash]) => entryHash(env[key]) === hash
|
|
9887
|
+
);
|
|
9888
|
+
if (canRemoveGateway && Object.keys(previousHashes).length > 0) {
|
|
9889
|
+
for (const key of Object.keys(previousHashes)) delete env[key];
|
|
9890
|
+
settings.env = env;
|
|
9891
|
+
await writeModelJson(settingsPath, settings);
|
|
9892
|
+
}
|
|
9893
|
+
await remove(profilePath);
|
|
9894
|
+
manifest.claudeEnv = {};
|
|
9895
|
+
return;
|
|
9896
|
+
}
|
|
9897
|
+
const desired = claudeEnvForModel(models[0]);
|
|
9898
|
+
await writeModelJson(profilePath, { env: desired });
|
|
9899
|
+
const conflictKeys = /* @__PURE__ */ new Set([
|
|
9900
|
+
...Object.keys(desired),
|
|
9901
|
+
"ANTHROPIC_API_KEY"
|
|
9902
|
+
]);
|
|
9903
|
+
const canManage = [...conflictKeys].every((key) => env[key] === void 0 || previousHashes[key] !== void 0 && entryHash(env[key]) === previousHashes[key]);
|
|
9904
|
+
if (!canManage) {
|
|
9905
|
+
manifest.claudeEnv = {};
|
|
9906
|
+
return;
|
|
9907
|
+
}
|
|
9908
|
+
for (const [key, hash] of Object.entries(previousHashes)) {
|
|
9909
|
+
if (entryHash(env[key]) === hash) delete env[key];
|
|
9910
|
+
}
|
|
9911
|
+
Object.assign(env, desired);
|
|
9912
|
+
settings.env = env;
|
|
9913
|
+
await writeModelJson(settingsPath, settings);
|
|
9914
|
+
manifest.claudeEnv = Object.fromEntries(
|
|
9915
|
+
Object.entries(desired).map(([key, value]) => [key, entryHash(value)])
|
|
9916
|
+
);
|
|
9917
|
+
}
|
|
9918
|
+
async function applyModelConfig(command, tool) {
|
|
9919
|
+
const { models, fullSnapshot } = parseDeliveredModels(command.cmd);
|
|
9920
|
+
const manifest = await readJson(getModelManifestPath()) ?? {};
|
|
9921
|
+
const agentKind = modelAgentKind(tool);
|
|
9922
|
+
if (!agentKind) {
|
|
9923
|
+
throw new Error(`apply_model_config: unsupported agent "${tool ?? ""}"`);
|
|
9924
|
+
}
|
|
9925
|
+
const previousProviders = manifest.providersByAgent?.[agentKind] ?? manifest.providers ?? {};
|
|
9926
|
+
const providers = {
|
|
9927
|
+
...fullSnapshot ? {} : previousProviders,
|
|
9928
|
+
...Object.fromEntries(models.map((model) => [model.model_id, model.provider]))
|
|
9929
|
+
};
|
|
9930
|
+
manifest.providersByAgent = {
|
|
9931
|
+
...manifest.providersByAgent,
|
|
9932
|
+
[agentKind]: providers
|
|
9933
|
+
};
|
|
9934
|
+
if (agentKind === "codebuddy") {
|
|
9935
|
+
await reconcileCodebuddyModels(models, fullSnapshot, manifest);
|
|
9936
|
+
} else {
|
|
9937
|
+
await reconcileClaudeModels(models, manifest);
|
|
9938
|
+
}
|
|
9939
|
+
await writeJsonAtomic(getModelManifestPath(), manifest);
|
|
9940
|
+
}
|
|
9643
9941
|
function parseTeamaiCmd(raw) {
|
|
9644
9942
|
const argv = [];
|
|
9645
9943
|
let current = "";
|
|
@@ -9966,6 +10264,10 @@ async function runMcpCommand(config, command, context) {
|
|
|
9966
10264
|
return command.version;
|
|
9967
10265
|
}
|
|
9968
10266
|
async function executeCommand(config, command, context) {
|
|
10267
|
+
if (command.type === "apply_model_config") {
|
|
10268
|
+
await applyModelConfig(command, context.tool);
|
|
10269
|
+
return;
|
|
10270
|
+
}
|
|
9969
10271
|
if (command.type === "uninstall_teamai") {
|
|
9970
10272
|
return runCmdCommand(command, context);
|
|
9971
10273
|
}
|
|
@@ -9995,18 +10297,20 @@ async function executeCommand(config, command, context) {
|
|
|
9995
10297
|
}
|
|
9996
10298
|
async function processCommands(config, commands, context) {
|
|
9997
10299
|
const tag = localAgentTag(context);
|
|
10300
|
+
let modelConfigApplied = false;
|
|
9998
10301
|
for (const command of commands) {
|
|
9999
|
-
if (isUnimplementedCommand(command)) {
|
|
10302
|
+
if (isUnimplementedCommand(command) || command.type !== "apply_model_config" && command.type !== "uninstall_teamai" && command.type !== "install_hook_rule" && command.type !== "uninstall_hook_rule" && command.type !== "install_mcp" && command.type !== "uninstall_mcp" && (!commandKind(command) || !commandAction(command))) {
|
|
10000
10303
|
log.debug(`${tag} skipping unimplemented command ${command.id} (${command.type})`);
|
|
10001
10304
|
continue;
|
|
10002
10305
|
}
|
|
10003
10306
|
try {
|
|
10004
10307
|
const version2 = await executeCommand(config, command, context);
|
|
10005
10308
|
await ackCommand(config, tag, command, "success", version2);
|
|
10309
|
+
if (command.type === "apply_model_config") modelConfigApplied = true;
|
|
10006
10310
|
log.debug(`${tag} command ${command.id} (${command.type ?? ""}) succeeded`);
|
|
10007
10311
|
if (command.type === "uninstall_teamai") {
|
|
10008
10312
|
log.debug(`${tag} uninstall_teamai completed \u2014 remaining commands skipped`);
|
|
10009
|
-
return;
|
|
10313
|
+
return modelConfigApplied;
|
|
10010
10314
|
}
|
|
10011
10315
|
} catch (e) {
|
|
10012
10316
|
const error = e.message;
|
|
@@ -10018,6 +10322,7 @@ async function processCommands(config, commands, context) {
|
|
|
10018
10322
|
}
|
|
10019
10323
|
}
|
|
10020
10324
|
}
|
|
10325
|
+
return modelConfigApplied;
|
|
10021
10326
|
}
|
|
10022
10327
|
async function reportAndSyncLocalAgent(context) {
|
|
10023
10328
|
const config = await loadLocalAgentConfig();
|
|
@@ -10067,13 +10372,22 @@ async function reportAndSyncLocalAgent(context) {
|
|
|
10067
10372
|
config,
|
|
10068
10373
|
tag,
|
|
10069
10374
|
"sync",
|
|
10070
|
-
{ method: "POST", body: JSON.stringify(syncPayload) }
|
|
10375
|
+
{ method: "POST", body: JSON.stringify(syncPayload) },
|
|
10376
|
+
{ redactResponseLog: true }
|
|
10071
10377
|
);
|
|
10072
10378
|
const cmds = syncResponse.cmds;
|
|
10073
10379
|
const commands = cmds && cmds.length > 0 ? cmds : syncResponse.commands ?? [];
|
|
10074
10380
|
if (commands.length > 0) {
|
|
10075
10381
|
log.debug(`${tag} sync returned ${commands.length} command(s): ${commands.map((c) => `${c.type}#${c.id}`).join(", ")}`);
|
|
10076
|
-
await processCommands(config, commands, context);
|
|
10382
|
+
const modelConfigApplied = await processCommands(config, commands, context);
|
|
10383
|
+
if (modelConfigApplied && !skipReport) {
|
|
10384
|
+
const reportPayload = await buildReportPayload(config, context);
|
|
10385
|
+
await localAgentFetch(config, tag, "report", {
|
|
10386
|
+
method: "POST",
|
|
10387
|
+
body: JSON.stringify(reportPayload)
|
|
10388
|
+
});
|
|
10389
|
+
log.debug(`${tag} model config report OK`);
|
|
10390
|
+
}
|
|
10077
10391
|
}
|
|
10078
10392
|
log.debug(`${tag} sync OK (${commands.length} command(s))`);
|
|
10079
10393
|
} catch (e) {
|
|
@@ -10248,7 +10562,7 @@ async function bindCurrentProject(options) {
|
|
|
10248
10562
|
log.info("\u672A\u7ED1\u5B9A\u9879\u76EE\u3002");
|
|
10249
10563
|
}
|
|
10250
10564
|
}
|
|
10251
|
-
var execFileAsync, LOCAL_AGENT_DIR, CONFIG_FILE, MANIFEST_FILE, REPORTER_ERROR_LOG, LOCAL_AGENT_FETCH_TIMEOUT_MS, LOCAL_AGENT_HOOK_FETCH_TIMEOUT_MS, activeFetchTimeoutMs, UNIMPLEMENTED_COMMAND_TYPES, IMPLEMENTED_HOOK_COMMAND_TYPES, DEFAULT_ROUTES, PLUGIN_PULL_INTERVAL_MS, PLUGIN_FAIL_BACKOFF_MS, ZIP_MAGIC, VALID_MCP_TRANSPORTS;
|
|
10565
|
+
var execFileAsync, LOCAL_AGENT_DIR, CONFIG_FILE, MANIFEST_FILE, MODEL_MANIFEST_FILE, REPORTER_ERROR_LOG, LOCAL_AGENT_FETCH_TIMEOUT_MS, LOCAL_AGENT_HOOK_FETCH_TIMEOUT_MS, activeFetchTimeoutMs, UNIMPLEMENTED_COMMAND_TYPES, IMPLEMENTED_HOOK_COMMAND_TYPES, DEFAULT_ROUTES, PLUGIN_PULL_INTERVAL_MS, PLUGIN_FAIL_BACKOFF_MS, ZIP_MAGIC, DEFAULT_MAX_TOKENS, VALID_MCP_TRANSPORTS;
|
|
10252
10566
|
var init_local_agent = __esm({
|
|
10253
10567
|
"src/local-agent.ts"() {
|
|
10254
10568
|
"use strict";
|
|
@@ -10279,6 +10593,7 @@ var init_local_agent = __esm({
|
|
|
10279
10593
|
LOCAL_AGENT_DIR = "local-agent";
|
|
10280
10594
|
CONFIG_FILE = "config.json";
|
|
10281
10595
|
MANIFEST_FILE = "manifest.json";
|
|
10596
|
+
MODEL_MANIFEST_FILE = "model-manifest.json";
|
|
10282
10597
|
REPORTER_ERROR_LOG = "reporter/errors.jsonl";
|
|
10283
10598
|
LOCAL_AGENT_FETCH_TIMEOUT_MS = 15e3;
|
|
10284
10599
|
LOCAL_AGENT_HOOK_FETCH_TIMEOUT_MS = 3e3;
|
|
@@ -10295,6 +10610,7 @@ var init_local_agent = __esm({
|
|
|
10295
10610
|
PLUGIN_PULL_INTERVAL_MS = 12 * 60 * 60 * 1e3;
|
|
10296
10611
|
PLUGIN_FAIL_BACKOFF_MS = 60 * 60 * 1e3;
|
|
10297
10612
|
ZIP_MAGIC = Buffer.from([80, 75, 3, 4]);
|
|
10613
|
+
DEFAULT_MAX_TOKENS = 4096;
|
|
10298
10614
|
VALID_MCP_TRANSPORTS = /* @__PURE__ */ new Set(["stdio", "http", "sse"]);
|
|
10299
10615
|
}
|
|
10300
10616
|
});
|
|
@@ -27739,6 +28055,16 @@ var init_mr_hint = __esm({
|
|
|
27739
28055
|
|
|
27740
28056
|
// src/hook-handlers.ts
|
|
27741
28057
|
import path86 from "path";
|
|
28058
|
+
async function contributeHintAllowed() {
|
|
28059
|
+
const { isContributeHintEnabled: isContributeHintEnabled2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
28060
|
+
try {
|
|
28061
|
+
const { autoDetectInit: autoDetectInit2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
28062
|
+
const { localConfig, teamConfig } = await autoDetectInit2();
|
|
28063
|
+
return isContributeHintEnabled2(localConfig, teamConfig);
|
|
28064
|
+
} catch {
|
|
28065
|
+
return isContributeHintEnabled2({}, {});
|
|
28066
|
+
}
|
|
28067
|
+
}
|
|
27742
28068
|
function buildHandlerRegistry() {
|
|
27743
28069
|
return [
|
|
27744
28070
|
// ─── SessionStart ─────────────────────────────────
|
|
@@ -27885,6 +28211,7 @@ var init_hook_handlers = __esm({
|
|
|
27885
28211
|
contributeCheckHandler = {
|
|
27886
28212
|
name: "contribute-check",
|
|
27887
28213
|
async execute(stdin, tool) {
|
|
28214
|
+
if (!await contributeHintAllowed()) return null;
|
|
27888
28215
|
const { contributeCheckForSession: contributeCheckForSession2 } = await Promise.resolve().then(() => (init_contribute_check(), contribute_check_exports));
|
|
27889
28216
|
const { formatStopHookOutput: formatStopHookOutput2 } = await Promise.resolve().then(() => (init_hook_output(), hook_output_exports));
|
|
27890
28217
|
const { STOP_STDOUT_UNSUPPORTED_TOOLS: STOP_STDOUT_UNSUPPORTED_TOOLS2 } = await Promise.resolve().then(() => (init_tool_names(), tool_names_exports));
|
|
@@ -27904,7 +28231,8 @@ var init_hook_handlers = __esm({
|
|
|
27904
28231
|
if (!STOP_STDOUT_UNSUPPORTED_TOOLS2.has(tool)) return null;
|
|
27905
28232
|
const sessionId = deriveSessionId(stdin, { includeCwd: true });
|
|
27906
28233
|
const pending = await Promise.resolve().then(() => (init_contribute_check(), contribute_check_exports));
|
|
27907
|
-
const
|
|
28234
|
+
const stashed = await pending.takePendingHint(sessionId);
|
|
28235
|
+
const hint = await contributeHintAllowed() ? stashed : null;
|
|
27908
28236
|
const votesHint = await pending.takePendingVotesHint(sessionId);
|
|
27909
28237
|
const combined = [hint, votesHint].filter(Boolean).join("\n");
|
|
27910
28238
|
if (!combined) return null;
|