teamai-cli 0.23.0-beta.6 → 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 +518 -34
- 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). */
|
|
@@ -747,7 +765,18 @@ var init_types = __esm({
|
|
|
747
765
|
"instead",
|
|
748
766
|
"don't",
|
|
749
767
|
"that's not",
|
|
750
|
-
"not what"
|
|
768
|
+
"not what",
|
|
769
|
+
// Japanese: "that's wrong" / "not that" / "redo" / "you got it wrong" / "on your own" / "put it back".
|
|
770
|
+
"\u9055\u3046",
|
|
771
|
+
"\u3061\u304C\u3046",
|
|
772
|
+
"\u305D\u3046\u3058\u3083\u306A",
|
|
773
|
+
"\u305D\u3046\u3067\u306F\u306A",
|
|
774
|
+
"\u3084\u308A\u76F4",
|
|
775
|
+
"\u3084\u308A\u306A\u304A\u3057",
|
|
776
|
+
"\u9593\u9055\u3063\u3066",
|
|
777
|
+
"\u9593\u9055\u3048",
|
|
778
|
+
"\u52DD\u624B\u306B",
|
|
779
|
+
"\u623B\u3057\u3066"
|
|
751
780
|
];
|
|
752
781
|
INTERVENTION_SCAN_MAX_BYTES = 50 * 1024 * 1024;
|
|
753
782
|
TRANSCRIPT_INTERRUPT_PREFIX = "[Request interrupted by user";
|
|
@@ -869,15 +898,17 @@ async function readJson(filePath) {
|
|
|
869
898
|
async function writeJson(filePath, data) {
|
|
870
899
|
await writeFile(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
871
900
|
}
|
|
872
|
-
async function writeJsonAtomic(filePath, data) {
|
|
901
|
+
async function writeJsonAtomic(filePath, data, options) {
|
|
873
902
|
const expanded = expandHome(filePath);
|
|
874
903
|
await fse.ensureDir(path3.dirname(expanded));
|
|
875
904
|
const content = JSON.stringify(data, null, 2) + "\n";
|
|
876
|
-
let mode = 384;
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
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
|
+
}
|
|
881
912
|
}
|
|
882
913
|
const tmp = `${expanded}.${process.pid}.${crypto.randomBytes(6).toString("hex")}.tmp`;
|
|
883
914
|
try {
|
|
@@ -6562,22 +6593,32 @@ async function readLastAssistantOutput(transcriptPath) {
|
|
|
6562
6593
|
}
|
|
6563
6594
|
}
|
|
6564
6595
|
async function scanTranscriptStop(transcriptPath, opts) {
|
|
6596
|
+
if (path20.basename(transcriptPath) === "index.json") {
|
|
6597
|
+
const cb = await scanCodebuddyIndex(transcriptPath, opts?.frictionOnly ?? false);
|
|
6598
|
+
if (cb) return cb;
|
|
6599
|
+
}
|
|
6600
|
+
const initial = await scanJsonlTranscriptOnce(transcriptPath);
|
|
6601
|
+
if (opts?.frictionOnly || !isCodexTool(opts?.tool)) return initial.result;
|
|
6602
|
+
const flushedSnapshot = await waitForCodexUsageFlush(transcriptPath, initial.codexSnapshot);
|
|
6603
|
+
return flushedSnapshot ? { ...initial.result, tokens: flushedSnapshot.tokens, tokenScope: flushedSnapshot.scope } : initial.result;
|
|
6604
|
+
}
|
|
6605
|
+
async function scanJsonlTranscriptOnce(transcriptPath) {
|
|
6565
6606
|
let interrupt = 0;
|
|
6566
6607
|
let toolReject = 0;
|
|
6567
6608
|
let toolError = 0;
|
|
6568
6609
|
let prompts = 0;
|
|
6569
6610
|
const tokens = emptyTokenUsage();
|
|
6611
|
+
let codexSessionSnapshot = null;
|
|
6612
|
+
let codexTranscriptSnapshot = null;
|
|
6570
6613
|
const countedUsageKeys = /* @__PURE__ */ new Set();
|
|
6571
|
-
if (path20.basename(transcriptPath) === "index.json") {
|
|
6572
|
-
const cb = await scanCodebuddyIndex(transcriptPath, opts?.frictionOnly ?? false);
|
|
6573
|
-
if (cb) return cb;
|
|
6574
|
-
}
|
|
6575
6614
|
try {
|
|
6576
6615
|
const stat8 = await fs9.promises.stat(transcriptPath);
|
|
6577
|
-
if (stat8.size === 0)
|
|
6616
|
+
if (stat8.size === 0) {
|
|
6617
|
+
return { result: { interrupt, toolReject, toolError, tokens, prompts }, codexSnapshot: null };
|
|
6618
|
+
}
|
|
6578
6619
|
if (stat8.size > INTERVENTION_SCAN_MAX_BYTES) {
|
|
6579
6620
|
log.warn(`dashboard: transcript too large to scan (${stat8.size} bytes)`);
|
|
6580
|
-
return { interrupt, toolReject, toolError, tokens, prompts };
|
|
6621
|
+
return { result: { interrupt, toolReject, toolError, tokens, prompts }, codexSnapshot: null };
|
|
6581
6622
|
}
|
|
6582
6623
|
const rl = readline2.createInterface({
|
|
6583
6624
|
input: fs9.createReadStream(transcriptPath, { encoding: "utf-8" }),
|
|
@@ -6585,13 +6626,19 @@ async function scanTranscriptStop(transcriptPath, opts) {
|
|
|
6585
6626
|
});
|
|
6586
6627
|
for await (const line of rl) {
|
|
6587
6628
|
const trimmed = line.trim();
|
|
6588
|
-
if (!trimmed || !trimmed.includes('"user"') && !trimmed.includes('"assistant"')) continue;
|
|
6629
|
+
if (!trimmed || !trimmed.includes('"user"') && !trimmed.includes('"assistant"') && !trimmed.includes('"token_usage_record"') && !trimmed.includes('"token_count"')) continue;
|
|
6589
6630
|
let entry;
|
|
6590
6631
|
try {
|
|
6591
6632
|
entry = JSON.parse(trimmed);
|
|
6592
6633
|
} catch {
|
|
6593
6634
|
continue;
|
|
6594
6635
|
}
|
|
6636
|
+
const codexUsage = parseCodexCumulativeUsage(entry);
|
|
6637
|
+
if (codexUsage) {
|
|
6638
|
+
if (codexUsage.scope === "session") codexSessionSnapshot = codexUsage;
|
|
6639
|
+
else codexTranscriptSnapshot = codexUsage;
|
|
6640
|
+
continue;
|
|
6641
|
+
}
|
|
6595
6642
|
if (entry.type === "assistant") {
|
|
6596
6643
|
const usage = entry.message?.usage;
|
|
6597
6644
|
const dedupKey2 = typeof entry.message?.id === "string" ? entry.message.id : typeof entry.requestId === "string" ? entry.requestId : void 0;
|
|
@@ -6638,7 +6685,97 @@ async function scanTranscriptStop(transcriptPath, opts) {
|
|
|
6638
6685
|
} catch (e) {
|
|
6639
6686
|
log.warn(`dashboard: failed to scan transcript: ${e.message}`);
|
|
6640
6687
|
}
|
|
6641
|
-
|
|
6688
|
+
const codexSnapshot = codexSessionSnapshot ?? codexTranscriptSnapshot;
|
|
6689
|
+
const result = {
|
|
6690
|
+
interrupt,
|
|
6691
|
+
toolReject,
|
|
6692
|
+
toolError,
|
|
6693
|
+
tokens: codexSnapshot?.tokens ?? tokens,
|
|
6694
|
+
prompts,
|
|
6695
|
+
...codexSnapshot ? { tokenScope: codexSnapshot.scope } : {}
|
|
6696
|
+
};
|
|
6697
|
+
return { result, codexSnapshot };
|
|
6698
|
+
}
|
|
6699
|
+
function asRecord(value) {
|
|
6700
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
6701
|
+
}
|
|
6702
|
+
function codexUsageToTokenUsage(usage) {
|
|
6703
|
+
const inclusiveInput = toNum(usage.input_tokens);
|
|
6704
|
+
const cacheRead = toNum(usage.cached_input_tokens);
|
|
6705
|
+
const cacheCreation = toNum(usage.cache_write_input_tokens);
|
|
6706
|
+
return {
|
|
6707
|
+
input: Math.max(0, inclusiveInput - cacheRead - cacheCreation),
|
|
6708
|
+
output: toNum(usage.output_tokens),
|
|
6709
|
+
cacheRead,
|
|
6710
|
+
cacheCreation
|
|
6711
|
+
};
|
|
6712
|
+
}
|
|
6713
|
+
function parseCodexCumulativeUsage(entry) {
|
|
6714
|
+
const payload = asRecord(entry.payload);
|
|
6715
|
+
if (entry.type === "token_usage_record") {
|
|
6716
|
+
const usage = asRecord(payload?.thread_token_usage);
|
|
6717
|
+
return usage ? { tokens: codexUsageToTokenUsage(usage), scope: "session" } : null;
|
|
6718
|
+
}
|
|
6719
|
+
if (entry.type === "event_msg" && payload?.type === "token_count") {
|
|
6720
|
+
const usage = asRecord(asRecord(payload.info)?.total_token_usage);
|
|
6721
|
+
return usage ? { tokens: codexUsageToTokenUsage(usage), scope: "transcript" } : null;
|
|
6722
|
+
}
|
|
6723
|
+
return null;
|
|
6724
|
+
}
|
|
6725
|
+
function isCodexTool(tool) {
|
|
6726
|
+
return typeof tool === "string" && tool.toLowerCase().includes("codex");
|
|
6727
|
+
}
|
|
6728
|
+
function codexSnapshotEquals(a, b) {
|
|
6729
|
+
return a !== null && a.scope === b.scope && a.tokens.input === b.tokens.input && a.tokens.output === b.tokens.output && a.tokens.cacheRead === b.tokens.cacheRead && a.tokens.cacheCreation === b.tokens.cacheCreation;
|
|
6730
|
+
}
|
|
6731
|
+
function preferCodexSnapshot(current, observed) {
|
|
6732
|
+
if (current?.scope === "session" && observed.scope === "transcript") return current;
|
|
6733
|
+
return observed;
|
|
6734
|
+
}
|
|
6735
|
+
async function readLatestCodexUsageFromTail(transcriptPath) {
|
|
6736
|
+
try {
|
|
6737
|
+
const stat8 = await fs9.promises.stat(transcriptPath);
|
|
6738
|
+
if (stat8.size === 0) return null;
|
|
6739
|
+
const readSize = Math.min(stat8.size, CODEX_USAGE_TAIL_BYTES);
|
|
6740
|
+
const offset = stat8.size - readSize;
|
|
6741
|
+
const fh = await fs9.promises.open(transcriptPath, "r");
|
|
6742
|
+
try {
|
|
6743
|
+
const buffer = Buffer.alloc(readSize);
|
|
6744
|
+
await fh.read(buffer, 0, readSize, offset);
|
|
6745
|
+
const lines = buffer.toString("utf-8").split("\n");
|
|
6746
|
+
if (offset > 0) lines.shift();
|
|
6747
|
+
let latestSession = null;
|
|
6748
|
+
let latestTranscript = null;
|
|
6749
|
+
for (const line of lines) {
|
|
6750
|
+
if (!line.includes('"token_usage_record"') && !line.includes('"token_count"')) continue;
|
|
6751
|
+
try {
|
|
6752
|
+
const parsed = JSON.parse(line);
|
|
6753
|
+
const snapshot = parseCodexCumulativeUsage(parsed);
|
|
6754
|
+
if (snapshot?.scope === "session") latestSession = snapshot;
|
|
6755
|
+
else if (snapshot) latestTranscript = snapshot;
|
|
6756
|
+
} catch {
|
|
6757
|
+
}
|
|
6758
|
+
}
|
|
6759
|
+
return latestSession ?? latestTranscript;
|
|
6760
|
+
} finally {
|
|
6761
|
+
await fh.close();
|
|
6762
|
+
}
|
|
6763
|
+
} catch {
|
|
6764
|
+
return null;
|
|
6765
|
+
}
|
|
6766
|
+
}
|
|
6767
|
+
async function waitForCodexUsageFlush(transcriptPath, initial) {
|
|
6768
|
+
let latest = initial;
|
|
6769
|
+
for (let attempt = 1; attempt < CODEX_USAGE_MAX_ATTEMPTS; attempt++) {
|
|
6770
|
+
await new Promise((resolve) => setTimeout(resolve, CODEX_USAGE_RETRY_MS));
|
|
6771
|
+
const observed = await readLatestCodexUsageFromTail(transcriptPath);
|
|
6772
|
+
if (!observed) continue;
|
|
6773
|
+
latest = preferCodexSnapshot(latest, observed);
|
|
6774
|
+
if (!codexSnapshotEquals(initial, latest) && (initial !== null || totalTokenCount(latest.tokens) > 0)) {
|
|
6775
|
+
return latest;
|
|
6776
|
+
}
|
|
6777
|
+
}
|
|
6778
|
+
return latest;
|
|
6642
6779
|
}
|
|
6643
6780
|
async function readCodebuddyIndexOnce(transcriptPath) {
|
|
6644
6781
|
try {
|
|
@@ -6834,7 +6971,7 @@ async function parseHookEvent(raw, tool) {
|
|
|
6834
6971
|
if (output) {
|
|
6835
6972
|
event.stoppedOutput = output;
|
|
6836
6973
|
}
|
|
6837
|
-
const scan = await scanTranscriptStop(hookData.transcript_path);
|
|
6974
|
+
const scan = await scanTranscriptStop(hookData.transcript_path, { tool });
|
|
6838
6975
|
if (scan.interrupt > 0 || scan.toolReject > 0 || scan.toolError > 0) {
|
|
6839
6976
|
event.interventions = {
|
|
6840
6977
|
interrupt: scan.interrupt,
|
|
@@ -6844,6 +6981,7 @@ async function parseHookEvent(raw, tool) {
|
|
|
6844
6981
|
}
|
|
6845
6982
|
if (scan.tokens.input > 0 || scan.tokens.output > 0 || scan.tokens.cacheRead > 0 || scan.tokens.cacheCreation > 0) {
|
|
6846
6983
|
event.tokens = scan.tokens;
|
|
6984
|
+
if (scan.tokenScope) event.tokenScope = scan.tokenScope;
|
|
6847
6985
|
}
|
|
6848
6986
|
if (scan.prompts > 0) {
|
|
6849
6987
|
event.prompts = scan.prompts;
|
|
@@ -6984,11 +7122,23 @@ function rebuildSessions(events) {
|
|
|
6984
7122
|
});
|
|
6985
7123
|
return result;
|
|
6986
7124
|
}
|
|
7125
|
+
function setLatestTokenSnapshot(snapshots, key, event) {
|
|
7126
|
+
if (!event.tokens) return;
|
|
7127
|
+
const current = snapshots.get(key);
|
|
7128
|
+
const candidateTime = Date.parse(event.timestamp);
|
|
7129
|
+
const currentTime = current ? Date.parse(current.timestamp) : Number.NaN;
|
|
7130
|
+
if (!current || !Number.isFinite(candidateTime) || !Number.isFinite(currentTime) || candidateTime >= currentTime) {
|
|
7131
|
+
snapshots.set(key, { timestamp: event.timestamp, tokens: event.tokens });
|
|
7132
|
+
}
|
|
7133
|
+
}
|
|
6987
7134
|
function aggregateSessionMetrics(events) {
|
|
6988
7135
|
const map = /* @__PURE__ */ new Map();
|
|
6989
7136
|
const lastStopAt = /* @__PURE__ */ new Map();
|
|
6990
7137
|
const submitCount = /* @__PURE__ */ new Map();
|
|
6991
7138
|
const stopPrompts = /* @__PURE__ */ new Map();
|
|
7139
|
+
const unscopedTokens = /* @__PURE__ */ new Map();
|
|
7140
|
+
const sessionTokens = /* @__PURE__ */ new Map();
|
|
7141
|
+
const transcriptTokens = /* @__PURE__ */ new Map();
|
|
6992
7142
|
for (const event of events) {
|
|
6993
7143
|
let m = map.get(event.sessionId);
|
|
6994
7144
|
if (!m) {
|
|
@@ -7001,7 +7151,18 @@ function aggregateSessionMetrics(events) {
|
|
|
7001
7151
|
m.toolReject = event.interventions.toolReject;
|
|
7002
7152
|
}
|
|
7003
7153
|
if (event.tokens) {
|
|
7004
|
-
|
|
7154
|
+
if (event.tokenScope === "session") {
|
|
7155
|
+
setLatestTokenSnapshot(sessionTokens, event.sessionId, event);
|
|
7156
|
+
} else if (event.tokenScope === "transcript" && event.transcriptPath) {
|
|
7157
|
+
let segments = transcriptTokens.get(event.sessionId);
|
|
7158
|
+
if (!segments) {
|
|
7159
|
+
segments = /* @__PURE__ */ new Map();
|
|
7160
|
+
transcriptTokens.set(event.sessionId, segments);
|
|
7161
|
+
}
|
|
7162
|
+
setLatestTokenSnapshot(segments, event.transcriptPath, event);
|
|
7163
|
+
} else {
|
|
7164
|
+
setLatestTokenSnapshot(unscopedTokens, event.sessionId, event);
|
|
7165
|
+
}
|
|
7005
7166
|
}
|
|
7006
7167
|
if (typeof event.prompts === "number") {
|
|
7007
7168
|
stopPrompts.set(event.sessionId, event.prompts);
|
|
@@ -7020,6 +7181,18 @@ function aggregateSessionMetrics(events) {
|
|
|
7020
7181
|
}
|
|
7021
7182
|
}
|
|
7022
7183
|
for (const [sid, m] of map) {
|
|
7184
|
+
const sessionSnapshot = sessionTokens.get(sid);
|
|
7185
|
+
const segments = transcriptTokens.get(sid);
|
|
7186
|
+
if (sessionSnapshot) {
|
|
7187
|
+
m.tokens = { ...sessionSnapshot.tokens };
|
|
7188
|
+
} else if (segments && segments.size > 0) {
|
|
7189
|
+
let total = emptyTokenUsage();
|
|
7190
|
+
for (const segment of segments.values()) total = addTokenUsage(total, segment.tokens);
|
|
7191
|
+
m.tokens = total;
|
|
7192
|
+
} else {
|
|
7193
|
+
const unscoped = unscopedTokens.get(sid);
|
|
7194
|
+
if (unscoped) m.tokens = { ...unscoped.tokens };
|
|
7195
|
+
}
|
|
7023
7196
|
m.prompts = Math.max(submitCount.get(sid) ?? 0, stopPrompts.get(sid) ?? 0);
|
|
7024
7197
|
}
|
|
7025
7198
|
return map;
|
|
@@ -7062,7 +7235,7 @@ async function dashboardReport(toolArg) {
|
|
|
7062
7235
|
compactEvents().catch(() => {
|
|
7063
7236
|
});
|
|
7064
7237
|
}
|
|
7065
|
-
var TRANSCRIPT_TAIL_BYTES, STOPPED_OUTPUT_MAX_CHARS, CODEBUDDY_USAGE_MAX_ATTEMPTS, CODEBUDDY_USAGE_RETRY_MS, CODEBUDDY_BLOB_MAX_COUNT, CODEBUDDY_REJECT_MARKER;
|
|
7238
|
+
var TRANSCRIPT_TAIL_BYTES, STOPPED_OUTPUT_MAX_CHARS, CODEX_USAGE_TAIL_BYTES, CODEX_USAGE_MAX_ATTEMPTS, CODEX_USAGE_RETRY_MS, CODEBUDDY_USAGE_MAX_ATTEMPTS, CODEBUDDY_USAGE_RETRY_MS, CODEBUDDY_BLOB_MAX_COUNT, CODEBUDDY_REJECT_MARKER;
|
|
7066
7239
|
var init_dashboard_collector = __esm({
|
|
7067
7240
|
"src/dashboard-collector.ts"() {
|
|
7068
7241
|
"use strict";
|
|
@@ -7077,6 +7250,9 @@ var init_dashboard_collector = __esm({
|
|
|
7077
7250
|
init_home();
|
|
7078
7251
|
TRANSCRIPT_TAIL_BYTES = 10240;
|
|
7079
7252
|
STOPPED_OUTPUT_MAX_CHARS = 500;
|
|
7253
|
+
CODEX_USAGE_TAIL_BYTES = 256 * 1024;
|
|
7254
|
+
CODEX_USAGE_MAX_ATTEMPTS = 8;
|
|
7255
|
+
CODEX_USAGE_RETRY_MS = 250;
|
|
7080
7256
|
CODEBUDDY_USAGE_MAX_ATTEMPTS = 8;
|
|
7081
7257
|
CODEBUDDY_USAGE_RETRY_MS = 250;
|
|
7082
7258
|
CODEBUDDY_BLOB_MAX_COUNT = 2e3;
|
|
@@ -8270,6 +8446,12 @@ import path26 from "path";
|
|
|
8270
8446
|
import { execFile as execFile2 } from "child_process";
|
|
8271
8447
|
import { promisify } from "util";
|
|
8272
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
|
+
}
|
|
8273
8455
|
function isUnimplementedCommand(command) {
|
|
8274
8456
|
const type = command.type ?? "";
|
|
8275
8457
|
if (IMPLEMENTED_HOOK_COMMAND_TYPES.has(type)) return false;
|
|
@@ -8287,6 +8469,9 @@ function getConfigPath2() {
|
|
|
8287
8469
|
function getManifestPath() {
|
|
8288
8470
|
return path26.join(getLocalAgentHome(), MANIFEST_FILE);
|
|
8289
8471
|
}
|
|
8472
|
+
function getModelManifestPath() {
|
|
8473
|
+
return path26.join(getLocalAgentHome(), MODEL_MANIFEST_FILE);
|
|
8474
|
+
}
|
|
8290
8475
|
function getErrorLogPath() {
|
|
8291
8476
|
return path26.join(getTeamaiHomePath(), REPORTER_ERROR_LOG);
|
|
8292
8477
|
}
|
|
@@ -8994,7 +9179,7 @@ function collectManifestSlugs(manifest) {
|
|
|
8994
9179
|
}
|
|
8995
9180
|
return { skills, rules };
|
|
8996
9181
|
}
|
|
8997
|
-
async function scanMcpFromManifest(scope, projectRoot) {
|
|
9182
|
+
async function scanMcpFromManifest(scope, tool, projectRoot) {
|
|
8998
9183
|
const { resolveDataHomeForScope: resolveDataHomeForScope2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
8999
9184
|
const dataHome = await resolveDataHomeForScope2(scope, projectRoot);
|
|
9000
9185
|
let manifest;
|
|
@@ -9004,18 +9189,65 @@ async function scanMcpFromManifest(scope, projectRoot) {
|
|
|
9004
9189
|
} else {
|
|
9005
9190
|
manifest = await readJson(managedMcpManifestPath(dataHome)) ?? {};
|
|
9006
9191
|
}
|
|
9192
|
+
const manifestKey = `${tool}${scope === "project" ? ":project" : ""}`;
|
|
9193
|
+
const records = manifest[manifestKey];
|
|
9194
|
+
if (!Array.isArray(records)) return [];
|
|
9007
9195
|
const seen = /* @__PURE__ */ new Set();
|
|
9008
9196
|
const results = [];
|
|
9009
|
-
for (const
|
|
9010
|
-
if (!
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
seen.add(rec.name);
|
|
9014
|
-
results.push({ slug: rec.name, source: "enterprise" });
|
|
9015
|
-
}
|
|
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" });
|
|
9016
9201
|
}
|
|
9017
9202
|
return results.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
9018
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
|
+
}
|
|
9019
9251
|
async function pruneDeadWorkspaceBindings(config) {
|
|
9020
9252
|
let changed = false;
|
|
9021
9253
|
for (const workspacePath of Object.keys(config.workspaceBindings)) {
|
|
@@ -9068,8 +9300,10 @@ async function buildReportPayload(config, context) {
|
|
|
9068
9300
|
const userLevel = { group_id: config.userGroupId };
|
|
9069
9301
|
if (userScope.skills.length > 0) userLevel.skills = userScope.skills;
|
|
9070
9302
|
if (userScope.rules.length > 0) userLevel.rules = userScope.rules;
|
|
9071
|
-
const userMcps = await scanMcpFromManifest("user");
|
|
9303
|
+
const userMcps = await scanMcpFromManifest("user", tool);
|
|
9072
9304
|
if (userMcps.length > 0) userLevel.mcps = userMcps;
|
|
9305
|
+
const userModels = await scanModelsFromDisk(tool);
|
|
9306
|
+
if (userModels.length > 0) userLevel.models = userModels;
|
|
9073
9307
|
const payload = {
|
|
9074
9308
|
agent_type: normalizeAgentType(tool),
|
|
9075
9309
|
agent_version: await getAgentVersion(tool),
|
|
@@ -9100,7 +9334,7 @@ async function buildReportPayload(config, context) {
|
|
|
9100
9334
|
};
|
|
9101
9335
|
if (wsScope.skills.length > 0) workspace.skills = wsScope.skills;
|
|
9102
9336
|
if (wsScope.rules.length > 0) workspace.rules = wsScope.rules;
|
|
9103
|
-
const wsMcps = await scanMcpFromManifest("project", wsPath);
|
|
9337
|
+
const wsMcps = await scanMcpFromManifest("project", tool, wsPath);
|
|
9104
9338
|
if (wsMcps.length > 0) workspace.mcps = wsMcps;
|
|
9105
9339
|
return workspace;
|
|
9106
9340
|
})
|
|
@@ -9484,6 +9718,226 @@ async function ackCommand(config, tag, command, status2, version2, error) {
|
|
|
9484
9718
|
})
|
|
9485
9719
|
});
|
|
9486
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
|
+
}
|
|
9487
9941
|
function parseTeamaiCmd(raw) {
|
|
9488
9942
|
const argv = [];
|
|
9489
9943
|
let current = "";
|
|
@@ -9810,6 +10264,10 @@ async function runMcpCommand(config, command, context) {
|
|
|
9810
10264
|
return command.version;
|
|
9811
10265
|
}
|
|
9812
10266
|
async function executeCommand(config, command, context) {
|
|
10267
|
+
if (command.type === "apply_model_config") {
|
|
10268
|
+
await applyModelConfig(command, context.tool);
|
|
10269
|
+
return;
|
|
10270
|
+
}
|
|
9813
10271
|
if (command.type === "uninstall_teamai") {
|
|
9814
10272
|
return runCmdCommand(command, context);
|
|
9815
10273
|
}
|
|
@@ -9839,18 +10297,20 @@ async function executeCommand(config, command, context) {
|
|
|
9839
10297
|
}
|
|
9840
10298
|
async function processCommands(config, commands, context) {
|
|
9841
10299
|
const tag = localAgentTag(context);
|
|
10300
|
+
let modelConfigApplied = false;
|
|
9842
10301
|
for (const command of commands) {
|
|
9843
|
-
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))) {
|
|
9844
10303
|
log.debug(`${tag} skipping unimplemented command ${command.id} (${command.type})`);
|
|
9845
10304
|
continue;
|
|
9846
10305
|
}
|
|
9847
10306
|
try {
|
|
9848
10307
|
const version2 = await executeCommand(config, command, context);
|
|
9849
10308
|
await ackCommand(config, tag, command, "success", version2);
|
|
10309
|
+
if (command.type === "apply_model_config") modelConfigApplied = true;
|
|
9850
10310
|
log.debug(`${tag} command ${command.id} (${command.type ?? ""}) succeeded`);
|
|
9851
10311
|
if (command.type === "uninstall_teamai") {
|
|
9852
10312
|
log.debug(`${tag} uninstall_teamai completed \u2014 remaining commands skipped`);
|
|
9853
|
-
return;
|
|
10313
|
+
return modelConfigApplied;
|
|
9854
10314
|
}
|
|
9855
10315
|
} catch (e) {
|
|
9856
10316
|
const error = e.message;
|
|
@@ -9862,6 +10322,7 @@ async function processCommands(config, commands, context) {
|
|
|
9862
10322
|
}
|
|
9863
10323
|
}
|
|
9864
10324
|
}
|
|
10325
|
+
return modelConfigApplied;
|
|
9865
10326
|
}
|
|
9866
10327
|
async function reportAndSyncLocalAgent(context) {
|
|
9867
10328
|
const config = await loadLocalAgentConfig();
|
|
@@ -9911,13 +10372,22 @@ async function reportAndSyncLocalAgent(context) {
|
|
|
9911
10372
|
config,
|
|
9912
10373
|
tag,
|
|
9913
10374
|
"sync",
|
|
9914
|
-
{ method: "POST", body: JSON.stringify(syncPayload) }
|
|
10375
|
+
{ method: "POST", body: JSON.stringify(syncPayload) },
|
|
10376
|
+
{ redactResponseLog: true }
|
|
9915
10377
|
);
|
|
9916
10378
|
const cmds = syncResponse.cmds;
|
|
9917
10379
|
const commands = cmds && cmds.length > 0 ? cmds : syncResponse.commands ?? [];
|
|
9918
10380
|
if (commands.length > 0) {
|
|
9919
10381
|
log.debug(`${tag} sync returned ${commands.length} command(s): ${commands.map((c) => `${c.type}#${c.id}`).join(", ")}`);
|
|
9920
|
-
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
|
+
}
|
|
9921
10391
|
}
|
|
9922
10392
|
log.debug(`${tag} sync OK (${commands.length} command(s))`);
|
|
9923
10393
|
} catch (e) {
|
|
@@ -10092,7 +10562,7 @@ async function bindCurrentProject(options) {
|
|
|
10092
10562
|
log.info("\u672A\u7ED1\u5B9A\u9879\u76EE\u3002");
|
|
10093
10563
|
}
|
|
10094
10564
|
}
|
|
10095
|
-
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;
|
|
10096
10566
|
var init_local_agent = __esm({
|
|
10097
10567
|
"src/local-agent.ts"() {
|
|
10098
10568
|
"use strict";
|
|
@@ -10123,6 +10593,7 @@ var init_local_agent = __esm({
|
|
|
10123
10593
|
LOCAL_AGENT_DIR = "local-agent";
|
|
10124
10594
|
CONFIG_FILE = "config.json";
|
|
10125
10595
|
MANIFEST_FILE = "manifest.json";
|
|
10596
|
+
MODEL_MANIFEST_FILE = "model-manifest.json";
|
|
10126
10597
|
REPORTER_ERROR_LOG = "reporter/errors.jsonl";
|
|
10127
10598
|
LOCAL_AGENT_FETCH_TIMEOUT_MS = 15e3;
|
|
10128
10599
|
LOCAL_AGENT_HOOK_FETCH_TIMEOUT_MS = 3e3;
|
|
@@ -10139,6 +10610,7 @@ var init_local_agent = __esm({
|
|
|
10139
10610
|
PLUGIN_PULL_INTERVAL_MS = 12 * 60 * 60 * 1e3;
|
|
10140
10611
|
PLUGIN_FAIL_BACKOFF_MS = 60 * 60 * 1e3;
|
|
10141
10612
|
ZIP_MAGIC = Buffer.from([80, 75, 3, 4]);
|
|
10613
|
+
DEFAULT_MAX_TOKENS = 4096;
|
|
10142
10614
|
VALID_MCP_TRANSPORTS = /* @__PURE__ */ new Set(["stdio", "http", "sse"]);
|
|
10143
10615
|
}
|
|
10144
10616
|
});
|
|
@@ -27583,6 +28055,16 @@ var init_mr_hint = __esm({
|
|
|
27583
28055
|
|
|
27584
28056
|
// src/hook-handlers.ts
|
|
27585
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
|
+
}
|
|
27586
28068
|
function buildHandlerRegistry() {
|
|
27587
28069
|
return [
|
|
27588
28070
|
// ─── SessionStart ─────────────────────────────────
|
|
@@ -27729,6 +28211,7 @@ var init_hook_handlers = __esm({
|
|
|
27729
28211
|
contributeCheckHandler = {
|
|
27730
28212
|
name: "contribute-check",
|
|
27731
28213
|
async execute(stdin, tool) {
|
|
28214
|
+
if (!await contributeHintAllowed()) return null;
|
|
27732
28215
|
const { contributeCheckForSession: contributeCheckForSession2 } = await Promise.resolve().then(() => (init_contribute_check(), contribute_check_exports));
|
|
27733
28216
|
const { formatStopHookOutput: formatStopHookOutput2 } = await Promise.resolve().then(() => (init_hook_output(), hook_output_exports));
|
|
27734
28217
|
const { STOP_STDOUT_UNSUPPORTED_TOOLS: STOP_STDOUT_UNSUPPORTED_TOOLS2 } = await Promise.resolve().then(() => (init_tool_names(), tool_names_exports));
|
|
@@ -27748,7 +28231,8 @@ var init_hook_handlers = __esm({
|
|
|
27748
28231
|
if (!STOP_STDOUT_UNSUPPORTED_TOOLS2.has(tool)) return null;
|
|
27749
28232
|
const sessionId = deriveSessionId(stdin, { includeCwd: true });
|
|
27750
28233
|
const pending = await Promise.resolve().then(() => (init_contribute_check(), contribute_check_exports));
|
|
27751
|
-
const
|
|
28234
|
+
const stashed = await pending.takePendingHint(sessionId);
|
|
28235
|
+
const hint = await contributeHintAllowed() ? stashed : null;
|
|
27752
28236
|
const votesHint = await pending.takePendingVotesHint(sessionId);
|
|
27753
28237
|
const combined = [hint, votesHint].filter(Boolean).join("\n");
|
|
27754
28238
|
if (!combined) return null;
|