teamai-cli 0.20.0-beta.7 → 0.20.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/teamai-recall.md +27 -4
- package/dist/index.js +61 -21
- package/package.json +1 -1
package/agents/teamai-recall.md
CHANGED
|
@@ -124,7 +124,29 @@ none exists, silently skip.
|
|
|
124
124
|
### Step 2 — Extract keywords from the task description
|
|
125
125
|
|
|
126
126
|
Pick 3–6 high-signal keywords from the user query. Strip filler words
|
|
127
|
-
("the", "how", "please").
|
|
127
|
+
("the", "how", "please").
|
|
128
|
+
|
|
129
|
+
**Always search bilingually (cross-language recall).** The knowledge base
|
|
130
|
+
mixes Chinese and English: a learning may be titled in English while your
|
|
131
|
+
task is described in Chinese, or vice versa. The search is purely lexical
|
|
132
|
+
(BM25 token matching), so a Chinese-only query never matches an
|
|
133
|
+
English-only document. To bridge this, expand every domain term into
|
|
134
|
+
**both languages** and include both in your keyword set:
|
|
135
|
+
|
|
136
|
+
- Chinese task → add the English term for each concept
|
|
137
|
+
(e.g. "重启推理集群" → also add `restart`, `inference`, `cluster`).
|
|
138
|
+
- English task → add the Chinese term for each concept
|
|
139
|
+
(e.g. "restart inference cluster" → also add `重启`, `推理`, `集群`).
|
|
140
|
+
- Only translate terms that genuinely differ across languages. Keep any
|
|
141
|
+
**language-neutral token** as-is — anything that is written the same way
|
|
142
|
+
in both languages, or has no meaningful translation: proper nouns, brand
|
|
143
|
+
and product names, acronyms and initialisms, code identifiers and symbols,
|
|
144
|
+
error codes, version strings, file paths, CLI commands, and API names
|
|
145
|
+
(e.g. `AppID`, `Kubernetes`, `GPU`, `RuntimeError`, `v2.1.3`, `oauth`). Translating
|
|
146
|
+
these only hurts matching.
|
|
147
|
+
|
|
148
|
+
Only translate the meaningful domain terms; do not pad the query with
|
|
149
|
+
low-signal words. Aim for the same 3–6 concepts, each in both languages.
|
|
128
150
|
|
|
129
151
|
**Lead with the terms that pin down *this* task**: proper nouns, customer or
|
|
130
152
|
product names, service IDs, error codes, versions, symbol names
|
|
@@ -208,9 +230,10 @@ Use judgement rather than counting, in both directions:
|
|
|
208
230
|
discriminating term as a lead to verify by opening the file, not as a
|
|
209
231
|
conclusion.
|
|
210
232
|
- **Fewer than 5 results does not mean the corpus is thin.** Results are
|
|
211
|
-
deduplicated when title, date, author and content all
|
|
212
|
-
shared twice appears once
|
|
213
|
-
only matches are also dropped. Both
|
|
233
|
+
deduplicated when type, domain, title, date, author, tags and content all
|
|
234
|
+
match, so a learning shared twice appears once — including when the two copies
|
|
235
|
+
have collected different vote counts. Body-only matches are also dropped. Both
|
|
236
|
+
are intentional.
|
|
214
237
|
|
|
215
238
|
For each hit you keep, read the source file directly (use `Read`) and
|
|
216
239
|
condense each into **one or two sentences**.
|
package/dist/index.js
CHANGED
|
@@ -1188,6 +1188,13 @@ teamai recall "<\u5173\u952E\u8BCD1> <\u5173\u952E\u8BCD2> ..."
|
|
|
1188
1188
|
|
|
1189
1189
|
\u4ECE\u4EFB\u52A1\u63CF\u8FF0\u4E2D\u63D0\u53D6 3-6 \u4E2A\u9AD8\u4FE1\u53F7\u5173\u952E\u8BCD\u8FDB\u884C\u68C0\u7D22\u3002
|
|
1190
1190
|
|
|
1191
|
+
**\u52A1\u5FC5\u4E2D\u82F1\u53CC\u8BED\u68C0\u7D22\uFF08\u8DE8\u8BED\u8A00\u53EC\u56DE\uFF09**\uFF1A\u77E5\u8BC6\u5E93\u4E2D\u82F1\u6587\u6DF7\u6742\uFF0C\u68C0\u7D22\u662F\u7EAF\u8BCD\u6CD5\u5339\u914D\uFF0C
|
|
1192
|
+
\u4E2D\u6587 query \u65E0\u6CD5\u547D\u4E2D\u7EAF\u82F1\u6587\u6587\u6863\uFF0C\u53CD\u4E4B\u4EA6\u7136\u3002\u56E0\u6B64\u6BCF\u4E2A\u9886\u57DF\u672F\u8BED\u90FD\u5E94**\u540C\u65F6\u7ED9\u51FA\u4E2D\u82F1\u4E24\u79CD\u5199\u6CD5**
|
|
1193
|
+
\u4E00\u8D77\u68C0\u7D22\uFF0C\u4F8B\u5982\u300C\u91CD\u542F\u63A8\u7406\u96C6\u7FA4\u300D\u5E94\u6269\u5C55\u4E3A \`\u91CD\u542F \u63A8\u7406 \u96C6\u7FA4 restart inference cluster\`\uFF1B
|
|
1194
|
+
\u53EA\u7FFB\u8BD1\u4E2D\u82F1\u786E\u5B9E\u4E0D\u540C\u7684\u672F\u8BED\uFF1B\u4EFB\u4F55**\u8BED\u8A00\u65E0\u5173\u7684 token**\uFF08\u4E2D\u82F1\u5199\u6CD5\u76F8\u540C\u3001\u6216\u65E0\u5B9E\u9645\u8BD1\u6CD5\u8005\uFF09\u4E00\u5F8B\u4FDD\u6301\u539F\u6837\uFF0C
|
|
1195
|
+
\u4F8B\u5982\u4E13\u6709\u540D\u8BCD\u3001\u54C1\u724C\u4E0E\u4EA7\u54C1\u540D\u3001\u7F29\u5199\u3001\u4EE3\u7801\u6807\u8BC6\u7B26\u4E0E\u7B26\u53F7\u3001\u9519\u8BEF\u7801\u3001\u7248\u672C\u53F7\u3001\u6587\u4EF6\u8DEF\u5F84\u3001\u547D\u4EE4\u4E0E API \u540D
|
|
1196
|
+
\uFF08\`AppID\`\u3001\`Kubernetes\`\u3001\`GPU\`\u3001\`RuntimeError\`\u3001\`v2.1.3\`\u3001\`oauth\`\uFF09\u2014\u2014\u7FFB\u8BD1\u5B83\u4EEC\u53EA\u4F1A\u635F\u5BB3\u5339\u914D\u3002
|
|
1197
|
+
|
|
1191
1198
|
### \u68C0\u7D22\u540E\u8981\u6C42\uFF08\u7528\u4E8E\u8BC4\u4F30\u77E5\u8BC6\u88AB\u91C7\u7EB3\u60C5\u51B5\uFF0C\u8BF7\u52A1\u5FC5\u6267\u884C\uFF09
|
|
1192
1199
|
|
|
1193
1200
|
teamai-recall subagent \u7684\u8FD4\u56DE\u91CC\u5DF2\u5217\u51FA\u672C\u6B21\u68C0\u7D22\u5230\u7684\u5019\u9009 doc-id\uFF08\u89C1\u5176 recalled-doc-ids \u5217\u8868\uFF09\u3002
|
|
@@ -4437,9 +4444,9 @@ async function scanTranscriptStop(transcriptPath) {
|
|
|
4437
4444
|
}
|
|
4438
4445
|
if (entry.type === "assistant") {
|
|
4439
4446
|
const usage = entry.message?.usage;
|
|
4440
|
-
const
|
|
4441
|
-
if (usage &&
|
|
4442
|
-
countedUsageKeys.add(
|
|
4447
|
+
const dedupKey2 = typeof entry.message?.id === "string" ? entry.message.id : typeof entry.requestId === "string" ? entry.requestId : void 0;
|
|
4448
|
+
if (usage && dedupKey2 && !countedUsageKeys.has(dedupKey2)) {
|
|
4449
|
+
countedUsageKeys.add(dedupKey2);
|
|
4443
4450
|
tokens.input += toNum(usage.input_tokens);
|
|
4444
4451
|
tokens.output += toNum(usage.output_tokens);
|
|
4445
4452
|
tokens.cacheRead += toNum(usage.cache_read_input_tokens);
|
|
@@ -7803,7 +7810,7 @@ var init_skills = __esm({
|
|
|
7803
7810
|
if (isAgentDisabled(localConfig, tool)) continue;
|
|
7804
7811
|
if (!toolPath.skills) continue;
|
|
7805
7812
|
let dest;
|
|
7806
|
-
if (tool === "openclaw"
|
|
7813
|
+
if (tool === "openclaw") {
|
|
7807
7814
|
const wsDir = await resolveOpenclawWorkspaceDir();
|
|
7808
7815
|
if (!wsDir) {
|
|
7809
7816
|
log.debug(`Skipping skill sync for openclaw: workspace dir not found`);
|
|
@@ -7852,7 +7859,7 @@ var init_skills = __esm({
|
|
|
7852
7859
|
for (const [tool, toolPath] of Object.entries(teamConfig.toolPaths)) {
|
|
7853
7860
|
if (!toolPath.skills) continue;
|
|
7854
7861
|
let skillDir;
|
|
7855
|
-
if (tool === "openclaw"
|
|
7862
|
+
if (tool === "openclaw") {
|
|
7856
7863
|
const wsDir = await resolveOpenclawWorkspaceDir();
|
|
7857
7864
|
if (!wsDir) continue;
|
|
7858
7865
|
skillDir = path22.join(wsDir, "skills", name);
|
|
@@ -9895,6 +9902,10 @@ async function pushCore(localConfig, teamConfig, options) {
|
|
|
9895
9902
|
try {
|
|
9896
9903
|
const repoPath = localConfig.repo.localPath;
|
|
9897
9904
|
const git = createGit2(repoPath);
|
|
9905
|
+
if (!await isDedicatedRepoRoot(repoPath)) {
|
|
9906
|
+
pullSpin.fail("Cannot push: team repo path is not a dedicated git root. Run `teamai init` to re-clone the team repo before pushing.");
|
|
9907
|
+
return;
|
|
9908
|
+
}
|
|
9898
9909
|
await resetToCleanMaster(git, repoPath);
|
|
9899
9910
|
await pullRepo(repoPath);
|
|
9900
9911
|
pullSpin.succeed("Up to date");
|
|
@@ -10231,6 +10242,7 @@ __export(git_exports, {
|
|
|
10231
10242
|
getRepoStatus: () => getRepoStatus,
|
|
10232
10243
|
hasCommits: () => hasCommits,
|
|
10233
10244
|
initRepo: () => initRepo,
|
|
10245
|
+
isDedicatedRepoRoot: () => isDedicatedRepoRoot,
|
|
10234
10246
|
isGitRepo: () => isGitRepo,
|
|
10235
10247
|
isMetadataOnlyDiff: () => isMetadataOnlyDiff,
|
|
10236
10248
|
pullRepo: () => pullRepo,
|
|
@@ -10239,6 +10251,7 @@ __export(git_exports, {
|
|
|
10239
10251
|
resetToCleanMaster: () => resetToCleanMaster
|
|
10240
10252
|
});
|
|
10241
10253
|
import fs12 from "fs";
|
|
10254
|
+
import { realpath } from "fs/promises";
|
|
10242
10255
|
import path33 from "path";
|
|
10243
10256
|
import fse8 from "fs-extra";
|
|
10244
10257
|
import simpleGit from "simple-git";
|
|
@@ -10468,6 +10481,21 @@ function generateBranchName(username) {
|
|
|
10468
10481
|
const timestamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
10469
10482
|
return `teamai/push/${username}/${timestamp}`;
|
|
10470
10483
|
}
|
|
10484
|
+
async function isDedicatedRepoRoot(repoPath) {
|
|
10485
|
+
const git = createGit2(repoPath);
|
|
10486
|
+
let toplevel;
|
|
10487
|
+
try {
|
|
10488
|
+
toplevel = (await git.revparse(["--show-toplevel"])).trim();
|
|
10489
|
+
} catch {
|
|
10490
|
+
return true;
|
|
10491
|
+
}
|
|
10492
|
+
try {
|
|
10493
|
+
const [realTop, realRepo] = await Promise.all([realpath(toplevel), realpath(repoPath)]);
|
|
10494
|
+
return realTop === realRepo;
|
|
10495
|
+
} catch {
|
|
10496
|
+
return false;
|
|
10497
|
+
}
|
|
10498
|
+
}
|
|
10471
10499
|
async function resetToCleanMaster(git, localPath) {
|
|
10472
10500
|
const status2 = await git.status();
|
|
10473
10501
|
const hasConflicts = status2.conflicted.length > 0;
|
|
@@ -12532,13 +12560,13 @@ function parseLearningDoc(content, filename) {
|
|
|
12532
12560
|
date: typeof data.date === "string" ? data.date : data.date instanceof Date ? data.date.toISOString().slice(0, 10) : void 0,
|
|
12533
12561
|
tags: Array.isArray(data.tags) ? data.tags.filter((t) => typeof t === "string") : typeof data.Tags === "string" ? data.Tags.split(/[,,]\s*/).map((t) => t.trim()).filter(Boolean) : void 0
|
|
12534
12562
|
};
|
|
12535
|
-
const bodyExcerpt = body
|
|
12563
|
+
const bodyExcerpt = body;
|
|
12536
12564
|
return { meta, bodyExcerpt };
|
|
12537
12565
|
} catch {
|
|
12538
12566
|
log.error(`Failed to parse frontmatter for ${filename}, using fallback`);
|
|
12539
12567
|
return {
|
|
12540
12568
|
meta: {},
|
|
12541
|
-
bodyExcerpt: content
|
|
12569
|
+
bodyExcerpt: content
|
|
12542
12570
|
};
|
|
12543
12571
|
}
|
|
12544
12572
|
}
|
|
@@ -12759,6 +12787,16 @@ async function loadIndex(indexPath) {
|
|
|
12759
12787
|
}
|
|
12760
12788
|
return raw;
|
|
12761
12789
|
}
|
|
12790
|
+
function dedupKey(r) {
|
|
12791
|
+
return JSON.stringify([
|
|
12792
|
+
r.entry.type,
|
|
12793
|
+
r.entry.domain,
|
|
12794
|
+
r.entry.title,
|
|
12795
|
+
r.entry.date,
|
|
12796
|
+
r.entry.author,
|
|
12797
|
+
[...r.entry.tokens].sort()
|
|
12798
|
+
]);
|
|
12799
|
+
}
|
|
12762
12800
|
function search(query, index, limit = 5) {
|
|
12763
12801
|
if (!query.trim()) return [];
|
|
12764
12802
|
const queryTokens = tokenize(query);
|
|
@@ -12794,8 +12832,8 @@ function search(query, index, limit = 5) {
|
|
|
12794
12832
|
score += 1 * idf(qt);
|
|
12795
12833
|
}
|
|
12796
12834
|
}
|
|
12797
|
-
const
|
|
12798
|
-
if (score > 0 && (hasTitleOrTagMatch ||
|
|
12835
|
+
const isDocsEntry = entry.type === "docs";
|
|
12836
|
+
if (score > 0 && (hasTitleOrTagMatch || isDocsEntry)) {
|
|
12799
12837
|
score /= lengthNorm;
|
|
12800
12838
|
score += Math.min(entry.votes * 0.5, 5);
|
|
12801
12839
|
const domainMultiplier = domainWeightRow[entry.domain ?? "neutral"];
|
|
@@ -12827,14 +12865,7 @@ function search(query, index, limit = 5) {
|
|
|
12827
12865
|
const seen = /* @__PURE__ */ new Set();
|
|
12828
12866
|
const deduped = [];
|
|
12829
12867
|
for (const r of results) {
|
|
12830
|
-
const key =
|
|
12831
|
-
r.entry.type,
|
|
12832
|
-
r.entry.title,
|
|
12833
|
-
r.entry.date,
|
|
12834
|
-
r.entry.author,
|
|
12835
|
-
r.entry.tokens.length,
|
|
12836
|
-
r.score
|
|
12837
|
-
].join(" ");
|
|
12868
|
+
const key = dedupKey(r);
|
|
12838
12869
|
if (seen.has(key)) continue;
|
|
12839
12870
|
seen.add(key);
|
|
12840
12871
|
deduped.push(r);
|
|
@@ -12842,7 +12873,7 @@ function search(query, index, limit = 5) {
|
|
|
12842
12873
|
}
|
|
12843
12874
|
return deduped;
|
|
12844
12875
|
}
|
|
12845
|
-
var
|
|
12876
|
+
var MAX_DOC_BYTES, TECHNICAL_TAGS, OPS_TAGS, SUPPORT_TAGS, TECHNICAL_PATH_PATTERNS, OPS_PATH_PATTERNS, SUPPORT_PATH_PATTERNS, DOMAIN_WEIGHT, TYPE_BONUS, CODEBASE_INDEX_FILENAME, CODEBASE_FULL_FILENAME, CODEBASE_INDEX_WEIGHT_BOOST;
|
|
12846
12877
|
var init_search_index = __esm({
|
|
12847
12878
|
"src/utils/search-index.ts"() {
|
|
12848
12879
|
"use strict";
|
|
@@ -12850,7 +12881,6 @@ var init_search_index = __esm({
|
|
|
12850
12881
|
init_tokenizer();
|
|
12851
12882
|
init_logger();
|
|
12852
12883
|
init_types();
|
|
12853
|
-
MAX_BODY_CHARS = 2e3;
|
|
12854
12884
|
MAX_DOC_BYTES = 50 * 1024;
|
|
12855
12885
|
TECHNICAL_TAGS = /* @__PURE__ */ new Set([
|
|
12856
12886
|
"api",
|
|
@@ -14364,6 +14394,10 @@ async function reportUsageToTeam(repoPath, username, options) {
|
|
|
14364
14394
|
writeRoot = await ensureReportsWorktree2(selfConfig);
|
|
14365
14395
|
} else {
|
|
14366
14396
|
const git = createGit2(repoPath);
|
|
14397
|
+
if (!await isDedicatedRepoRoot(repoPath)) {
|
|
14398
|
+
log.debug(`Skipping report: ${repoPath} is not a dedicated team-repo root (safety guard)`);
|
|
14399
|
+
return;
|
|
14400
|
+
}
|
|
14367
14401
|
await resetToCleanMaster(git, repoPath);
|
|
14368
14402
|
await pullRepo(repoPath);
|
|
14369
14403
|
}
|
|
@@ -15813,7 +15847,7 @@ async function pull(options) {
|
|
|
15813
15847
|
const { reportUsageToTeam: reportUsageToTeam2 } = await Promise.resolve().then(() => (init_team_push(), team_push_exports));
|
|
15814
15848
|
const { truncateUsageAfterReport: truncateUsageAfterReport2, readUsageEvents: readUsageEvents2 } = await Promise.resolve().then(() => (init_usage_tracker(), usage_tracker_exports));
|
|
15815
15849
|
const targets = [];
|
|
15816
|
-
if (projectConfig) {
|
|
15850
|
+
if (projectConfig && projectConfig.repo.kind !== "http") {
|
|
15817
15851
|
targets.push({
|
|
15818
15852
|
repoPath: projectConfig.repo.localPath,
|
|
15819
15853
|
username: projectConfig.username,
|
|
@@ -15829,7 +15863,13 @@ async function pull(options) {
|
|
|
15829
15863
|
targets.push({
|
|
15830
15864
|
repoPath: activeUserConfig.repo.localPath,
|
|
15831
15865
|
username: activeUserConfig.username,
|
|
15832
|
-
opts: {
|
|
15866
|
+
opts: {
|
|
15867
|
+
skipTruncate: true,
|
|
15868
|
+
excludeProjectRoots: projectConfig?.projectRoot ? [projectConfig.projectRoot] : [],
|
|
15869
|
+
// Self mode routes stats/votes to the teamai-reports orphan branch —
|
|
15870
|
+
// never reset/pull the business repo working tree.
|
|
15871
|
+
...activeUserConfig.repo.kind === "self" ? { selfConfig: activeUserConfig } : {}
|
|
15872
|
+
}
|
|
15833
15873
|
});
|
|
15834
15874
|
}
|
|
15835
15875
|
const eventCount = (await readUsageEvents2()).length;
|