teamai-cli 0.20.0-beta.6 → 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/README.md +9 -0
- package/README.zh-CN.md +6 -0
- package/agents/teamai-recall.md +159 -10
- package/dist/index.js +562 -179
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -444,8 +444,8 @@ var init_types = __esm({
|
|
|
444
444
|
tcodex: { skills: ".tcodex/skills", rules: ".tcodex/rules", settings: ".tcodex/hooks.json", agents: ".tcodex/agents" },
|
|
445
445
|
cursor: { skills: ".cursor/skills", rules: ".cursor/rules", settings: ".cursor/hooks.json", agents: ".cursor/agents", mcp: ".cursor/mcp.json", mcpProject: ".cursor/mcp.json" },
|
|
446
446
|
codebuddy: { skills: ".codebuddy/skills", rules: ".codebuddy/rules", settings: ".codebuddy/settings.json", claudemd: ".codebuddy/CODEBUDDY.md", agents: ".codebuddy/agents", mcp: ".codebuddy/mcp.json", mcpProject: ".codebuddy/mcp.json" },
|
|
447
|
-
openclaw: { skills: ".openclaw/skills", rules: ".openclaw/rules" },
|
|
448
|
-
hermes: { skills: ".hermes/skills" },
|
|
447
|
+
openclaw: { skills: ".openclaw/skills", rules: ".openclaw/rules", claudemd: ".openclaw/workspace/AGENTS.md" },
|
|
448
|
+
hermes: { skills: ".hermes/skills", claudemd: "AGENTS.md" },
|
|
449
449
|
workbuddy: { skills: ".workbuddy/skills", rules: ".workbuddy/rules", settings: ".workbuddy/settings.json", claudemd: "AGENTS.md", mcp: ".workbuddy/mcp.json", mcpProject: ".workbuddy/mcp.json" }
|
|
450
450
|
})
|
|
451
451
|
});
|
|
@@ -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
|
|
@@ -1349,15 +1356,12 @@ function getTGitToken() {
|
|
|
1349
1356
|
"No TGit credentials found. Set the TGIT_TOKEN environment variable (a git.woa.com Personal Access Token) or run `gf auth login`."
|
|
1350
1357
|
);
|
|
1351
1358
|
}
|
|
1352
|
-
function
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
function tryGetTGitToken() {
|
|
1356
|
-
try {
|
|
1357
|
-
return getTGitToken();
|
|
1358
|
-
} catch {
|
|
1359
|
+
function tgitGitCloneUrl(httpsUrl) {
|
|
1360
|
+
const oauthToken = gfGetOAuthToken();
|
|
1361
|
+
if (!oauthToken) {
|
|
1359
1362
|
return null;
|
|
1360
1363
|
}
|
|
1364
|
+
return httpsUrl.replace(/^https:\/\//, `https://oauth2:${oauthToken}@`);
|
|
1361
1365
|
}
|
|
1362
1366
|
function tgitAuthHeaders(token, scheme) {
|
|
1363
1367
|
if (scheme === "bearer") {
|
|
@@ -1579,10 +1583,8 @@ function gitOutputSaysRepoMissing(output) {
|
|
|
1579
1583
|
return output.includes("not found") || output.includes("does not exist") || output.includes("Repository not found");
|
|
1580
1584
|
}
|
|
1581
1585
|
function gfRepoClone(repo, localPath) {
|
|
1582
|
-
const
|
|
1583
|
-
if (
|
|
1584
|
-
const user = tgitGitUser(creds.scheme);
|
|
1585
|
-
const cloneUrl = `https://${user}:${creds.token}@git.woa.com/${repo}.git`;
|
|
1586
|
+
const cloneUrl = tgitGitCloneUrl(`https://git.woa.com/${repo}.git`);
|
|
1587
|
+
if (cloneUrl) {
|
|
1586
1588
|
const result2 = spawnSync("git", ["clone", cloneUrl, localPath], {
|
|
1587
1589
|
encoding: "utf-8",
|
|
1588
1590
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -1593,7 +1595,7 @@ function gfRepoClone(repo, localPath) {
|
|
|
1593
1595
|
throw new RepoNotFoundError2(repo);
|
|
1594
1596
|
}
|
|
1595
1597
|
if (result2.status !== 0) {
|
|
1596
|
-
const sanitized = allOutput2.replace(/
|
|
1598
|
+
const sanitized = allOutput2.replace(/oauth2:[^@]+@/g, "oauth2:***@");
|
|
1597
1599
|
throw new Error(`git clone failed: ${sanitized.trim()}`);
|
|
1598
1600
|
}
|
|
1599
1601
|
return;
|
|
@@ -2856,9 +2858,191 @@ var init_builtin_skills = __esm({
|
|
|
2856
2858
|
}
|
|
2857
2859
|
});
|
|
2858
2860
|
|
|
2861
|
+
// src/openclaw-hooks.ts
|
|
2862
|
+
var openclaw_hooks_exports = {};
|
|
2863
|
+
__export(openclaw_hooks_exports, {
|
|
2864
|
+
OPENCLAW_HOOK_DIR: () => OPENCLAW_HOOK_DIR,
|
|
2865
|
+
applyOpenClawAgentHook: () => applyOpenClawAgentHook,
|
|
2866
|
+
injectOpenClawHooks: () => injectOpenClawHooks,
|
|
2867
|
+
removeOpenClawAgentHook: () => removeOpenClawAgentHook,
|
|
2868
|
+
removeOpenClawHooks: () => removeOpenClawHooks,
|
|
2869
|
+
resolveOpenClawHooksDir: () => resolveOpenClawHooksDir,
|
|
2870
|
+
resolveOpenclawWorkspaceDir: () => resolveOpenclawWorkspaceDir
|
|
2871
|
+
});
|
|
2872
|
+
import path10 from "path";
|
|
2873
|
+
function resolveOpenClawHooksDir(tool) {
|
|
2874
|
+
if (tool === "openclaw" && process.env.OPENCLAW_STATE_DIR) {
|
|
2875
|
+
return path10.join(process.env.OPENCLAW_STATE_DIR, "hooks");
|
|
2876
|
+
}
|
|
2877
|
+
const home = process.env.HOME ?? "";
|
|
2878
|
+
return path10.join(home, `.${tool}`, "hooks");
|
|
2879
|
+
}
|
|
2880
|
+
function buildHookMd(tool) {
|
|
2881
|
+
const events = Object.keys(EVENT_MAP);
|
|
2882
|
+
const metadata = JSON.stringify({ openclaw: { events } });
|
|
2883
|
+
return [
|
|
2884
|
+
"---",
|
|
2885
|
+
`name: ${TEAMAI_MARKER} status-report`,
|
|
2886
|
+
`metadata:`,
|
|
2887
|
+
` ${metadata}`,
|
|
2888
|
+
`handler: ./handler.ts`,
|
|
2889
|
+
"---",
|
|
2890
|
+
"",
|
|
2891
|
+
`${TEAMAI_MARKER} Reports agent status to the team backend (report/sync/ack) for tool \`${tool}\`.`,
|
|
2892
|
+
"Managed by teamai \u2014 do not edit by hand.",
|
|
2893
|
+
""
|
|
2894
|
+
].join("\n");
|
|
2895
|
+
}
|
|
2896
|
+
function buildHandlerTs(tool) {
|
|
2897
|
+
const mapLiteral = JSON.stringify(EVENT_MAP);
|
|
2898
|
+
return `// ${TEAMAI_MARKER} status-report handler \u2014 generated by teamai, do not edit.
|
|
2899
|
+
import { spawn } from 'node:child_process';
|
|
2900
|
+
|
|
2901
|
+
const EVENT_MAP: Record<string, string> = ${mapLiteral};
|
|
2902
|
+
const TOOL = ${JSON.stringify(tool)};
|
|
2903
|
+
|
|
2904
|
+
export default async function handler(ctx: { event?: string } = {}): Promise<void> {
|
|
2905
|
+
const dispatchEvent = ctx.event ? EVENT_MAP[ctx.event] : undefined;
|
|
2906
|
+
if (!dispatchEvent) return;
|
|
2907
|
+
try {
|
|
2908
|
+
const child = spawn('teamai', ['hook-dispatch', dispatchEvent, '--tool', TOOL], {
|
|
2909
|
+
stdio: ['inherit', 'ignore', 'ignore'],
|
|
2910
|
+
});
|
|
2911
|
+
child.on('error', () => {});
|
|
2912
|
+
} catch {
|
|
2913
|
+
// never block the agent
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
`;
|
|
2917
|
+
}
|
|
2918
|
+
async function injectOpenClawHooks(hooksDir, tool = "openclaw") {
|
|
2919
|
+
const effectiveHooksDir = resolveOpenClawHooksDir(tool);
|
|
2920
|
+
const dir = path10.join(effectiveHooksDir, OPENCLAW_HOOK_DIR);
|
|
2921
|
+
await ensureDir(dir);
|
|
2922
|
+
await writeFile(path10.join(dir, "HOOK.md"), buildHookMd(tool));
|
|
2923
|
+
await writeFile(path10.join(dir, "handler.ts"), buildHandlerTs(tool));
|
|
2924
|
+
log.success(`Injected teamai OpenClaw hook into ${dir}`);
|
|
2925
|
+
}
|
|
2926
|
+
async function removeOpenClawHooks(hooksDir) {
|
|
2927
|
+
const dir = path10.join(hooksDir, OPENCLAW_HOOK_DIR);
|
|
2928
|
+
if (await pathExists(dir)) {
|
|
2929
|
+
await remove(dir);
|
|
2930
|
+
log.success(`Removed teamai OpenClaw hook from ${dir}`);
|
|
2931
|
+
}
|
|
2932
|
+
if (process.env.OPENCLAW_STATE_DIR) {
|
|
2933
|
+
const altDir = path10.join(process.env.OPENCLAW_STATE_DIR, "hooks", OPENCLAW_HOOK_DIR);
|
|
2934
|
+
if (altDir !== dir && await pathExists(altDir)) {
|
|
2935
|
+
await remove(altDir);
|
|
2936
|
+
log.success(`Removed teamai OpenClaw hook from ${altDir}`);
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
function buildAgentHookMd(slug, openclawEvent) {
|
|
2941
|
+
const metadata = JSON.stringify({ openclaw: { events: [openclawEvent] } });
|
|
2942
|
+
return [
|
|
2943
|
+
"---",
|
|
2944
|
+
`name: ${TEAMAI_MARKER} ${slug}`,
|
|
2945
|
+
`metadata:`,
|
|
2946
|
+
` ${metadata}`,
|
|
2947
|
+
`handler: ./handler.ts`,
|
|
2948
|
+
"---",
|
|
2949
|
+
"",
|
|
2950
|
+
`${TEAMAI_MARKER} Agent hook [${slug}] \u2014 managed by teamai, do not edit by hand.`,
|
|
2951
|
+
""
|
|
2952
|
+
].join("\n");
|
|
2953
|
+
}
|
|
2954
|
+
function buildAgentHandlerTs(command, timeout) {
|
|
2955
|
+
const timeoutMs = timeout * 1e3;
|
|
2956
|
+
return [
|
|
2957
|
+
`// ${TEAMAI_MARKER} agent hook handler \u2014 generated by teamai, do not edit.`,
|
|
2958
|
+
`import { spawn } from 'node:child_process';`,
|
|
2959
|
+
"",
|
|
2960
|
+
`export default async function handler(): Promise<void> {`,
|
|
2961
|
+
" try {",
|
|
2962
|
+
` const child = spawn('sh', ['-c', ${JSON.stringify(command)}], {`,
|
|
2963
|
+
` stdio: ['inherit', 'ignore', 'ignore'],`,
|
|
2964
|
+
` timeout: ${timeoutMs},`,
|
|
2965
|
+
" });",
|
|
2966
|
+
" child.on('error', () => {});",
|
|
2967
|
+
" } catch {",
|
|
2968
|
+
" // never block the agent",
|
|
2969
|
+
" }",
|
|
2970
|
+
"}",
|
|
2971
|
+
""
|
|
2972
|
+
].join("\n");
|
|
2973
|
+
}
|
|
2974
|
+
async function applyOpenClawAgentHook(def) {
|
|
2975
|
+
const openclawEvent = CLAUDE_TO_OPENCLAW_EVENTS[def.event];
|
|
2976
|
+
if (!openclawEvent) {
|
|
2977
|
+
log.warn(`OpenClaw does not support event "${def.event}" \u2014 skipping hook [${def.slug}]`);
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
const tool = def.tool ?? "openclaw";
|
|
2981
|
+
const hooksDir = resolveOpenClawHooksDir(tool);
|
|
2982
|
+
const dir = path10.join(hooksDir, def.slug);
|
|
2983
|
+
await ensureDir(dir);
|
|
2984
|
+
await writeFile(path10.join(dir, "HOOK.md"), buildAgentHookMd(def.slug, openclawEvent));
|
|
2985
|
+
await writeFile(path10.join(dir, "handler.ts"), buildAgentHandlerTs(def.command, def.timeout ?? 10));
|
|
2986
|
+
log.success(`Installed OpenClaw agent hook [${def.slug}] in ${dir}`);
|
|
2987
|
+
}
|
|
2988
|
+
async function removeOpenClawAgentHook(opts) {
|
|
2989
|
+
const tool = opts.tool ?? "openclaw";
|
|
2990
|
+
const hooksDir = resolveOpenClawHooksDir(tool);
|
|
2991
|
+
const dir = path10.join(hooksDir, opts.slug);
|
|
2992
|
+
if (await pathExists(dir)) {
|
|
2993
|
+
await remove(dir);
|
|
2994
|
+
log.success(`Removed OpenClaw agent hook [${opts.slug}] from ${dir}`);
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
async function resolveOpenclawWorkspaceDir(workspacePath) {
|
|
2998
|
+
const candidates = [];
|
|
2999
|
+
if (workspacePath) candidates.push(workspacePath);
|
|
3000
|
+
const stateDir = process.env.OPENCLAW_STATE_DIR;
|
|
3001
|
+
if (stateDir && path10.isAbsolute(stateDir)) {
|
|
3002
|
+
const cfgRaw = await readFileSafe(path10.join(stateDir, "openclaw.json"));
|
|
3003
|
+
if (cfgRaw) {
|
|
3004
|
+
try {
|
|
3005
|
+
const cfg = JSON.parse(cfgRaw);
|
|
3006
|
+
const ws = cfg?.agents?.defaults?.workspace;
|
|
3007
|
+
if (typeof ws === "string" && ws) candidates.push(ws);
|
|
3008
|
+
} catch (e) {
|
|
3009
|
+
log.debug(`openclaw: failed to parse openclaw.json: ${e.message}`);
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
const home = process.env.HOME;
|
|
3014
|
+
if (home) candidates.push(path10.join(home, ".openclaw", "workspace"));
|
|
3015
|
+
for (const candidate of candidates) {
|
|
3016
|
+
if (await pathExists(candidate)) {
|
|
3017
|
+
log.debug(`openclaw: resolved workspace dir to ${candidate}`);
|
|
3018
|
+
return candidate;
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
log.warn(`openclaw: no workspace dir found (tried: ${candidates.join(", ") || "none"})`);
|
|
3022
|
+
return null;
|
|
3023
|
+
}
|
|
3024
|
+
var OPENCLAW_HOOK_DIR, TEAMAI_MARKER, EVENT_MAP, CLAUDE_TO_OPENCLAW_EVENTS;
|
|
3025
|
+
var init_openclaw_hooks = __esm({
|
|
3026
|
+
"src/openclaw-hooks.ts"() {
|
|
3027
|
+
"use strict";
|
|
3028
|
+
init_fs();
|
|
3029
|
+
init_logger();
|
|
3030
|
+
OPENCLAW_HOOK_DIR = "teamai-status-report";
|
|
3031
|
+
TEAMAI_MARKER = "[teamai]";
|
|
3032
|
+
EVENT_MAP = {
|
|
3033
|
+
"session:start": "session-start",
|
|
3034
|
+
"command:new": "prompt-submit"
|
|
3035
|
+
};
|
|
3036
|
+
CLAUDE_TO_OPENCLAW_EVENTS = {
|
|
3037
|
+
SessionStart: "session:start",
|
|
3038
|
+
UserPromptSubmit: "command:new"
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
});
|
|
3042
|
+
|
|
2859
3043
|
// src/builtin-hooks.ts
|
|
2860
3044
|
import fs5 from "fs";
|
|
2861
|
-
import
|
|
3045
|
+
import path11 from "path";
|
|
2862
3046
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2863
3047
|
function hasShell() {
|
|
2864
3048
|
if (_hasShellCache === void 0) {
|
|
@@ -2886,12 +3070,12 @@ function pickLatestVersion(versions) {
|
|
|
2886
3070
|
}
|
|
2887
3071
|
function resolveWorkbuddyNode() {
|
|
2888
3072
|
const home = process.env.HOME ?? "";
|
|
2889
|
-
const versionsDir =
|
|
3073
|
+
const versionsDir = path11.join(home, WORKBUDDY_BUNDLED_NODE_DIR);
|
|
2890
3074
|
try {
|
|
2891
3075
|
const versions = fs5.readdirSync(versionsDir).filter((d) => !d.startsWith("."));
|
|
2892
3076
|
const latest = pickLatestVersion(versions);
|
|
2893
3077
|
if (!latest) return null;
|
|
2894
|
-
const nodeBin =
|
|
3078
|
+
const nodeBin = path11.join(versionsDir, latest, "bin", "node");
|
|
2895
3079
|
if (fs5.existsSync(nodeBin)) return nodeBin;
|
|
2896
3080
|
} catch {
|
|
2897
3081
|
}
|
|
@@ -2904,10 +3088,10 @@ function resolveCodebuddyNode() {
|
|
|
2904
3088
|
for (const entry of entries) {
|
|
2905
3089
|
if (!entry.startsWith(".codebuddy-server")) continue;
|
|
2906
3090
|
try {
|
|
2907
|
-
const binDir =
|
|
3091
|
+
const binDir = path11.join(home, entry, "bin");
|
|
2908
3092
|
const stableDirs = fs5.readdirSync(binDir).filter((d) => d.startsWith("stable-"));
|
|
2909
3093
|
for (const stable of stableDirs) {
|
|
2910
|
-
const nodeBin =
|
|
3094
|
+
const nodeBin = path11.join(binDir, stable, "node");
|
|
2911
3095
|
if (fs5.existsSync(nodeBin)) return nodeBin;
|
|
2912
3096
|
}
|
|
2913
3097
|
} catch {
|
|
@@ -2920,8 +3104,8 @@ function resolveCodebuddyNode() {
|
|
|
2920
3104
|
function resolveTeamaiEntryScript() {
|
|
2921
3105
|
try {
|
|
2922
3106
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
2923
|
-
const distDir =
|
|
2924
|
-
const candidate =
|
|
3107
|
+
const distDir = path11.dirname(thisFile);
|
|
3108
|
+
const candidate = path11.join(distDir, "index.js");
|
|
2925
3109
|
if (fs5.existsSync(candidate)) return candidate;
|
|
2926
3110
|
} catch {
|
|
2927
3111
|
}
|
|
@@ -2932,8 +3116,8 @@ function ensureTeamaiWrapper() {
|
|
|
2932
3116
|
if (!entryScript) return null;
|
|
2933
3117
|
const nodeBin = resolveWorkbuddyNode() ?? resolveCodebuddyNode() ?? process.argv[0];
|
|
2934
3118
|
const home = process.env.HOME ?? "";
|
|
2935
|
-
const binDir =
|
|
2936
|
-
const wrapperPath =
|
|
3119
|
+
const binDir = path11.join(home, TEAMAI_BIN_DIR);
|
|
3120
|
+
const wrapperPath = path11.join(binDir, WRAPPER_NAME);
|
|
2937
3121
|
const script = [
|
|
2938
3122
|
"#!/bin/sh",
|
|
2939
3123
|
`# Auto-generated by teamai \u2014 do not edit.`,
|
|
@@ -3007,11 +3191,11 @@ var init_builtin_hooks = __esm({
|
|
|
3007
3191
|
});
|
|
3008
3192
|
|
|
3009
3193
|
// src/resources/hooks.ts
|
|
3010
|
-
import
|
|
3194
|
+
import path12 from "path";
|
|
3011
3195
|
import { z as z3 } from "zod";
|
|
3012
3196
|
import YAML2 from "yaml";
|
|
3013
3197
|
function teamHooksYamlPath(repoPath) {
|
|
3014
|
-
return
|
|
3198
|
+
return path12.join(repoPath, "hooks", "hooks.yaml");
|
|
3015
3199
|
}
|
|
3016
3200
|
async function parseHooksYaml(repoPath) {
|
|
3017
3201
|
const content = await readFileSafe(teamHooksYamlPath(repoPath));
|
|
@@ -3134,80 +3318,6 @@ var init_hooks = __esm({
|
|
|
3134
3318
|
}
|
|
3135
3319
|
});
|
|
3136
3320
|
|
|
3137
|
-
// src/openclaw-hooks.ts
|
|
3138
|
-
var openclaw_hooks_exports = {};
|
|
3139
|
-
__export(openclaw_hooks_exports, {
|
|
3140
|
-
OPENCLAW_HOOK_DIR: () => OPENCLAW_HOOK_DIR,
|
|
3141
|
-
injectOpenClawHooks: () => injectOpenClawHooks,
|
|
3142
|
-
removeOpenClawHooks: () => removeOpenClawHooks
|
|
3143
|
-
});
|
|
3144
|
-
import path12 from "path";
|
|
3145
|
-
function buildHookMd(tool) {
|
|
3146
|
-
const events = Object.keys(EVENT_MAP);
|
|
3147
|
-
return [
|
|
3148
|
-
"---",
|
|
3149
|
-
`name: ${TEAMAI_MARKER} status-report`,
|
|
3150
|
-
"events:",
|
|
3151
|
-
...events.map((e) => ` - ${e}`),
|
|
3152
|
-
`handler: ./handler.ts`,
|
|
3153
|
-
"---",
|
|
3154
|
-
"",
|
|
3155
|
-
`${TEAMAI_MARKER} Reports agent status to the team backend (report/sync/ack) for tool \`${tool}\`.`,
|
|
3156
|
-
"Managed by teamai \u2014 do not edit by hand.",
|
|
3157
|
-
""
|
|
3158
|
-
].join("\n");
|
|
3159
|
-
}
|
|
3160
|
-
function buildHandlerTs(tool) {
|
|
3161
|
-
const mapLiteral = JSON.stringify(EVENT_MAP);
|
|
3162
|
-
return `// ${TEAMAI_MARKER} status-report handler \u2014 generated by teamai, do not edit.
|
|
3163
|
-
import { spawn } from 'node:child_process';
|
|
3164
|
-
|
|
3165
|
-
const EVENT_MAP: Record<string, string> = ${mapLiteral};
|
|
3166
|
-
const TOOL = ${JSON.stringify(tool)};
|
|
3167
|
-
|
|
3168
|
-
export default async function handler(ctx: { event?: string } = {}): Promise<void> {
|
|
3169
|
-
const dispatchEvent = ctx.event ? EVENT_MAP[ctx.event] : undefined;
|
|
3170
|
-
if (!dispatchEvent) return;
|
|
3171
|
-
try {
|
|
3172
|
-
const child = spawn('teamai', ['hook-dispatch', dispatchEvent, '--tool', TOOL], {
|
|
3173
|
-
stdio: ['inherit', 'ignore', 'ignore'],
|
|
3174
|
-
});
|
|
3175
|
-
child.on('error', () => {});
|
|
3176
|
-
} catch {
|
|
3177
|
-
// never block the agent
|
|
3178
|
-
}
|
|
3179
|
-
}
|
|
3180
|
-
`;
|
|
3181
|
-
}
|
|
3182
|
-
async function injectOpenClawHooks(hooksDir, tool = "openclaw") {
|
|
3183
|
-
const dir = path12.join(hooksDir, OPENCLAW_HOOK_DIR);
|
|
3184
|
-
await ensureDir(dir);
|
|
3185
|
-
await writeFile(path12.join(dir, "HOOK.md"), buildHookMd(tool));
|
|
3186
|
-
await writeFile(path12.join(dir, "handler.ts"), buildHandlerTs(tool));
|
|
3187
|
-
log.success(`Injected teamai OpenClaw hook into ${dir}`);
|
|
3188
|
-
}
|
|
3189
|
-
async function removeOpenClawHooks(hooksDir) {
|
|
3190
|
-
const dir = path12.join(hooksDir, OPENCLAW_HOOK_DIR);
|
|
3191
|
-
if (await pathExists(dir)) {
|
|
3192
|
-
await remove(dir);
|
|
3193
|
-
log.success(`Removed teamai OpenClaw hook from ${dir}`);
|
|
3194
|
-
}
|
|
3195
|
-
}
|
|
3196
|
-
var OPENCLAW_HOOK_DIR, TEAMAI_MARKER, EVENT_MAP;
|
|
3197
|
-
var init_openclaw_hooks = __esm({
|
|
3198
|
-
"src/openclaw-hooks.ts"() {
|
|
3199
|
-
"use strict";
|
|
3200
|
-
init_fs();
|
|
3201
|
-
init_logger();
|
|
3202
|
-
OPENCLAW_HOOK_DIR = "teamai-status-report";
|
|
3203
|
-
TEAMAI_MARKER = "[teamai]";
|
|
3204
|
-
EVENT_MAP = {
|
|
3205
|
-
"session:start": "session-start",
|
|
3206
|
-
"command:new": "prompt-submit"
|
|
3207
|
-
};
|
|
3208
|
-
}
|
|
3209
|
-
});
|
|
3210
|
-
|
|
3211
3321
|
// src/hermes-home.ts
|
|
3212
3322
|
var hermes_home_exports = {};
|
|
3213
3323
|
__export(hermes_home_exports, {
|
|
@@ -3477,6 +3587,7 @@ var hooks_exports = {};
|
|
|
3477
3587
|
__export(hooks_exports, {
|
|
3478
3588
|
AGENT_HOOK_EVENTS: () => AGENT_HOOK_EVENTS,
|
|
3479
3589
|
CLAUDE_TO_CURSOR_EVENTS: () => CLAUDE_TO_CURSOR_EVENTS,
|
|
3590
|
+
OPENCLAW_TOOLS: () => OPENCLAW_TOOLS,
|
|
3480
3591
|
TEAMAI_HOOK_SUBCOMMANDS: () => TEAMAI_HOOK_SUBCOMMANDS,
|
|
3481
3592
|
TEAMAI_LEGACY_HOOK_SUBCOMMANDS: () => TEAMAI_LEGACY_HOOK_SUBCOMMANDS,
|
|
3482
3593
|
agentHookDescription: () => agentHookDescription,
|
|
@@ -3684,7 +3795,7 @@ async function reconcileCodexFormat(hooksPath, tool, teamDefs, opts, priorTeamCo
|
|
|
3684
3795
|
}
|
|
3685
3796
|
}
|
|
3686
3797
|
function isAgentHookSupportedTool(tool) {
|
|
3687
|
-
return !CURSOR_TOOLS.has(tool)
|
|
3798
|
+
return !CURSOR_TOOLS.has(tool);
|
|
3688
3799
|
}
|
|
3689
3800
|
function isAgentHookEvent(event) {
|
|
3690
3801
|
return AGENT_HOOK_EVENTS.has(event);
|
|
@@ -4333,9 +4444,9 @@ async function scanTranscriptStop(transcriptPath) {
|
|
|
4333
4444
|
}
|
|
4334
4445
|
if (entry.type === "assistant") {
|
|
4335
4446
|
const usage = entry.message?.usage;
|
|
4336
|
-
const
|
|
4337
|
-
if (usage &&
|
|
4338
|
-
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);
|
|
4339
4450
|
tokens.input += toNum(usage.input_tokens);
|
|
4340
4451
|
tokens.output += toNum(usage.output_tokens);
|
|
4341
4452
|
tokens.cacheRead += toNum(usage.cache_read_input_tokens);
|
|
@@ -4793,6 +4904,16 @@ async function detectWorkbuddyVersion() {
|
|
|
4793
4904
|
}
|
|
4794
4905
|
return "";
|
|
4795
4906
|
}
|
|
4907
|
+
async function detectHermesVersion() {
|
|
4908
|
+
const raw = await execVersion("hermes");
|
|
4909
|
+
const match = raw.match(/^\(?(\d+(?:\.\d+)*)\)?/);
|
|
4910
|
+
return match?.[1] ?? "";
|
|
4911
|
+
}
|
|
4912
|
+
async function detectOpenclawVersion() {
|
|
4913
|
+
const raw = await execVersion("openclaw");
|
|
4914
|
+
const match = raw.match(/^([\d.]+)/);
|
|
4915
|
+
return match?.[1] ?? "";
|
|
4916
|
+
}
|
|
4796
4917
|
async function getAgentVersion(agentType) {
|
|
4797
4918
|
if (VERSION_CACHE.has(agentType)) return VERSION_CACHE.get(agentType);
|
|
4798
4919
|
const detector = DETECTORS[agentType];
|
|
@@ -4825,7 +4946,9 @@ var init_agent_version = __esm({
|
|
|
4825
4946
|
cursor: detectCursorVersion,
|
|
4826
4947
|
codebuddy: detectCodebuddyCliVersion,
|
|
4827
4948
|
"codebuddy-ide": detectCodebuddyIdeVersion,
|
|
4828
|
-
workbuddy: detectWorkbuddyVersion
|
|
4949
|
+
workbuddy: detectWorkbuddyVersion,
|
|
4950
|
+
hermes: detectHermesVersion,
|
|
4951
|
+
openclaw: detectOpenclawVersion
|
|
4829
4952
|
};
|
|
4830
4953
|
}
|
|
4831
4954
|
});
|
|
@@ -4834,24 +4957,29 @@ var init_agent_version = __esm({
|
|
|
4834
4957
|
import crypto2 from "crypto";
|
|
4835
4958
|
import fs8 from "fs";
|
|
4836
4959
|
import { execFileSync } from "child_process";
|
|
4960
|
+
import os2 from "os";
|
|
4837
4961
|
function getMachineId() {
|
|
4838
4962
|
if (cachedMachineId !== null) return cachedMachineId;
|
|
4839
4963
|
cachedMachineId = detectMachineId();
|
|
4840
4964
|
return cachedMachineId;
|
|
4841
4965
|
}
|
|
4842
4966
|
function detectMachineId(platform = process.platform) {
|
|
4967
|
+
let id = "";
|
|
4843
4968
|
try {
|
|
4844
4969
|
switch (platform) {
|
|
4845
4970
|
case "darwin":
|
|
4846
|
-
|
|
4971
|
+
id = readDarwinMachineId();
|
|
4972
|
+
break;
|
|
4847
4973
|
case "win32":
|
|
4848
|
-
|
|
4974
|
+
id = readWindowsMachineId();
|
|
4975
|
+
break;
|
|
4849
4976
|
default:
|
|
4850
|
-
|
|
4977
|
+
id = readLinuxMachineId();
|
|
4978
|
+
break;
|
|
4851
4979
|
}
|
|
4852
4980
|
} catch {
|
|
4853
|
-
return "";
|
|
4854
4981
|
}
|
|
4982
|
+
return id || os2.hostname() || "";
|
|
4855
4983
|
}
|
|
4856
4984
|
function readDarwinMachineId() {
|
|
4857
4985
|
const out = execFileSync("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
|
|
@@ -4896,7 +5024,7 @@ var init_machine_id = __esm({
|
|
|
4896
5024
|
});
|
|
4897
5025
|
|
|
4898
5026
|
// src/utils/path-safety.ts
|
|
4899
|
-
import
|
|
5027
|
+
import os3 from "os";
|
|
4900
5028
|
import path19 from "path";
|
|
4901
5029
|
import fs9 from "fs";
|
|
4902
5030
|
function assertSafePath(target, allowedRoots) {
|
|
@@ -4912,7 +5040,7 @@ function assertSafePath(target, allowedRoots) {
|
|
|
4912
5040
|
);
|
|
4913
5041
|
}
|
|
4914
5042
|
function resolveReal(p) {
|
|
4915
|
-
const expanded = p.startsWith("~") ? path19.join(
|
|
5043
|
+
const expanded = p.startsWith("~") ? path19.join(os3.homedir(), p.slice(1)) : p;
|
|
4916
5044
|
const abs = path19.resolve(expanded);
|
|
4917
5045
|
try {
|
|
4918
5046
|
return fs9.realpathSync(abs);
|
|
@@ -4921,7 +5049,7 @@ function resolveReal(p) {
|
|
|
4921
5049
|
}
|
|
4922
5050
|
}
|
|
4923
5051
|
function defaultAllowedRoots() {
|
|
4924
|
-
return [process.cwd(),
|
|
5052
|
+
return [process.cwd(), os3.homedir()];
|
|
4925
5053
|
}
|
|
4926
5054
|
function assertSafeResourceName(name) {
|
|
4927
5055
|
if (name.includes("\0")) {
|
|
@@ -5300,7 +5428,7 @@ __export(local_agent_exports, {
|
|
|
5300
5428
|
writeTokenFile: () => writeTokenFile
|
|
5301
5429
|
});
|
|
5302
5430
|
import fs10 from "fs";
|
|
5303
|
-
import
|
|
5431
|
+
import os4 from "os";
|
|
5304
5432
|
import path20 from "path";
|
|
5305
5433
|
import readline3 from "readline";
|
|
5306
5434
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -5525,7 +5653,7 @@ function createResourceLocalConfig(config, scope, repoPath, workspacePath) {
|
|
|
5525
5653
|
const projectScope = scope === "project";
|
|
5526
5654
|
return {
|
|
5527
5655
|
repo: { localPath: repoPath, remote: config.endpoint },
|
|
5528
|
-
username:
|
|
5656
|
+
username: os4.userInfo().username,
|
|
5529
5657
|
scope: projectScope ? "project" : "user",
|
|
5530
5658
|
projectRoot: projectScope ? workspacePath : void 0,
|
|
5531
5659
|
additionalRoles: []
|
|
@@ -5835,7 +5963,7 @@ async function bindWorkspaceToProject(workspacePath, projectId) {
|
|
|
5835
5963
|
async function ensureWorkspaceBinding(config, workspacePath, sessionId) {
|
|
5836
5964
|
if (config.workspaceBindings[workspacePath]) return;
|
|
5837
5965
|
const markerKey = sessionId || `ppid-${process.ppid}`;
|
|
5838
|
-
const hintMarker = path20.join(
|
|
5966
|
+
const hintMarker = path20.join(os4.tmpdir(), `teamai-bind-session-${markerKey}`);
|
|
5839
5967
|
if (fs10.existsSync(hintMarker)) return;
|
|
5840
5968
|
try {
|
|
5841
5969
|
fs10.writeFileSync(hintMarker, "");
|
|
@@ -5883,7 +6011,7 @@ function isBindPromptEnabled() {
|
|
|
5883
6011
|
async function emitBindingHint(config, workspacePath, sessionId) {
|
|
5884
6012
|
if (config.workspaceBindings[workspacePath]) return;
|
|
5885
6013
|
const markerKey = sessionId || `ppid-${process.ppid}`;
|
|
5886
|
-
const hintMarker = path20.join(
|
|
6014
|
+
const hintMarker = path20.join(os4.tmpdir(), `teamai-bind-hint-${markerKey}`);
|
|
5887
6015
|
if (fs10.existsSync(hintMarker)) return;
|
|
5888
6016
|
try {
|
|
5889
6017
|
fs10.writeFileSync(hintMarker, "");
|
|
@@ -6044,8 +6172,8 @@ async function buildReportPayload(config, context) {
|
|
|
6044
6172
|
agent_type: normalizeAgentType(tool),
|
|
6045
6173
|
agent_version: await getAgentVersion(tool),
|
|
6046
6174
|
local_agent_id: resolveLocalAgentId(context),
|
|
6047
|
-
host_name:
|
|
6048
|
-
os:
|
|
6175
|
+
host_name: os4.hostname(),
|
|
6176
|
+
os: os4.platform(),
|
|
6049
6177
|
started_at: config.createdAt,
|
|
6050
6178
|
last_status: context.status ?? "running",
|
|
6051
6179
|
// Instance-level skills/rules are a phase-1 legacy concept. They are
|
|
@@ -6156,7 +6284,7 @@ function assertHttpUrl(rawUrl) {
|
|
|
6156
6284
|
return parsed;
|
|
6157
6285
|
}
|
|
6158
6286
|
async function downloadResource(downloadUrl) {
|
|
6159
|
-
const tmpDir = await fs10.promises.mkdtemp(path20.join(
|
|
6287
|
+
const tmpDir = await fs10.promises.mkdtemp(path20.join(os4.tmpdir(), "teamai-local-agent-"));
|
|
6160
6288
|
const filePath = path20.join(tmpDir, "resource");
|
|
6161
6289
|
let current = assertHttpUrl(downloadUrl);
|
|
6162
6290
|
let response;
|
|
@@ -6326,7 +6454,7 @@ async function installDownloadedResource(input) {
|
|
|
6326
6454
|
const dest = path20.join(repoPath, "claudemd", `${input.slug}.md`);
|
|
6327
6455
|
await fse3.ensureDir(path20.dirname(dest));
|
|
6328
6456
|
await fse3.copyFile(mdFile, dest);
|
|
6329
|
-
await syncClaudemd(teamConfig, localConfig, repoPath);
|
|
6457
|
+
await syncClaudemd(teamConfig, localConfig, repoPath, input.workspacePath);
|
|
6330
6458
|
}
|
|
6331
6459
|
const version2 = commandVersion(input.command, input.kind);
|
|
6332
6460
|
const manifest = await loadManifest();
|
|
@@ -6364,12 +6492,28 @@ async function uninstallResource(input) {
|
|
|
6364
6492
|
await new RulesHandler().removeItem(input.slug, teamConfig, localConfig);
|
|
6365
6493
|
} else {
|
|
6366
6494
|
await remove(path20.join(repoPath, "claudemd", `${input.slug}.md`));
|
|
6367
|
-
await syncClaudemd(teamConfig, localConfig, repoPath);
|
|
6495
|
+
await syncClaudemd(teamConfig, localConfig, repoPath, input.workspacePath);
|
|
6368
6496
|
}
|
|
6369
6497
|
delete scopeManifest[manifestKind(input.kind)][input.slug];
|
|
6370
6498
|
await saveManifest(manifest);
|
|
6371
6499
|
}
|
|
6372
|
-
async function
|
|
6500
|
+
async function resolveHermesUserBaseDir() {
|
|
6501
|
+
try {
|
|
6502
|
+
const envWs = process.env.TEAMAI_HERMES_WORKSPACE;
|
|
6503
|
+
if (envWs && path20.isAbsolute(envWs)) return envWs;
|
|
6504
|
+
const cfg = await readJson(getConfigPath2());
|
|
6505
|
+
const bindings = cfg?.workspaceBindings;
|
|
6506
|
+
if (bindings && typeof bindings === "object") {
|
|
6507
|
+
const entries = Object.entries(bindings).filter(([p, v]) => path20.isAbsolute(p) && v?.ideType === "hermes").sort((a, b) => (b[1].boundAt ?? "").localeCompare(a[1].boundAt ?? ""));
|
|
6508
|
+
for (const [p] of entries) {
|
|
6509
|
+
if (await pathExists(path20.join(p, ".hermes"))) return p;
|
|
6510
|
+
}
|
|
6511
|
+
}
|
|
6512
|
+
} catch {
|
|
6513
|
+
}
|
|
6514
|
+
return void 0;
|
|
6515
|
+
}
|
|
6516
|
+
async function syncClaudemd(teamConfig, localConfig, repoPath, workspacePath) {
|
|
6373
6517
|
const claudemdDir = path20.join(repoPath, "claudemd");
|
|
6374
6518
|
const files = await pathExists(claudemdDir) ? (await fse3.readdir(claudemdDir)).filter((file) => file.endsWith(".md")).sort() : [];
|
|
6375
6519
|
const contents = [];
|
|
@@ -6378,24 +6522,48 @@ async function syncClaudemd(teamConfig, localConfig, repoPath) {
|
|
|
6378
6522
|
if (content) contents.push(content);
|
|
6379
6523
|
}
|
|
6380
6524
|
const block = compileClaudemdBlock(contents);
|
|
6381
|
-
|
|
6525
|
+
let syncedAny = false;
|
|
6526
|
+
const defaultBaseDir = localConfig.scope === "project" && localConfig.projectRoot ? localConfig.projectRoot : process.env.HOME ?? "";
|
|
6382
6527
|
for (const [tool, toolPath] of Object.entries(teamConfig.toolPaths)) {
|
|
6383
6528
|
if (!toolPath.claudemd) continue;
|
|
6384
|
-
|
|
6385
|
-
|
|
6529
|
+
let baseDir = defaultBaseDir;
|
|
6530
|
+
let resolvedAbsPath = null;
|
|
6531
|
+
if (tool === "openclaw" && localConfig.scope !== "project") {
|
|
6532
|
+
const openclawWs = await resolveOpenclawWorkspaceDir(workspacePath);
|
|
6533
|
+
if (openclawWs) {
|
|
6534
|
+
resolvedAbsPath = path20.join(openclawWs, path20.basename(toolPath.claudemd));
|
|
6535
|
+
}
|
|
6536
|
+
} else if (tool === "hermes" && localConfig.scope !== "project") {
|
|
6537
|
+
const hermesBase = workspacePath ?? await resolveHermesUserBaseDir();
|
|
6538
|
+
if (hermesBase) {
|
|
6539
|
+
baseDir = hermesBase;
|
|
6540
|
+
log.debug(`local-agent: hermes user-scope baseDir resolved to ${baseDir}`);
|
|
6541
|
+
}
|
|
6542
|
+
}
|
|
6543
|
+
const toolInstalled = resolvedAbsPath ? await pathExists(resolvedAbsPath) : toolPath.claudemd.includes("/") ? await ResourceHandler.isToolInstalled(toolPath.claudemd, baseDir) : await pathExists(path20.join(baseDir, `.${tool}`));
|
|
6544
|
+
if (!toolInstalled) {
|
|
6545
|
+
log.debug(`Skipped CLAUDE.md sync for ${tool}: target not found`);
|
|
6546
|
+
continue;
|
|
6547
|
+
}
|
|
6548
|
+
const claudeMdPath = resolvedAbsPath ?? path20.join(baseDir, toolPath.claudemd);
|
|
6386
6549
|
try {
|
|
6387
6550
|
const { injectClaudeMdSection: injectClaudeMdSection2 } = await Promise.resolve().then(() => (init_claudemd(), claudemd_exports));
|
|
6388
6551
|
if (block) {
|
|
6389
6552
|
await injectClaudeMdSection2(claudeMdPath, TEAMAI_CLAUDEMD_START, TEAMAI_CLAUDEMD_END, block);
|
|
6390
6553
|
log.debug(`local-agent: synced CLAUDE.md instructions to ${tool}`);
|
|
6554
|
+
syncedAny = true;
|
|
6391
6555
|
} else {
|
|
6392
6556
|
await removeClaudeMdSection(claudeMdPath, TEAMAI_CLAUDEMD_START, TEAMAI_CLAUDEMD_END);
|
|
6393
6557
|
log.debug(`local-agent: removed CLAUDE.md instructions from ${tool}`);
|
|
6558
|
+
syncedAny = true;
|
|
6394
6559
|
}
|
|
6395
6560
|
} catch (e) {
|
|
6396
6561
|
log.warn(`Failed to sync CLAUDE.md instructions to ${tool}: ${e.message}`);
|
|
6397
6562
|
}
|
|
6398
6563
|
}
|
|
6564
|
+
if (files.length > 0 && !syncedAny) {
|
|
6565
|
+
throw new Error("CLAUDE.md sync landed on no tool: every configured target was skipped");
|
|
6566
|
+
}
|
|
6399
6567
|
}
|
|
6400
6568
|
async function removeClaudeMdSection(filePath, startMarker, endMarker) {
|
|
6401
6569
|
const existing = await readFileSafe(filePath);
|
|
@@ -6521,6 +6689,9 @@ async function runHookRuleCommand(config, command, context) {
|
|
|
6521
6689
|
if (rec.tool === "hermes") {
|
|
6522
6690
|
const { removeHermesAgentHook: removeHermesAgentHook2 } = await Promise.resolve().then(() => (init_hermes_hooks(), hermes_hooks_exports));
|
|
6523
6691
|
await removeHermesAgentHook2({ slug, event: rec.event, command: rec.command });
|
|
6692
|
+
} else if (OPENCLAW_TOOLS.has(rec.tool)) {
|
|
6693
|
+
const { removeOpenClawAgentHook: removeOpenClawAgentHook2 } = await Promise.resolve().then(() => (init_openclaw_hooks(), openclaw_hooks_exports));
|
|
6694
|
+
await removeOpenClawAgentHook2({ slug, tool: rec.tool });
|
|
6524
6695
|
} else {
|
|
6525
6696
|
const settingsPath = resolveToolSettingsPath(config, rec.tool);
|
|
6526
6697
|
await removeAgentHook(settingsPath, rec.tool, { slug, command: rec.command });
|
|
@@ -6546,6 +6717,9 @@ async function runHookRuleCommand(config, command, context) {
|
|
|
6546
6717
|
if (prior.tool === "hermes") {
|
|
6547
6718
|
const { removeHermesAgentHook: removeHermesAgentHook2 } = await Promise.resolve().then(() => (init_hermes_hooks(), hermes_hooks_exports));
|
|
6548
6719
|
await removeHermesAgentHook2({ slug, event: prior.event, command: prior.command });
|
|
6720
|
+
} else if (OPENCLAW_TOOLS.has(prior.tool)) {
|
|
6721
|
+
const { removeOpenClawAgentHook: removeOpenClawAgentHook2 } = await Promise.resolve().then(() => (init_openclaw_hooks(), openclaw_hooks_exports));
|
|
6722
|
+
await removeOpenClawAgentHook2({ slug, tool: prior.tool });
|
|
6549
6723
|
} else {
|
|
6550
6724
|
const priorPath = resolveToolSettingsPath(config, prior.tool);
|
|
6551
6725
|
await removeAgentHook(priorPath, prior.tool, { slug, command: prior.command });
|
|
@@ -6557,6 +6731,9 @@ async function runHookRuleCommand(config, command, context) {
|
|
|
6557
6731
|
if (tool === "hermes") {
|
|
6558
6732
|
const { applyHermesAgentHook: applyHermesAgentHook2 } = await Promise.resolve().then(() => (init_hermes_hooks(), hermes_hooks_exports));
|
|
6559
6733
|
await applyHermesAgentHook2({ slug, event, command: cmd, matcher, timeout });
|
|
6734
|
+
} else if (OPENCLAW_TOOLS.has(tool)) {
|
|
6735
|
+
const { applyOpenClawAgentHook: applyOpenClawAgentHook2 } = await Promise.resolve().then(() => (init_openclaw_hooks(), openclaw_hooks_exports));
|
|
6736
|
+
await applyOpenClawAgentHook2({ slug, event, command: cmd, tool, matcher, timeout });
|
|
6560
6737
|
} else {
|
|
6561
6738
|
const settingsPath = resolveToolSettingsPath(config, tool);
|
|
6562
6739
|
await applyAgentHook(settingsPath, tool, { slug, event, command: cmd, matcher, timeout });
|
|
@@ -6781,6 +6958,9 @@ async function removeAllAgentHooks() {
|
|
|
6781
6958
|
if (rec.tool === "hermes") {
|
|
6782
6959
|
const { removeHermesAgentHook: removeHermesAgentHook2 } = await Promise.resolve().then(() => (init_hermes_hooks(), hermes_hooks_exports));
|
|
6783
6960
|
await removeHermesAgentHook2({ slug, event: rec.event, command: rec.command });
|
|
6961
|
+
} else if (OPENCLAW_TOOLS.has(rec.tool)) {
|
|
6962
|
+
const { removeOpenClawAgentHook: removeOpenClawAgentHook2 } = await Promise.resolve().then(() => (init_openclaw_hooks(), openclaw_hooks_exports));
|
|
6963
|
+
await removeOpenClawAgentHook2({ slug, tool: rec.tool });
|
|
6784
6964
|
} else {
|
|
6785
6965
|
const settingsPath = resolveToolSettingsPath(config, rec.tool);
|
|
6786
6966
|
await removeAgentHook(settingsPath, rec.tool, { slug, command: rec.command });
|
|
@@ -6854,6 +7034,7 @@ var init_local_agent = __esm({
|
|
|
6854
7034
|
init_machine_id();
|
|
6855
7035
|
init_builtin_rules();
|
|
6856
7036
|
init_builtin_hooks();
|
|
7037
|
+
init_openclaw_hooks();
|
|
6857
7038
|
init_path_safety();
|
|
6858
7039
|
init_tool_names();
|
|
6859
7040
|
init_http_log();
|
|
@@ -7442,6 +7623,7 @@ var init_skills = __esm({
|
|
|
7442
7623
|
init_fs();
|
|
7443
7624
|
init_logger();
|
|
7444
7625
|
init_builtin_skills();
|
|
7626
|
+
init_openclaw_hooks();
|
|
7445
7627
|
init_roles();
|
|
7446
7628
|
CONTRIBUTORS_FILE = "CONTRIBUTORS";
|
|
7447
7629
|
SKILL_MD = "SKILL.md";
|
|
@@ -7627,11 +7809,21 @@ var init_skills = __esm({
|
|
|
7627
7809
|
for (const [tool, toolPath] of Object.entries(teamConfig.toolPaths)) {
|
|
7628
7810
|
if (isAgentDisabled(localConfig, tool)) continue;
|
|
7629
7811
|
if (!toolPath.skills) continue;
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7812
|
+
let dest;
|
|
7813
|
+
if (tool === "openclaw") {
|
|
7814
|
+
const wsDir = await resolveOpenclawWorkspaceDir();
|
|
7815
|
+
if (!wsDir) {
|
|
7816
|
+
log.debug(`Skipping skill sync for openclaw: workspace dir not found`);
|
|
7817
|
+
continue;
|
|
7818
|
+
}
|
|
7819
|
+
dest = path22.join(wsDir, "skills", item.name);
|
|
7820
|
+
} else {
|
|
7821
|
+
if (!await ResourceHandler.isToolInstalled(toolPath.skills, baseDir)) {
|
|
7822
|
+
log.debug(`Skipping skill sync for ${tool}: tool not installed`);
|
|
7823
|
+
continue;
|
|
7824
|
+
}
|
|
7825
|
+
dest = path22.join(baseDir, toolPath.skills, item.name);
|
|
7633
7826
|
}
|
|
7634
|
-
const dest = path22.join(baseDir, toolPath.skills, item.name);
|
|
7635
7827
|
try {
|
|
7636
7828
|
await copyDir(item.sourcePath, dest);
|
|
7637
7829
|
await ensureSkillFrontmatter(dest, item.name);
|
|
@@ -7666,7 +7858,14 @@ var init_skills = __esm({
|
|
|
7666
7858
|
await this.addTombstone(name, localConfig);
|
|
7667
7859
|
for (const [tool, toolPath] of Object.entries(teamConfig.toolPaths)) {
|
|
7668
7860
|
if (!toolPath.skills) continue;
|
|
7669
|
-
|
|
7861
|
+
let skillDir;
|
|
7862
|
+
if (tool === "openclaw") {
|
|
7863
|
+
const wsDir = await resolveOpenclawWorkspaceDir();
|
|
7864
|
+
if (!wsDir) continue;
|
|
7865
|
+
skillDir = path22.join(wsDir, "skills", name);
|
|
7866
|
+
} else {
|
|
7867
|
+
skillDir = path22.join(baseDir, toolPath.skills, name);
|
|
7868
|
+
}
|
|
7670
7869
|
if (await pathExists(skillDir)) {
|
|
7671
7870
|
await remove(skillDir);
|
|
7672
7871
|
removed.push(skillDir);
|
|
@@ -9703,6 +9902,10 @@ async function pushCore(localConfig, teamConfig, options) {
|
|
|
9703
9902
|
try {
|
|
9704
9903
|
const repoPath = localConfig.repo.localPath;
|
|
9705
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
|
+
}
|
|
9706
9909
|
await resetToCleanMaster(git, repoPath);
|
|
9707
9910
|
await pullRepo(repoPath);
|
|
9708
9911
|
pullSpin.succeed("Up to date");
|
|
@@ -9743,8 +9946,8 @@ async function pushCore(localConfig, teamConfig, options) {
|
|
|
9743
9946
|
process.exitCode = 2;
|
|
9744
9947
|
return;
|
|
9745
9948
|
}
|
|
9746
|
-
const
|
|
9747
|
-
const skillPath = options.skill.startsWith("~") ? path32.join(
|
|
9949
|
+
const os8 = await import("os");
|
|
9950
|
+
const skillPath = options.skill.startsWith("~") ? path32.join(os8.homedir(), options.skill.slice(1)) : path32.resolve(options.skill);
|
|
9748
9951
|
let matchedItem;
|
|
9749
9952
|
for (const item of allItems) {
|
|
9750
9953
|
if (item.type !== "skills") continue;
|
|
@@ -9756,7 +9959,7 @@ async function pushCore(localConfig, teamConfig, options) {
|
|
|
9756
9959
|
matchedItem = item;
|
|
9757
9960
|
break;
|
|
9758
9961
|
}
|
|
9759
|
-
const skillInput = options.skill.replace(/^~/,
|
|
9962
|
+
const skillInput = options.skill.replace(/^~/, os8.homedir());
|
|
9760
9963
|
if (item.sourcePath.endsWith(skillInput) || item.sourcePath.includes(path32.sep + skillInput)) {
|
|
9761
9964
|
matchedItem = item;
|
|
9762
9965
|
break;
|
|
@@ -10039,6 +10242,7 @@ __export(git_exports, {
|
|
|
10039
10242
|
getRepoStatus: () => getRepoStatus,
|
|
10040
10243
|
hasCommits: () => hasCommits,
|
|
10041
10244
|
initRepo: () => initRepo,
|
|
10245
|
+
isDedicatedRepoRoot: () => isDedicatedRepoRoot,
|
|
10042
10246
|
isGitRepo: () => isGitRepo,
|
|
10043
10247
|
isMetadataOnlyDiff: () => isMetadataOnlyDiff,
|
|
10044
10248
|
pullRepo: () => pullRepo,
|
|
@@ -10047,6 +10251,7 @@ __export(git_exports, {
|
|
|
10047
10251
|
resetToCleanMaster: () => resetToCleanMaster
|
|
10048
10252
|
});
|
|
10049
10253
|
import fs12 from "fs";
|
|
10254
|
+
import { realpath } from "fs/promises";
|
|
10050
10255
|
import path33 from "path";
|
|
10051
10256
|
import fse8 from "fs-extra";
|
|
10052
10257
|
import simpleGit from "simple-git";
|
|
@@ -10276,6 +10481,21 @@ function generateBranchName(username) {
|
|
|
10276
10481
|
const timestamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
10277
10482
|
return `teamai/push/${username}/${timestamp}`;
|
|
10278
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
|
+
}
|
|
10279
10499
|
async function resetToCleanMaster(git, localPath) {
|
|
10280
10500
|
const status2 = await git.status();
|
|
10281
10501
|
const hasConflicts = status2.conflicted.length > 0;
|
|
@@ -11909,13 +12129,10 @@ function tokenize(text) {
|
|
|
11909
12129
|
if (t !== word) tokens.push(t);
|
|
11910
12130
|
}
|
|
11911
12131
|
}
|
|
11912
|
-
|
|
11913
|
-
|
|
11914
|
-
|
|
11915
|
-
|
|
11916
|
-
} else {
|
|
11917
|
-
flushCjkRun();
|
|
11918
|
-
}
|
|
12132
|
+
if (word.length === 1 && /[一-鿿]/.test(word)) {
|
|
12133
|
+
cjkRun.push(word);
|
|
12134
|
+
} else {
|
|
12135
|
+
flushCjkRun();
|
|
11919
12136
|
}
|
|
11920
12137
|
}
|
|
11921
12138
|
flushCjkRun();
|
|
@@ -11930,6 +12147,40 @@ function tokenCount(text) {
|
|
|
11930
12147
|
}
|
|
11931
12148
|
return count;
|
|
11932
12149
|
}
|
|
12150
|
+
function wordSegments(text) {
|
|
12151
|
+
if (!text) return [];
|
|
12152
|
+
const input = text.length > MAX_TOKENIZE_CHARS ? text.slice(0, MAX_TOKENIZE_CHARS) : text;
|
|
12153
|
+
const words = [];
|
|
12154
|
+
let run = [];
|
|
12155
|
+
let lastWasCjkWord = false;
|
|
12156
|
+
const flushRun = () => {
|
|
12157
|
+
if (run.length > 0 && lastWasCjkWord) {
|
|
12158
|
+
words[words.length - 1] += run.shift();
|
|
12159
|
+
lastWasCjkWord = false;
|
|
12160
|
+
}
|
|
12161
|
+
if (run.length > 0) {
|
|
12162
|
+
words.push(run.join(""));
|
|
12163
|
+
lastWasCjkWord = false;
|
|
12164
|
+
}
|
|
12165
|
+
run = [];
|
|
12166
|
+
};
|
|
12167
|
+
for (const seg of sharedSegmenter.segment(input)) {
|
|
12168
|
+
if (!seg.isWordLike) {
|
|
12169
|
+
flushRun();
|
|
12170
|
+
lastWasCjkWord = false;
|
|
12171
|
+
continue;
|
|
12172
|
+
}
|
|
12173
|
+
if (seg.segment.length === 1 && /[一-鿿]/.test(seg.segment)) {
|
|
12174
|
+
run.push(seg.segment);
|
|
12175
|
+
continue;
|
|
12176
|
+
}
|
|
12177
|
+
flushRun();
|
|
12178
|
+
words.push(seg.segment);
|
|
12179
|
+
lastWasCjkWord = /^[一-鿿]+$/.test(seg.segment);
|
|
12180
|
+
}
|
|
12181
|
+
flushRun();
|
|
12182
|
+
return words;
|
|
12183
|
+
}
|
|
11933
12184
|
var MAX_TOKENIZE_CHARS, sharedSegmenter;
|
|
11934
12185
|
var init_tokenizer = __esm({
|
|
11935
12186
|
"src/utils/tokenizer.ts"() {
|
|
@@ -12244,7 +12495,8 @@ __export(search_index_exports, {
|
|
|
12244
12495
|
parseLearningDoc: () => parseLearningDoc,
|
|
12245
12496
|
search: () => search,
|
|
12246
12497
|
titleFromFilename: () => titleFromFilename,
|
|
12247
|
-
tokenize: () => tokenize
|
|
12498
|
+
tokenize: () => tokenize,
|
|
12499
|
+
wordSegments: () => wordSegments
|
|
12248
12500
|
});
|
|
12249
12501
|
import path42 from "path";
|
|
12250
12502
|
import matter3 from "gray-matter";
|
|
@@ -12308,13 +12560,13 @@ function parseLearningDoc(content, filename) {
|
|
|
12308
12560
|
date: typeof data.date === "string" ? data.date : data.date instanceof Date ? data.date.toISOString().slice(0, 10) : void 0,
|
|
12309
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
|
|
12310
12562
|
};
|
|
12311
|
-
const bodyExcerpt = body
|
|
12563
|
+
const bodyExcerpt = body;
|
|
12312
12564
|
return { meta, bodyExcerpt };
|
|
12313
12565
|
} catch {
|
|
12314
12566
|
log.error(`Failed to parse frontmatter for ${filename}, using fallback`);
|
|
12315
12567
|
return {
|
|
12316
12568
|
meta: {},
|
|
12317
|
-
bodyExcerpt: content
|
|
12569
|
+
bodyExcerpt: content
|
|
12318
12570
|
};
|
|
12319
12571
|
}
|
|
12320
12572
|
}
|
|
@@ -12535,6 +12787,16 @@ async function loadIndex(indexPath) {
|
|
|
12535
12787
|
}
|
|
12536
12788
|
return raw;
|
|
12537
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
|
+
}
|
|
12538
12800
|
function search(query, index, limit = 5) {
|
|
12539
12801
|
if (!query.trim()) return [];
|
|
12540
12802
|
const queryTokens = tokenize(query);
|
|
@@ -12548,6 +12810,8 @@ function search(query, index, limit = 5) {
|
|
|
12548
12810
|
const docFreq = df[token] ?? 0;
|
|
12549
12811
|
return Math.log((N + 1) / (docFreq + 1)) + 1;
|
|
12550
12812
|
};
|
|
12813
|
+
const lengthNorm = Math.sqrt(queryTokens.length);
|
|
12814
|
+
const wordTokens = wordSegments(query).map((w) => ({ word: w, tokens: tokenize(w) }));
|
|
12551
12815
|
const results = [];
|
|
12552
12816
|
for (const entry of index.entries) {
|
|
12553
12817
|
let score = 0;
|
|
@@ -12568,8 +12832,9 @@ function search(query, index, limit = 5) {
|
|
|
12568
12832
|
score += 1 * idf(qt);
|
|
12569
12833
|
}
|
|
12570
12834
|
}
|
|
12571
|
-
const
|
|
12572
|
-
if (score > 0 && (hasTitleOrTagMatch ||
|
|
12835
|
+
const isDocsEntry = entry.type === "docs";
|
|
12836
|
+
if (score > 0 && (hasTitleOrTagMatch || isDocsEntry)) {
|
|
12837
|
+
score /= lengthNorm;
|
|
12573
12838
|
score += Math.min(entry.votes * 0.5, 5);
|
|
12574
12839
|
const domainMultiplier = domainWeightRow[entry.domain ?? "neutral"];
|
|
12575
12840
|
const typeMultiplier = TYPE_BONUS[entry.type];
|
|
@@ -12580,16 +12845,35 @@ function search(query, index, limit = 5) {
|
|
|
12580
12845
|
if (entry.hotness !== void 0 && entry.hotness < 1) {
|
|
12581
12846
|
score *= entry.hotness;
|
|
12582
12847
|
}
|
|
12583
|
-
|
|
12848
|
+
if (!hasTitleOrTagMatch) {
|
|
12849
|
+
results.push({ entry, score });
|
|
12850
|
+
continue;
|
|
12851
|
+
}
|
|
12852
|
+
const matchedTerms = [];
|
|
12853
|
+
const missingTerms = [];
|
|
12854
|
+
for (const { word, tokens: wt } of wordTokens) {
|
|
12855
|
+
const hit = wt.some((t) => entryTokens.has(`title:${t}`) || entryTokens.has(`tag:${t}`));
|
|
12856
|
+
(hit ? matchedTerms : missingTerms).push(word);
|
|
12857
|
+
}
|
|
12858
|
+
results.push({ entry, score, matchedTerms, missingTerms });
|
|
12584
12859
|
}
|
|
12585
12860
|
}
|
|
12586
12861
|
results.sort((a, b) => {
|
|
12587
12862
|
if (b.score !== a.score) return b.score - a.score;
|
|
12588
12863
|
return (b.entry.date || "").localeCompare(a.entry.date || "");
|
|
12589
12864
|
});
|
|
12590
|
-
|
|
12865
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12866
|
+
const deduped = [];
|
|
12867
|
+
for (const r of results) {
|
|
12868
|
+
const key = dedupKey(r);
|
|
12869
|
+
if (seen.has(key)) continue;
|
|
12870
|
+
seen.add(key);
|
|
12871
|
+
deduped.push(r);
|
|
12872
|
+
if (deduped.length === limit) break;
|
|
12873
|
+
}
|
|
12874
|
+
return deduped;
|
|
12591
12875
|
}
|
|
12592
|
-
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;
|
|
12593
12877
|
var init_search_index = __esm({
|
|
12594
12878
|
"src/utils/search-index.ts"() {
|
|
12595
12879
|
"use strict";
|
|
@@ -12597,7 +12881,6 @@ var init_search_index = __esm({
|
|
|
12597
12881
|
init_tokenizer();
|
|
12598
12882
|
init_logger();
|
|
12599
12883
|
init_types();
|
|
12600
|
-
MAX_BODY_CHARS = 2e3;
|
|
12601
12884
|
MAX_DOC_BYTES = 50 * 1024;
|
|
12602
12885
|
TECHNICAL_TAGS = /* @__PURE__ */ new Set([
|
|
12603
12886
|
"api",
|
|
@@ -13349,6 +13632,7 @@ var init_skill_health = __esm({
|
|
|
13349
13632
|
// src/digest.ts
|
|
13350
13633
|
var digest_exports = {};
|
|
13351
13634
|
__export(digest_exports, {
|
|
13635
|
+
buildSkillChangePathspec: () => buildSkillChangePathspec,
|
|
13352
13636
|
formatTokenCount: () => formatTokenCount,
|
|
13353
13637
|
generateDigest: () => generateDigest,
|
|
13354
13638
|
summarizeConversation: () => summarizeConversation,
|
|
@@ -13399,9 +13683,17 @@ function parseGitLogOutput(output) {
|
|
|
13399
13683
|
if (current) commits.push(current);
|
|
13400
13684
|
return commits;
|
|
13401
13685
|
}
|
|
13402
|
-
|
|
13686
|
+
function buildSkillChangePathspec(subdir = "") {
|
|
13687
|
+
const prefix = subdir ? `${subdir.replace(/\/+$/, "")}/` : "";
|
|
13688
|
+
const pathspec = `${prefix}skills/*/SKILL.md`;
|
|
13689
|
+
const escaped = prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13690
|
+
const skillRe = new RegExp(`^${escaped}skills/([^/]+)/SKILL\\.md$`);
|
|
13691
|
+
return { pathspec, skillRe };
|
|
13692
|
+
}
|
|
13693
|
+
async function getRecentSkillChanges(repoPath, subdir = "") {
|
|
13403
13694
|
const seen = /* @__PURE__ */ new Set();
|
|
13404
13695
|
const changes = [];
|
|
13696
|
+
const { pathspec, skillRe } = buildSkillChangePathspec(subdir);
|
|
13405
13697
|
try {
|
|
13406
13698
|
const git = createGit2(repoPath);
|
|
13407
13699
|
const rawOutput = await git.raw([
|
|
@@ -13411,13 +13703,13 @@ async function getRecentSkillChanges(repoPath) {
|
|
|
13411
13703
|
"--name-only",
|
|
13412
13704
|
"--pretty=format:%H|%an|%s",
|
|
13413
13705
|
"--",
|
|
13414
|
-
|
|
13706
|
+
pathspec
|
|
13415
13707
|
]);
|
|
13416
13708
|
if (!rawOutput.trim()) return changes;
|
|
13417
13709
|
const commits = parseGitLogOutput(rawOutput);
|
|
13418
13710
|
for (const commit of commits) {
|
|
13419
13711
|
for (const file of commit.files) {
|
|
13420
|
-
const match = file.match(
|
|
13712
|
+
const match = file.match(skillRe);
|
|
13421
13713
|
if (!match) continue;
|
|
13422
13714
|
const skillName = match[1];
|
|
13423
13715
|
if (seen.has(skillName)) continue;
|
|
@@ -13435,14 +13727,14 @@ async function getRecentSkillChanges(repoPath) {
|
|
|
13435
13727
|
"--name-only",
|
|
13436
13728
|
"--pretty=format:%H|%an|%s",
|
|
13437
13729
|
"--",
|
|
13438
|
-
|
|
13730
|
+
pathspec
|
|
13439
13731
|
]);
|
|
13440
13732
|
const newSkills = /* @__PURE__ */ new Set();
|
|
13441
13733
|
if (addedOutput.trim()) {
|
|
13442
13734
|
const addedCommits = parseGitLogOutput(addedOutput);
|
|
13443
13735
|
for (const commit of addedCommits) {
|
|
13444
13736
|
for (const file of commit.files) {
|
|
13445
|
-
const match = file.match(
|
|
13737
|
+
const match = file.match(skillRe);
|
|
13446
13738
|
if (match) {
|
|
13447
13739
|
newSkills.add(match[1]);
|
|
13448
13740
|
}
|
|
@@ -13623,7 +13915,10 @@ async function generateDigest(options) {
|
|
|
13623
13915
|
console.log(`\u{1F4CA} Knowledge base total: ${totalLearnings} learnings`);
|
|
13624
13916
|
console.log("");
|
|
13625
13917
|
}
|
|
13626
|
-
const skillChanges = await getRecentSkillChanges(
|
|
13918
|
+
const skillChanges = localConfig.repo.kind === "self" ? await getRecentSkillChanges(
|
|
13919
|
+
localConfig.repo.businessRepoRoot ?? path44.dirname(repoPath),
|
|
13920
|
+
".teamai"
|
|
13921
|
+
) : await getRecentSkillChanges(repoPath);
|
|
13627
13922
|
const newSkills = skillChanges.filter((c) => c.type === "new");
|
|
13628
13923
|
const updatedSkills = skillChanges.filter((c) => c.type === "updated");
|
|
13629
13924
|
if (newSkills.length > 0) {
|
|
@@ -14099,6 +14394,10 @@ async function reportUsageToTeam(repoPath, username, options) {
|
|
|
14099
14394
|
writeRoot = await ensureReportsWorktree2(selfConfig);
|
|
14100
14395
|
} else {
|
|
14101
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
|
+
}
|
|
14102
14401
|
await resetToCleanMaster(git, repoPath);
|
|
14103
14402
|
await pullRepo(repoPath);
|
|
14104
14403
|
}
|
|
@@ -15092,7 +15391,15 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
15092
15391
|
const docsRepoDir = path48.join(localConfig.repo.localPath, "docs");
|
|
15093
15392
|
const rulesRepoDir = path48.join(localConfig.repo.localPath, "rules");
|
|
15094
15393
|
const skillsRepoDir = path48.join(localConfig.repo.localPath, "skills");
|
|
15095
|
-
|
|
15394
|
+
let votesDir = path48.join(localConfig.repo.localPath, "votes");
|
|
15395
|
+
if (localConfig.repo.kind === "self") {
|
|
15396
|
+
try {
|
|
15397
|
+
const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
15398
|
+
votesDir = path48.join(await ensureReportsWorktree2(localConfig), "votes");
|
|
15399
|
+
} catch (e) {
|
|
15400
|
+
log.debug(`[self] reports worktree for votes unavailable: ${e.message}`);
|
|
15401
|
+
}
|
|
15402
|
+
}
|
|
15096
15403
|
let learningsCount = 0;
|
|
15097
15404
|
let effectiveLearningsDir;
|
|
15098
15405
|
if (localConfig.scope === "user") {
|
|
@@ -15258,7 +15565,15 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
15258
15565
|
const YAML20 = (await import("yaml")).default;
|
|
15259
15566
|
const { listFiles: listFiles2, readFileSafe: readFileSafe5 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
15260
15567
|
const { getRecommendations: getRecommendations2, displayRecommendations: displayRecommendations2 } = await Promise.resolve().then(() => (init_skill_recommend(), skill_recommend_exports));
|
|
15261
|
-
|
|
15568
|
+
let statsDir = path48.join(localConfig.repo.localPath, "stats");
|
|
15569
|
+
if (localConfig.repo.kind === "self") {
|
|
15570
|
+
try {
|
|
15571
|
+
const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
15572
|
+
statsDir = path48.join(await ensureReportsWorktree2(localConfig), "stats");
|
|
15573
|
+
} catch (e) {
|
|
15574
|
+
log.debug(`[self] reports worktree for stats unavailable: ${e.message}`);
|
|
15575
|
+
}
|
|
15576
|
+
}
|
|
15262
15577
|
const files = await listFiles2(statsDir);
|
|
15263
15578
|
const teamStats = [];
|
|
15264
15579
|
for (const file of files) {
|
|
@@ -15532,7 +15847,7 @@ async function pull(options) {
|
|
|
15532
15847
|
const { reportUsageToTeam: reportUsageToTeam2 } = await Promise.resolve().then(() => (init_team_push(), team_push_exports));
|
|
15533
15848
|
const { truncateUsageAfterReport: truncateUsageAfterReport2, readUsageEvents: readUsageEvents2 } = await Promise.resolve().then(() => (init_usage_tracker(), usage_tracker_exports));
|
|
15534
15849
|
const targets = [];
|
|
15535
|
-
if (projectConfig) {
|
|
15850
|
+
if (projectConfig && projectConfig.repo.kind !== "http") {
|
|
15536
15851
|
targets.push({
|
|
15537
15852
|
repoPath: projectConfig.repo.localPath,
|
|
15538
15853
|
username: projectConfig.username,
|
|
@@ -15548,7 +15863,13 @@ async function pull(options) {
|
|
|
15548
15863
|
targets.push({
|
|
15549
15864
|
repoPath: activeUserConfig.repo.localPath,
|
|
15550
15865
|
username: activeUserConfig.username,
|
|
15551
|
-
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
|
+
}
|
|
15552
15873
|
});
|
|
15553
15874
|
}
|
|
15554
15875
|
const eventCount = (await readUsageEvents2()).length;
|
|
@@ -17280,9 +17601,13 @@ async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, te
|
|
|
17280
17601
|
res.hookFiles.push({ path: settingsPath, tool });
|
|
17281
17602
|
}
|
|
17282
17603
|
} else {
|
|
17283
|
-
const
|
|
17284
|
-
|
|
17285
|
-
|
|
17604
|
+
const defaultHooksDir = path56.join(baseDir, `.${tool}`, "hooks");
|
|
17605
|
+
const resolvedHooksDir = resolveOpenClawHooksDir(tool);
|
|
17606
|
+
const dirsToCheck = /* @__PURE__ */ new Set([defaultHooksDir, resolvedHooksDir]);
|
|
17607
|
+
for (const hooksDir of dirsToCheck) {
|
|
17608
|
+
if (await pathExists(path56.join(hooksDir, OPENCLAW_HOOK_DIR))) {
|
|
17609
|
+
res.openclawHookDirs.push({ hooksDir, tool });
|
|
17610
|
+
}
|
|
17286
17611
|
}
|
|
17287
17612
|
}
|
|
17288
17613
|
if (toolPath.claudemd) {
|
|
@@ -20784,15 +21109,31 @@ async function contributeSelf(localConfig, content, options) {
|
|
|
20784
21109
|
await ensureDir(path69.join(wtRepo, "learnings"));
|
|
20785
21110
|
await fs24.promises.writeFile(path69.join(wtRepo, relPath), content, "utf-8");
|
|
20786
21111
|
try {
|
|
21112
|
+
const { pathExists: pathExists3 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
20787
21113
|
const wtLearnings = path69.join(wtRepo, "learnings");
|
|
20788
21114
|
await fse11.copy(wtLearnings, LEARNINGS_LOCAL_DIR, {
|
|
20789
21115
|
overwrite: true,
|
|
20790
21116
|
filter: (src) => !path69.basename(src).startsWith(".")
|
|
20791
21117
|
});
|
|
21118
|
+
const repoPath = localConfig.repo.localPath;
|
|
21119
|
+
const docsDir = path69.join(repoPath, "docs");
|
|
21120
|
+
const rulesDir = path69.join(repoPath, "rules");
|
|
21121
|
+
const skillsDir = path69.join(repoPath, "skills");
|
|
21122
|
+
let votesDir;
|
|
21123
|
+
try {
|
|
21124
|
+
const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
21125
|
+
const candidate = path69.join(await ensureReportsWorktree2(localConfig), "votes");
|
|
21126
|
+
if (await pathExists3(candidate)) votesDir = candidate;
|
|
21127
|
+
} catch {
|
|
21128
|
+
}
|
|
20792
21129
|
const teamaiHome = getTeamaiHome(localConfig.scope, localConfig.projectRoot);
|
|
20793
21130
|
const { buildIndex: buildIndex2 } = await Promise.resolve().then(() => (init_search_index(), search_index_exports));
|
|
20794
21131
|
await buildIndex2({
|
|
20795
|
-
learningsDir: LEARNINGS_LOCAL_DIR,
|
|
21132
|
+
learningsDir: await pathExists3(LEARNINGS_LOCAL_DIR) ? LEARNINGS_LOCAL_DIR : void 0,
|
|
21133
|
+
docsDir: await pathExists3(docsDir) ? docsDir : void 0,
|
|
21134
|
+
rulesDir: await pathExists3(rulesDir) ? rulesDir : void 0,
|
|
21135
|
+
skillsDir: await pathExists3(skillsDir) ? skillsDir : void 0,
|
|
21136
|
+
votesDir,
|
|
20796
21137
|
indexPath: path69.join(teamaiHome, "search-index.json")
|
|
20797
21138
|
});
|
|
20798
21139
|
} catch (e) {
|
|
@@ -21568,7 +21909,7 @@ function formatResults(results) {
|
|
|
21568
21909
|
lines.push(`--- [teamai:recall:start] --- (${results.length} result${results.length !== 1 ? "s" : ""})`);
|
|
21569
21910
|
lines.push("");
|
|
21570
21911
|
for (let i = 0; i < results.length; i++) {
|
|
21571
|
-
const { entry, score, scope, learningsBase, sources } = results[i];
|
|
21912
|
+
const { entry, score, scope, learningsBase, sources, matchedTerms, missingTerms } = results[i];
|
|
21572
21913
|
const voteStr = entry.votes > 0 ? ` \u2605${entry.votes}` : "";
|
|
21573
21914
|
const scopeStr = scope ? ` [${scope}]` : "";
|
|
21574
21915
|
const typeTag = entry.type ? `[${entry.type}] ` : "";
|
|
@@ -21577,6 +21918,10 @@ function formatResults(results) {
|
|
|
21577
21918
|
if (entry.tags.length > 0) {
|
|
21578
21919
|
lines.push(`Tags: ${entry.tags.join(", ")}`);
|
|
21579
21920
|
}
|
|
21921
|
+
if (missingTerms && missingTerms.length > 0) {
|
|
21922
|
+
const matchedStr = matchedTerms && matchedTerms.length > 0 ? matchedTerms.join(", ") : "none";
|
|
21923
|
+
lines.push(`Matched: ${matchedStr} | Missing: ${missingTerms.join(", ")}`);
|
|
21924
|
+
}
|
|
21580
21925
|
const filePath = entry.path ? entry.path : learningsBase ? `${learningsBase}/${entry.filename}` : `~/.teamai/learnings/${entry.filename}`;
|
|
21581
21926
|
lines.push(`File: ${filePath}`);
|
|
21582
21927
|
if (sources && sources.length > 0) {
|
|
@@ -21671,8 +22016,16 @@ async function recall(query, options) {
|
|
|
21671
22016
|
const rounded = Math.round(score * 10) / 10;
|
|
21672
22017
|
const verdict = isRelevantScore(score, isCodebaseHit, baseline) ? "RELEVANT" : "NOT_RELEVANT";
|
|
21673
22018
|
let line = `${verdict} score=${rounded.toFixed(1)}`;
|
|
22019
|
+
const cutoff = isCodebaseHit ? CODEBASE_RELEVANCE_THRESHOLD : Math.max((baseline > 0 ? baseline : 1) * LEARNINGS_RELEVANCE_RATIO, LEARNINGS_ABSOLUTE_FLOOR);
|
|
22020
|
+
line += ` threshold=${(Math.round(cutoff * 10) / 10).toFixed(1)}`;
|
|
21674
22021
|
if (verdict === "RELEVANT" && topResult) {
|
|
21675
22022
|
line += ` title="${topResult.entry.title}"`;
|
|
22023
|
+
if (topResult.matchedTerms && topResult.matchedTerms.length > 0) {
|
|
22024
|
+
line += ` matched=${topResult.matchedTerms.join(",")}`;
|
|
22025
|
+
}
|
|
22026
|
+
if (topResult.missingTerms && topResult.missingTerms.length > 0) {
|
|
22027
|
+
line += ` missing=${topResult.missingTerms.join(",")}`;
|
|
22028
|
+
}
|
|
21676
22029
|
if (topResult.sources && topResult.sources.length > 0) {
|
|
21677
22030
|
const srcStr = topResult.sources.map((s) => s.desc ? `${s.path}(${s.desc})` : s.path).join(",");
|
|
21678
22031
|
line += ` sources=${srcStr}`;
|
|
@@ -21964,10 +22317,10 @@ var init_recall_toggle = __esm({
|
|
|
21964
22317
|
|
|
21965
22318
|
// src/utils/cache-index.ts
|
|
21966
22319
|
import path75 from "path";
|
|
21967
|
-
import
|
|
22320
|
+
import os5 from "os";
|
|
21968
22321
|
import fs25 from "fs-extra";
|
|
21969
22322
|
function getCacheRoot() {
|
|
21970
|
-
return process.env.TEAMAI_CACHE_DIR ?? path75.join(
|
|
22323
|
+
return process.env.TEAMAI_CACHE_DIR ?? path75.join(os5.homedir(), ".teamai", "cache", "repos");
|
|
21971
22324
|
}
|
|
21972
22325
|
function buildKey(provider, owner, repo) {
|
|
21973
22326
|
return `${provider}/${owner}/${repo}`;
|
|
@@ -25913,6 +26266,23 @@ function parseJSON(raw) {
|
|
|
25913
26266
|
return null;
|
|
25914
26267
|
}
|
|
25915
26268
|
}
|
|
26269
|
+
function resolveImportToModule(importerFile, importPath) {
|
|
26270
|
+
if (importPath.startsWith(".")) {
|
|
26271
|
+
const importerDir = path86.dirname(importerFile);
|
|
26272
|
+
const resolved = path86.normalize(path86.join(importerDir, importPath));
|
|
26273
|
+
const topLevel = resolved.split("/")[0];
|
|
26274
|
+
if (!topLevel || topLevel === ".." || topLevel === ".") return void 0;
|
|
26275
|
+
return topLevel;
|
|
26276
|
+
}
|
|
26277
|
+
if (importPath.includes(".") && !importPath.includes("/")) {
|
|
26278
|
+
return importPath.split(".")[0];
|
|
26279
|
+
}
|
|
26280
|
+
const parts = importPath.split("/");
|
|
26281
|
+
const first = parts[0];
|
|
26282
|
+
if (!first) return void 0;
|
|
26283
|
+
if (first.startsWith("@")) return void 0;
|
|
26284
|
+
return first;
|
|
26285
|
+
}
|
|
25916
26286
|
async function enrichWithAI(ctx) {
|
|
25917
26287
|
const moduleEntries = [...ctx.modules.entries()].filter(([, facts]) => facts.length >= 5);
|
|
25918
26288
|
if (moduleEntries.length === 0) {
|
|
@@ -25976,9 +26346,9 @@ async function enrichWithAI(ctx) {
|
|
|
25976
26346
|
const moduleImports = ctx.facts.filter((f) => f.kind === "relation" && f.file.startsWith(name + "/"));
|
|
25977
26347
|
const targetModules = /* @__PURE__ */ new Set();
|
|
25978
26348
|
for (const imp of moduleImports) {
|
|
25979
|
-
const
|
|
25980
|
-
if (
|
|
25981
|
-
targetModules.add(
|
|
26349
|
+
const resolved = resolveImportToModule(imp.file, imp.name);
|
|
26350
|
+
if (resolved && resolved !== name) {
|
|
26351
|
+
targetModules.add(resolved);
|
|
25982
26352
|
}
|
|
25983
26353
|
}
|
|
25984
26354
|
for (const target of targetModules) {
|
|
@@ -26465,7 +26835,19 @@ async function extractCodebase(opts) {
|
|
|
26465
26835
|
let callChains;
|
|
26466
26836
|
const depPathsFile = path87.join(evidenceDir, "dependency-paths.md");
|
|
26467
26837
|
if (changedFiles) {
|
|
26468
|
-
|
|
26838
|
+
let reused = false;
|
|
26839
|
+
try {
|
|
26840
|
+
const existing = await readFile8(depPathsFile, "utf-8");
|
|
26841
|
+
if (existing.trim()) {
|
|
26842
|
+
reused = true;
|
|
26843
|
+
}
|
|
26844
|
+
} catch {
|
|
26845
|
+
}
|
|
26846
|
+
if (reused) {
|
|
26847
|
+
callChains = [];
|
|
26848
|
+
} else {
|
|
26849
|
+
callChains = traceCallChains(facts, files);
|
|
26850
|
+
}
|
|
26469
26851
|
} else {
|
|
26470
26852
|
callChains = traceCallChains(facts, files);
|
|
26471
26853
|
}
|
|
@@ -26750,15 +27132,16 @@ async function shallowClone(url, localPath, provider, opts) {
|
|
|
26750
27132
|
log.debug(`shallowClone: \u4F7F\u7528\u533F\u540D HTTPS \u514B\u9686 github \u4ED3\u5E93`);
|
|
26751
27133
|
}
|
|
26752
27134
|
} else if (provider === "tgit") {
|
|
26753
|
-
const
|
|
26754
|
-
|
|
26755
|
-
if (
|
|
26756
|
-
cloneUrl =
|
|
27135
|
+
const httpsUrl = url.replace(/^http:\/\//, "https://");
|
|
27136
|
+
const authed = tgitGitCloneUrl(httpsUrl);
|
|
27137
|
+
if (authed) {
|
|
27138
|
+
cloneUrl = authed;
|
|
26757
27139
|
cloneMethod = "https-token";
|
|
26758
|
-
log.debug(`shallowClone: \u4F7F\u7528 HTTPS+token \u514B\u9686 tgit \u4ED3\u5E93`);
|
|
27140
|
+
log.debug(`shallowClone: \u4F7F\u7528 HTTPS+OAuth token \u514B\u9686 tgit \u4ED3\u5E93`);
|
|
26759
27141
|
} else {
|
|
27142
|
+
cloneUrl = httpsUrl;
|
|
26760
27143
|
cloneMethod = "https-anonymous";
|
|
26761
|
-
log.debug(`shallowClone: \u65E0 TGit token\uFF0C\u5C1D\u8BD5\u533F\u540D HTTPS \u514B\u9686`);
|
|
27144
|
+
log.debug(`shallowClone: \u65E0 TGit OAuth token\uFF0C\u5C1D\u8BD5\u533F\u540D HTTPS \u514B\u9686`);
|
|
26762
27145
|
}
|
|
26763
27146
|
} else {
|
|
26764
27147
|
cloneUrl = url.replace(/^http:\/\//, "https://");
|
|
@@ -26826,10 +27209,10 @@ __export(repo_cache_exports, {
|
|
|
26826
27209
|
writeLastSync: () => writeLastSync
|
|
26827
27210
|
});
|
|
26828
27211
|
import path88 from "path";
|
|
26829
|
-
import
|
|
27212
|
+
import os6 from "os";
|
|
26830
27213
|
import fs31 from "fs-extra";
|
|
26831
27214
|
function getCacheRoot2() {
|
|
26832
|
-
return process.env.TEAMAI_CACHE_DIR ?? path88.join(
|
|
27215
|
+
return process.env.TEAMAI_CACHE_DIR ?? path88.join(os6.homedir(), ".teamai", "cache", "repos");
|
|
26833
27216
|
}
|
|
26834
27217
|
function getRepoCacheDir(provider, owner, repo) {
|
|
26835
27218
|
return path88.join(getCacheRoot2(), provider, owner, repo);
|
|
@@ -28884,7 +29267,7 @@ __export(import_exports, {
|
|
|
28884
29267
|
importCmd: () => importCmd
|
|
28885
29268
|
});
|
|
28886
29269
|
import path98 from "path";
|
|
28887
|
-
import
|
|
29270
|
+
import os7 from "os";
|
|
28888
29271
|
import fs38 from "fs-extra";
|
|
28889
29272
|
import { Listr, PRESET_TIMER } from "listr2";
|
|
28890
29273
|
async function importCmd(opts) {
|
|
@@ -29078,7 +29461,7 @@ async function importCmd(opts) {
|
|
|
29078
29461
|
log.success(`Local directory ${slug} import complete (dry-run)`);
|
|
29079
29462
|
return;
|
|
29080
29463
|
}
|
|
29081
|
-
const tmpExtractDir = await fs38.mkdtemp(path98.join(
|
|
29464
|
+
const tmpExtractDir = await fs38.mkdtemp(path98.join(os7.tmpdir(), "teamai-extract-"));
|
|
29082
29465
|
try {
|
|
29083
29466
|
const { extractCodebase: extractCodebase2 } = await Promise.resolve().then(() => (init_codebase_extract(), codebase_extract_exports));
|
|
29084
29467
|
await extractCodebase2({
|