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 +6 -1
- package/index.js +64 -30
- package/package.json +1 -1
- package/scripts/claude-code/cozeloop_hook.py +4 -9
- package/scripts/codex/cozeloop_hook.py +4 -9
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
|
-
|
|
4530
|
-
|
|
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 (
|
|
4533
|
-
existing.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
|
-
|
|
4541
|
-
|
|
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 (
|
|
4544
|
-
existing.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
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
envLines.push(shellEnvLine('
|
|
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
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
envLines.push(shellEnvLine('
|
|
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
|
-
//
|
|
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: 云端模式,从环境变量读取
|
|
5403
|
-
|
|
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
|
|
5437
|
+
'云端 sandbox 应在进程环境中注入可用于 trace ingest 的 token。',
|
|
5409
5438
|
'未检测到该变量,无法配置 trace 上报。',
|
|
5410
5439
|
]);
|
|
5411
5440
|
}
|
|
5412
|
-
|
|
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
|
@@ -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
|
|
276
|
-
return env_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
|
|
237
|
-
return env_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
|
|