coze_lab 0.1.22 → 0.1.24

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 CHANGED
@@ -24,7 +24,7 @@ npx coze_lab --logout # Clear cached credentials
24
24
  |-----------|----------|-----------------|
25
25
  | `--agent` | ✓ (for setup) | `claude-code`, `codex`, `openclaw` |
26
26
  | `--agent-id` | — | Resolve `~/.coze/agents/<agentId>/config.json` and write per-agent config |
27
- | `--cloud` | — | Cloud mode: read token from env and emit `COZE_LAB_RESULT=...` |
27
+ | `--cloud` | — | Cloud mode: read trace token from env and emit `COZE_LAB_RESULT=...` |
28
28
  | `--codex-home` | — | Override Codex config home for non-cloud/custom runs |
29
29
  | `--login` | — | Run the Device Code login flow only |
30
30
  | `--status` | — | Print local token status (valid / expiring / expired) |
@@ -70,6 +70,11 @@ created after a new Codex turn, Codex did not load or execute the hook.
70
70
 
71
71
  OAuth tokens are stored in `~/.cozeloop/credentials.json` (mode 600).
72
72
 
73
+ In cloud mode, trace verification and hook uploads prefer
74
+ `COZELOOP_API_TOKEN` and fall back to `COZE_API_TOKEN`. The selfcheck result is
75
+ authoritative: if the token does not have trace ingest permission, onboard still
76
+ writes the hook configuration but reports `verify=fail` with `token_source`.
77
+
73
78
  **At hook execution time** (Claude Code / Codex), the Python hook script automatically:
74
79
  1. Reads `~/.cozeloop/credentials.json`
75
80
  2. If the token expires in < 10 minutes, calls the Coze refresh API
package/index.js CHANGED
@@ -17,7 +17,7 @@ const REFRESH_THRESHOLD_MS = 10 * 60 * 1000;
17
17
  // 云端(--cloud)模式:在 stdout 输出一行机器可读结果 COZE_LAB_RESULT={...},
18
18
  // 供管理后台解析判定(inject/verify/logid/message),不依赖中文文案。
19
19
  let CLOUD_MODE = false;
20
- const cloudResult = { version: PACKAGE_VERSION, inject: 'skip', verify: 'skip', logid: '', message: '' };
20
+ const cloudResult = { version: PACKAGE_VERSION, inject: 'skip', verify: 'skip', logid: '', message: '', token_source: '' };
21
21
 
22
22
  // errorBox 在云端模式下抛此异常(而非 process.exit),由 main 外层统一收尾。
23
23
  class CloudAbort extends Error {}
@@ -40,6 +40,22 @@ function emitCloudResult() {
40
40
  console.log('COZE_LAB_RESULT=' + JSON.stringify(safe));
41
41
  }
42
42
 
43
+ function readEnv(name) {
44
+ return String(process.env[name] || '').trim();
45
+ }
46
+
47
+ function getCloudTokenInfo() {
48
+ const loopToken = readEnv('COZELOOP_API_TOKEN');
49
+ if (loopToken) {
50
+ return { token: loopToken, source: 'COZELOOP_API_TOKEN', traceUsable: true };
51
+ }
52
+ const cozeToken = readEnv('COZE_API_TOKEN');
53
+ if (cozeToken) {
54
+ return { token: cozeToken, source: 'COZE_API_TOKEN', traceUsable: true };
55
+ }
56
+ return { token: '', source: '', traceUsable: false };
57
+ }
58
+
43
59
  // ─── 1. Color helpers ────────────────────────────────────────────────────────
44
60
  const C = {
45
61
  reset: '\x1b[0m',
@@ -4526,22 +4542,26 @@ function writeClaudeCodeHook(patToken, workspaceId, pythonCmd, configBaseDir, cl
4526
4542
  if (!existing.env) existing.env = {};
4527
4543
  existing.env.COZELOOP_WORKSPACE_ID = workspaceId;
4528
4544
  if (cloud) {
4529
- if (process.env.COZELOOP_API_TOKEN) {
4530
- existing.env.COZELOOP_API_TOKEN = process.env.COZELOOP_API_TOKEN;
4545
+ const loopToken = readEnv('COZELOOP_API_TOKEN');
4546
+ const cozeToken = readEnv('COZE_API_TOKEN');
4547
+ if (loopToken) {
4548
+ existing.env.COZELOOP_API_TOKEN = loopToken;
4531
4549
  delete existing.env.COZE_API_TOKEN;
4532
- } else if (process.env.COZE_API_TOKEN) {
4533
- existing.env.COZE_API_TOKEN = process.env.COZE_API_TOKEN;
4550
+ } else if (cozeToken) {
4551
+ existing.env.COZE_API_TOKEN = cozeToken;
4534
4552
  delete existing.env.COZELOOP_API_TOKEN;
4535
4553
  }
4536
4554
  } else {
4537
4555
  existing.env.COZELOOP_API_TOKEN = patToken;
4538
4556
  delete existing.env.COZE_API_TOKEN;
4539
4557
  }
4540
- if (process.env.COZELOOP_API_BASE_URL) {
4541
- existing.env.COZELOOP_API_BASE_URL = process.env.COZELOOP_API_BASE_URL;
4558
+ const loopBaseUrl = readEnv('COZELOOP_API_BASE_URL');
4559
+ const otelEndpoint = readEnv('OTEL_ENDPOINT');
4560
+ if (loopBaseUrl) {
4561
+ existing.env.COZELOOP_API_BASE_URL = loopBaseUrl;
4542
4562
  delete existing.env.OTEL_ENDPOINT;
4543
- } else if (process.env.OTEL_ENDPOINT) {
4544
- existing.env.OTEL_ENDPOINT = process.env.OTEL_ENDPOINT;
4563
+ } else if (otelEndpoint) {
4564
+ existing.env.OTEL_ENDPOINT = otelEndpoint;
4545
4565
  delete existing.env.COZELOOP_API_BASE_URL;
4546
4566
  }
4547
4567
  // PPE 泳道:cozeloop SDK 读这两个环境变量,自动注入 x-tt-env / x-use-ppe header
@@ -4584,10 +4604,12 @@ function writeCodexHook(token, workspaceId, pythonCmd, codexHome, cloud) {
4584
4604
  shellEnvLine('COZELOOP_WORKSPACE_ID', workspaceId),
4585
4605
  ];
4586
4606
  if (cloud) {
4587
- if (process.env.COZELOOP_API_TOKEN) {
4588
- envLines.push(shellEnvLine('COZELOOP_API_TOKEN', process.env.COZELOOP_API_TOKEN));
4589
- } else if (process.env.COZE_API_TOKEN) {
4590
- envLines.push(shellEnvLine('COZE_API_TOKEN', process.env.COZE_API_TOKEN));
4607
+ const loopToken = readEnv('COZELOOP_API_TOKEN');
4608
+ const cozeToken = readEnv('COZE_API_TOKEN');
4609
+ if (loopToken) {
4610
+ envLines.push(shellEnvLine('COZELOOP_API_TOKEN', loopToken));
4611
+ } else if (cozeToken) {
4612
+ envLines.push(shellEnvLine('COZE_API_TOKEN', cozeToken));
4591
4613
  }
4592
4614
  } else {
4593
4615
  envLines.push(shellEnvLine('COZELOOP_API_TOKEN', token));
@@ -4595,10 +4617,12 @@ function writeCodexHook(token, workspaceId, pythonCmd, codexHome, cloud) {
4595
4617
  envLines.push(shellEnvLine('CODEX_HOME', home));
4596
4618
  envLines.push(shellEnvLine('COZELOOP_HOOK_LOG', logFile));
4597
4619
  envLines.push('TRACE_TO_COZELOOP=true');
4598
- if (process.env.COZELOOP_API_BASE_URL) {
4599
- envLines.push(shellEnvLine('COZELOOP_API_BASE_URL', process.env.COZELOOP_API_BASE_URL));
4600
- } else if (process.env.OTEL_ENDPOINT) {
4601
- envLines.push(shellEnvLine('OTEL_ENDPOINT', process.env.OTEL_ENDPOINT));
4620
+ const loopBaseUrl = readEnv('COZELOOP_API_BASE_URL');
4621
+ const otelEndpoint = readEnv('OTEL_ENDPOINT');
4622
+ if (loopBaseUrl) {
4623
+ envLines.push(shellEnvLine('COZELOOP_API_BASE_URL', loopBaseUrl));
4624
+ } else if (otelEndpoint) {
4625
+ envLines.push(shellEnvLine('OTEL_ENDPOINT', otelEndpoint));
4602
4626
  }
4603
4627
  // PPE 泳道:cozeloop SDK 读这两个环境变量,自动注入 x-tt-env / x-use-ppe header
4604
4628
  envLines.push(shellEnvLine('x_tt_env', PPE_TT_ENV));
@@ -4909,8 +4933,9 @@ function runCommand(input) {
4909
4933
  });
4910
4934
  }
4911
4935
 
4912
- async function verifyTraceReportViaSdk(token, workspaceId, pairCode, pythonCmd) {
4936
+ async function verifyTraceReportViaSdk(token, workspaceId, pairCode, pythonCmd, tokenSource) {
4913
4937
  const pair = pairCode || crypto.randomBytes(6).toString('hex');
4938
+ const apiBase = getCozeloopApiBaseUrl(true);
4914
4939
  const script = `
4915
4940
  import json
4916
4941
  import os
@@ -4941,11 +4966,11 @@ try:
4941
4966
  client.flush()
4942
4967
  client.close()
4943
4968
  if events:
4944
- print(json.dumps({"success": False, "body": "\\n".join(events)}, ensure_ascii=False))
4969
+ print(json.dumps({"success": False, "body": "\\n".join(events), "api_base_url": os.environ.get("COZELOOP_API_BASE_URL", ""), "token_source": os.environ.get("COZELOOP_TOKEN_SOURCE", "")}, ensure_ascii=False))
4945
4970
  sys.exit(1)
4946
- print(json.dumps({"success": True, "body": ""}, ensure_ascii=False))
4971
+ print(json.dumps({"success": True, "body": "", "api_base_url": os.environ.get("COZELOOP_API_BASE_URL", ""), "token_source": os.environ.get("COZELOOP_TOKEN_SOURCE", "")}, ensure_ascii=False))
4947
4972
  except Exception as e:
4948
- print(json.dumps({"success": False, "body": str(e)}, ensure_ascii=False))
4973
+ print(json.dumps({"success": False, "body": str(e), "api_base_url": os.environ.get("COZELOOP_API_BASE_URL", ""), "token_source": os.environ.get("COZELOOP_TOKEN_SOURCE", "")}, ensure_ascii=False))
4949
4974
  sys.exit(1)
4950
4975
  `;
4951
4976
  const env = {
@@ -4953,10 +4978,10 @@ except Exception as e:
4953
4978
  COZELOOP_WORKSPACE_ID: workspaceId,
4954
4979
  COZELOOP_API_TOKEN: token,
4955
4980
  COZELOOP_PAIR_CODE: pair,
4981
+ COZELOOP_TOKEN_SOURCE: tokenSource || '',
4956
4982
  x_tt_env: PPE_TT_ENV,
4957
4983
  x_use_ppe: PPE_USE_PPE,
4958
4984
  };
4959
- const apiBase = getCozeloopApiBaseUrl(true);
4960
4985
  if (apiBase) env.COZELOOP_API_BASE_URL = apiBase;
4961
4986
  const result = await runCommand({
4962
4987
  cmd: pythonCmd || 'python3',
@@ -4976,10 +5001,11 @@ except Exception as e:
4976
5001
  info(`查询方可用 pair_code=${pair} 在 CozeLoop 回查确认该 trace 已落库。`);
4977
5002
  } else {
4978
5003
  warn(`trace 上报失败: SDK selfcheck exit ${result.code}`);
5004
+ info(`trace selfcheck api_base_url=${apiBase || COZE_API}, token_source=${tokenSource || 'unknown'}`);
4979
5005
  const snippet = String(body || '').slice(0, 300);
4980
5006
  if (snippet) console.log(snippet);
4981
5007
  }
4982
- return { success, status: result.code || 0, body, traceId: '', pairCode: pair };
5008
+ return { success, status: result.code || 0, body, traceId: '', pairCode: pair, apiBaseUrl: apiBase, tokenSource };
4983
5009
  }
4984
5010
 
4985
5011
  // 真实发一条最小 OTLP trace 到 CozeLoop,验证上报链路是否打通。
@@ -5394,25 +5420,33 @@ async function main() {
5394
5420
 
5395
5421
  // Step 1: Authorize.
5396
5422
  // 云端(--cloud):token 取自 sandbox 注入的环境变量,跳过 OAuth / credentials.json。
5397
- // 云端真实变量名是 COZELOOP_API_TOKEN(兼容历史的 COZE_API_TOKEN)。
5423
+ // 优先使用 COZELOOP_API_TOKEN;兼容使用 COZE_API_TOKEN,并以真实 selfcheck 为准。
5398
5424
  // 本地:load cached → refresh → device code。
5399
5425
  // 注意:workspace_id 始终用写死的 WORKSPACE_ID(团队固定上报 workspace),不读环境。
5400
5426
  let token;
5427
+ let tokenSource = '';
5401
5428
  if (args.cloud) {
5402
- info('Step 1/5: 云端模式,从环境变量读取 COZELOOP_API_TOKEN...');
5403
- token = process.env.COZELOOP_API_TOKEN || process.env.COZE_API_TOKEN;
5429
+ info('Step 1/5: 云端模式,从环境变量读取 trace token...');
5430
+ const tokenInfo = getCloudTokenInfo();
5431
+ token = tokenInfo.token;
5432
+ tokenSource = tokenInfo.source;
5404
5433
  if (!token) {
5405
5434
  errorBox([
5406
- 'ERROR: --cloud 模式要求环境变量 COZELOOP_API_TOKEN',
5435
+ 'ERROR: --cloud 模式要求环境变量 COZELOOP_API_TOKEN 或 COZE_API_TOKEN',
5407
5436
  '',
5408
- '云端 sandbox 应在进程环境中注入 COZELOOP_API_TOKEN(或 COZE_API_TOKEN)。',
5437
+ '云端 sandbox 应在进程环境中注入可用于 trace ingest 的 token。',
5409
5438
  '未检测到该变量,无法配置 trace 上报。',
5410
5439
  ]);
5411
5440
  }
5412
- ok('已从环境变量读取 COZELOOP_API_TOKEN');
5441
+ cloudResult.token_source = tokenSource;
5442
+ ok(`已从环境变量读取 ${tokenSource}`);
5443
+ if (tokenSource === 'COZE_API_TOKEN') {
5444
+ info('将兼容使用 COZE_API_TOKEN 作为 trace token,并通过 selfcheck 验证实际可用性。');
5445
+ }
5413
5446
  } else {
5414
5447
  info('Step 1/5: 检查授权状态...');
5415
5448
  token = await getValidToken();
5449
+ tokenSource = 'credentials';
5416
5450
  }
5417
5451
  console.log('');
5418
5452
 
@@ -5497,7 +5531,7 @@ async function main() {
5497
5531
  // Step 5: Verify trace reporting end-to-end
5498
5532
  info('Step 5/5: 验证 trace 上报链路...');
5499
5533
  const verifyResult = args.cloud
5500
- ? await verifyTraceReportViaSdk(token, WORKSPACE_ID, args.pairCode, pythonCmd || 'python3')
5534
+ ? await verifyTraceReportViaSdk(token, WORKSPACE_ID, args.pairCode, pythonCmd || 'python3', tokenSource)
5501
5535
  : await verifyTraceReport(token, WORKSPACE_ID, args.pairCode, getOtelTracesUrl(false));
5502
5536
  if (verifyResult.success) {
5503
5537
  cloudResult.verify = 'ok';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coze_lab",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "Configure local AI agents (Claude Code, Codex, OpenClaw) to report traces to CozeLoop",
5
5
  "keywords": [
6
6
  "cozeloop",
@@ -269,11 +269,12 @@ def get_api_base_url() -> str:
269
269
 
270
270
  def get_fresh_token() -> Optional[str]:
271
271
  """Return a valid access token, refreshing if needed. Falls back to env var."""
272
- api_base_url = get_api_base_url()
273
272
  env_token = os.environ.get("COZELOOP_API_TOKEN")
274
273
  env_coze_token = os.environ.get("COZE_API_TOKEN")
275
- if api_base_url and (env_token or env_coze_token):
276
- return env_token or env_coze_token
274
+ if env_token:
275
+ return env_token
276
+ if env_coze_token:
277
+ return env_coze_token
277
278
  creds = _load_credentials()
278
279
  if creds:
279
280
  expires_at_sec = creds.get("expires_at", 0) / 1000
@@ -287,12 +288,6 @@ def get_fresh_token() -> Optional[str]:
287
288
  if new_token:
288
289
  return new_token
289
290
  debug_log("Refresh failed, falling back to env var.")
290
- if env_token:
291
- return env_token
292
- if api_base_url:
293
- return env_coze_token
294
- if env_coze_token:
295
- debug_log("COZE_API_TOKEN present but ignored without COZELOOP_API_BASE_URL or OTEL_ENDPOINT")
296
291
  return None
297
292
 
298
293
  # -------------------------------------------------------------------------
@@ -230,11 +230,12 @@ def get_api_base_url() -> str:
230
230
 
231
231
 
232
232
  def get_fresh_token():
233
- api_base_url = get_api_base_url()
234
233
  env_token = os.environ.get("COZELOOP_API_TOKEN")
235
234
  env_coze_token = os.environ.get("COZE_API_TOKEN")
236
- if api_base_url and (env_token or env_coze_token):
237
- return env_token or env_coze_token
235
+ if env_token:
236
+ return env_token
237
+ if env_coze_token:
238
+ return env_coze_token
238
239
  creds = _load_credentials()
239
240
  if creds:
240
241
  remaining = creds.get("expires_at", 0) / 1000 - time.time()
@@ -244,12 +245,6 @@ def get_fresh_token():
244
245
  new_token = _refresh_token(creds["refresh_token"])
245
246
  if new_token:
246
247
  return new_token
247
- if env_token:
248
- return env_token
249
- if api_base_url:
250
- return env_coze_token
251
- if env_coze_token:
252
- hook_log("COZE_API_TOKEN present but ignored without COZELOOP_API_BASE_URL or OTEL_ENDPOINT")
253
248
  return None
254
249
  # -------------------------------------------------------------------------
255
250