coze_lab 0.1.12 → 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 +102 -45
  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
- function writeOpenClawHook(token, workspaceId, agentId) {
4626
- const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
4627
- const pluginDir = path.join(os.homedir(), '.cozeloop', 'openclaw-plugin');
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
+
4706
+ function writeOpenClawHook(token, workspaceId, agentId, cloud) {
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,41 +4773,7 @@ function writeOpenClawHook(token, workspaceId, agentId) {
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
- pcfg.endpoint = 'https://api.coze.cn/v1/loop/opentelemetry';
4707
- pcfg.workspaceId = workspaceId;
4708
- pcfg.debug = true;
4709
- // per-agent trace 放行:把当前 agentId 并入 traceAgentIds(去重、归一为小写,
4710
- // 与插件侧 resolveAgentIdFromHookCtx 的归一一致)。无 agentId(全局模式)则不动
4711
- // allowlist —— 空 allowlist 表示全部放行。
4712
- if (agentId) {
4713
- const norm = String(agentId).trim().toLowerCase();
4714
- // 读 existing 时一并归一(小写、去空),与插件侧 resolveAgentIdFromHookCtx 一致,
4715
- // 防手工编辑混入大写条目导致去重失效。
4716
- const list = (Array.isArray(pcfg.traceAgentIds) ? pcfg.traceAgentIds : [])
4717
- .map((s) => String(s).trim().toLowerCase())
4718
- .filter(Boolean);
4719
- if (norm && !list.includes(norm)) list.push(norm);
4720
- pcfg.traceAgentIds = list;
4721
- }
4722
- return existing;
4776
+ return applyOpenClawPluginConfig(existing, token, workspaceId, agentId, cloud);
4723
4777
  });
4724
4778
 
4725
4779
  try {
@@ -4772,7 +4826,7 @@ function httpsPost(url, body, extraHeaders) {
4772
4826
  // 只看 HTTP 状态码(2xx=通),不回查 trace 是否落库——回查由外部查询方完成。
4773
4827
  // pairCode 写进 span 的 pair_code attribute,供查询方按该字段过滤回查;缺省自动生成。
4774
4828
  // 不在函数内退出,退出行为交给调用方(主流程 Step 6 / 独立命令 --verify)。
4775
- async function verifyTraceReport(token, workspaceId, pairCode) {
4829
+ async function verifyTraceReport(token, workspaceId, pairCode, baseUrl) {
4776
4830
  const traceId = crypto.randomBytes(16).toString('hex'); // 32 hex chars
4777
4831
  const spanId = crypto.randomBytes(8).toString('hex'); // 16 hex chars
4778
4832
  const nowNs = String(Date.now() * 1_000_000); // OTLP 要求纳秒 unix 时间(字符串)
@@ -4805,9 +4859,12 @@ async function verifyTraceReport(token, workspaceId, pairCode) {
4805
4859
  };
4806
4860
 
4807
4861
  let res;
4862
+ // 云端:上报走 sandbox 注入的 COZELOOP_API_BASE_URL 代理(vefaas token 经它鉴权转换);
4863
+ // 缺省回退到 api.coze.cn 直连。base 已去尾部斜杠。
4864
+ const apiBase = (baseUrl || COZE_API).replace(/\/+$/, '');
4808
4865
  try {
4809
4866
  res = await httpsPost(
4810
- `${COZE_API}/v1/loop/opentelemetry/v1/traces`,
4867
+ `${apiBase}/v1/loop/opentelemetry/v1/traces`,
4811
4868
  otlpBody,
4812
4869
  { Authorization: `Bearer ${token}`, 'cozeloop-workspace-id': workspaceId },
4813
4870
  );
@@ -5158,7 +5215,7 @@ async function main() {
5158
5215
  info('验证 trace 上报链路...');
5159
5216
  const token = await getValidToken(); // 无凭证会自动走登录/刷新
5160
5217
  console.log('');
5161
- const result = await verifyTraceReport(token, WORKSPACE_ID, args.pairCode);
5218
+ const result = await verifyTraceReport(token, WORKSPACE_ID, args.pairCode, process.env.COZELOOP_API_BASE_URL || undefined);
5162
5219
  process.exit(result.success ? 0 : 1);
5163
5220
  }
5164
5221
 
@@ -5239,7 +5296,7 @@ async function main() {
5239
5296
  written = writeCodexHook(token, WORKSPACE_ID, pythonCmd, codexHome, args.cloud);
5240
5297
  } else {
5241
5298
  // openclaw:云端用 traceAgentIds allowlist 做 per-agent 放行。
5242
- written = writeOpenClawHook(token, WORKSPACE_ID, args.agentId) || {};
5299
+ written = writeOpenClawHook(token, WORKSPACE_ID, args.agentId, args.cloud) || {};
5243
5300
  }
5244
5301
  // 走到这里说明 detectAgent / 环境检查 / 写 hook 配置全部成功 → 注入成功。
5245
5302
  cloudResult.inject = 'ok';
@@ -5276,7 +5333,7 @@ async function main() {
5276
5333
 
5277
5334
  // Step 5: Verify trace reporting end-to-end
5278
5335
  info('Step 5/5: 验证 trace 上报链路...');
5279
- const verifyResult = await verifyTraceReport(token, WORKSPACE_ID, args.pairCode);
5336
+ const verifyResult = await verifyTraceReport(token, WORKSPACE_ID, args.pairCode, args.cloud ? process.env.COZELOOP_API_BASE_URL : undefined);
5280
5337
  if (verifyResult.success) {
5281
5338
  cloudResult.verify = 'ok';
5282
5339
  } else if (CLOUD_MODE) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coze_lab",
3
- "version": "0.1.12",
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",