coze_lab 0.1.13 → 0.1.14

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.
Files changed (2) hide show
  1. package/index.js +93 -44
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -4622,19 +4622,107 @@ function writeCodexHook(token, workspaceId, pythonCmd, codexHome, cloud) {
4622
4622
  // agentId 非空时并入 plugins.entries[...].config.traceAgentIds allowlist —— 插件运行时
4623
4623
  // 用 resolveAgentIdFromHookCtx 取当前 agentId,仅 allowlist 内的 agent 才上报 trace。
4624
4624
  // allowlist 为空(本地全局模式)= 全部放行,向后兼容。
4625
+ // resolveHomeDir 解析 home 目录。云端 SandboxShellExec 执行环境的 $HOME 可能缺失/不一致,
4626
+ // 导致 os.homedir() 解析不到真实 home(如 /root)。云端模式下做兜底探测。
4627
+ function resolveHomeDir(cloud) {
4628
+ const h = os.homedir();
4629
+ if (!cloud) return h;
4630
+ // 优先 $HOME,其次 os.homedir(),再回退云端常见 root home。
4631
+ const candidates = [process.env.HOME, h, '/root'].filter(Boolean);
4632
+ for (const c of candidates) {
4633
+ try {
4634
+ if (fs.existsSync(path.join(c, '.coze')) || fs.existsSync(path.join(c, '.openclaw'))) {
4635
+ return c;
4636
+ }
4637
+ } catch { /* ignore */ }
4638
+ }
4639
+ return h || '/root';
4640
+ }
4641
+
4642
+ function normalizeTraceAgentIds(ids) {
4643
+ return (Array.isArray(ids) ? ids : [])
4644
+ .map((s) => String(s).trim().toLowerCase())
4645
+ .filter(Boolean);
4646
+ }
4647
+
4648
+ function getOpenClawEndpoint(cloud) {
4649
+ return (cloud && process.env.COZELOOP_API_BASE_URL)
4650
+ ? process.env.COZELOOP_API_BASE_URL.replace(/\/+$/, '') + '/v1/loop/opentelemetry'
4651
+ : 'https://api.coze.cn/v1/loop/opentelemetry';
4652
+ }
4653
+
4654
+ function applyOpenClawPluginConfig(existing, token, workspaceId, agentId, cloud) {
4655
+ if (!existing.plugins) existing.plugins = {};
4656
+ if (!existing.plugins.allow) existing.plugins.allow = [];
4657
+ if (!existing.plugins.entries) existing.plugins.entries = {};
4658
+
4659
+ const PLUGIN = 'openclaw-cozeloop-trace';
4660
+ if (!existing.plugins.allow.includes(PLUGIN)) {
4661
+ existing.plugins.allow.push(PLUGIN);
4662
+ }
4663
+ // Preserve existing entry structure, only update config.
4664
+ if (!existing.plugins.entries[PLUGIN]) {
4665
+ existing.plugins.entries[PLUGIN] = { enabled: true };
4666
+ }
4667
+ existing.plugins.entries[PLUGIN].enabled = true;
4668
+ // hooks.allowConversationAccess required for 2026.5+ to access session content.
4669
+ existing.plugins.entries[PLUGIN].hooks = { allowConversationAccess: true };
4670
+ if (!existing.plugins.entries[PLUGIN].config) existing.plugins.entries[PLUGIN].config = {};
4671
+ const pcfg = existing.plugins.entries[PLUGIN].config;
4672
+ pcfg.authorization = `Bearer ${token}`;
4673
+ pcfg.endpoint = getOpenClawEndpoint(cloud);
4674
+ pcfg.workspaceId = workspaceId;
4675
+ pcfg.debug = true;
4676
+ // per-agent trace 放行:把当前 agentId 并入 traceAgentIds(去重、归一为小写,
4677
+ // 与插件侧 resolveAgentIdFromHookCtx 的归一一致)。无 agentId(全局模式)则不动
4678
+ // allowlist —— 空 allowlist 表示全部放行。
4679
+ if (agentId) {
4680
+ const norm = String(agentId).trim().toLowerCase();
4681
+ const list = normalizeTraceAgentIds(pcfg.traceAgentIds);
4682
+ if (norm && !list.includes(norm)) list.push(norm);
4683
+ pcfg.traceAgentIds = list;
4684
+ }
4685
+ return existing;
4686
+ }
4687
+
4688
+ function isOpenClawAlreadyInjected(configPath, pluginDir, token, workspaceId, agentId, cloud) {
4689
+ if (!fs.existsSync(pluginDir)) return false;
4690
+ let existing;
4691
+ try {
4692
+ existing = JSON.parse(fs.readFileSync(configPath, 'utf8'));
4693
+ } catch {
4694
+ return false;
4695
+ }
4696
+ const desired = applyOpenClawPluginConfig(
4697
+ JSON.parse(JSON.stringify(existing)),
4698
+ token,
4699
+ workspaceId,
4700
+ agentId,
4701
+ cloud,
4702
+ );
4703
+ return JSON.stringify(existing) === JSON.stringify(desired);
4704
+ }
4705
+
4625
4706
  function writeOpenClawHook(token, workspaceId, agentId, cloud) {
4626
- const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
4627
- const pluginDir = path.join(os.homedir(), '.cozeloop', 'openclaw-plugin');
4707
+ const home = resolveHomeDir(cloud);
4708
+ const configPath = path.join(home, '.openclaw', 'openclaw.json');
4709
+ const pluginDir = path.join(home, '.cozeloop', 'openclaw-plugin');
4628
4710
 
4629
4711
  if (!fs.existsSync(configPath)) {
4630
4712
  errorBox([
4631
- 'ERROR: ~/.openclaw/openclaw.json not found',
4713
+ `ERROR: openclaw.json not found at ${configPath}`,
4632
4714
  '',
4633
4715
  'Make sure OpenClaw is installed and has been run at least once.',
4634
- 'Install: npm install -g openclaw',
4716
+ `(home=${home}, $HOME=${process.env.HOME || 'unset'})`,
4635
4717
  ]);
4636
4718
  }
4637
4719
 
4720
+ if (isOpenClawAlreadyInjected(configPath, pluginDir, token, workspaceId, agentId, cloud)) {
4721
+ ok(`OpenClaw plugin already configured in ${configPath}`);
4722
+ info('OpenClaw gateway restart skipped (configuration unchanged).');
4723
+ return { configPath, pluginDir, unchanged: true };
4724
+ }
4725
+
4638
4726
  // 1. Write plugin files to ~/.cozeloop/openclaw-plugin/
4639
4727
  ensureDir(pluginDir);
4640
4728
  ensureDir(path.join(pluginDir, 'dist'));
@@ -4685,46 +4773,7 @@ function writeOpenClawHook(token, workspaceId, agentId, cloud) {
4685
4773
 
4686
4774
  // 4. Update openclaw.json with token and workspace
4687
4775
  const config = mergeJson(configPath, (existing) => {
4688
- if (!existing.plugins) existing.plugins = {};
4689
- if (!existing.plugins.allow) existing.plugins.allow = [];
4690
- if (!existing.plugins.entries) existing.plugins.entries = {};
4691
-
4692
- const PLUGIN = 'openclaw-cozeloop-trace';
4693
- if (!existing.plugins.allow.includes(PLUGIN)) {
4694
- existing.plugins.allow.push(PLUGIN);
4695
- }
4696
- // Preserve existing entry structure, only update config
4697
- if (!existing.plugins.entries[PLUGIN]) {
4698
- existing.plugins.entries[PLUGIN] = { enabled: true };
4699
- }
4700
- existing.plugins.entries[PLUGIN].enabled = true;
4701
- // hooks.allowConversationAccess required for 2026.5+ to access session content
4702
- existing.plugins.entries[PLUGIN].hooks = { allowConversationAccess: true };
4703
- if (!existing.plugins.entries[PLUGIN].config) existing.plugins.entries[PLUGIN].config = {};
4704
- const pcfg = existing.plugins.entries[PLUGIN].config;
4705
- pcfg.authorization = `Bearer ${token}`;
4706
- // 云端:endpoint 走 sandbox 注入的 COZELOOP_API_BASE_URL 代理(token 经它鉴权);
4707
- // 缺省回退 api.coze.cn 直连。插件 exporter 会在此基础上拼 /v1/traces。
4708
- const ocBase = (cloud && process.env.COZELOOP_API_BASE_URL)
4709
- ? process.env.COZELOOP_API_BASE_URL.replace(/\/+$/, '') + '/v1/loop/opentelemetry'
4710
- : 'https://api.coze.cn/v1/loop/opentelemetry';
4711
- pcfg.endpoint = ocBase;
4712
- pcfg.workspaceId = workspaceId;
4713
- pcfg.debug = true;
4714
- // per-agent trace 放行:把当前 agentId 并入 traceAgentIds(去重、归一为小写,
4715
- // 与插件侧 resolveAgentIdFromHookCtx 的归一一致)。无 agentId(全局模式)则不动
4716
- // allowlist —— 空 allowlist 表示全部放行。
4717
- if (agentId) {
4718
- const norm = String(agentId).trim().toLowerCase();
4719
- // 读 existing 时一并归一(小写、去空),与插件侧 resolveAgentIdFromHookCtx 一致,
4720
- // 防手工编辑混入大写条目导致去重失效。
4721
- const list = (Array.isArray(pcfg.traceAgentIds) ? pcfg.traceAgentIds : [])
4722
- .map((s) => String(s).trim().toLowerCase())
4723
- .filter(Boolean);
4724
- if (norm && !list.includes(norm)) list.push(norm);
4725
- pcfg.traceAgentIds = list;
4726
- }
4727
- return existing;
4776
+ return applyOpenClawPluginConfig(existing, token, workspaceId, agentId, cloud);
4728
4777
  });
4729
4778
 
4730
4779
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coze_lab",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Configure local AI agents (Claude Code, Codex, OpenClaw) to report traces to CozeLoop",
5
5
  "keywords": [
6
6
  "cozeloop",