coze_lab 0.1.22 → 0.1.23
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 +7 -1
- package/index.js +84 -31
- package/package.json +1 -1
- package/scripts/claude-code/cozeloop_hook.py +3 -8
- package/scripts/codex/cozeloop_hook.py +3 -8
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,12 @@ 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 require
|
|
74
|
+
`COZELOOP_API_TOKEN`. `COZE_API_TOKEN` is kept only as a legacy compatibility
|
|
75
|
+
signal for hook injection; it is not treated as a CozeLoop SDK trace ingest
|
|
76
|
+
token. If cloud only provides `COZE_API_TOKEN`, onboard still writes the hook
|
|
77
|
+
configuration but reports `verify=fail` with `token_source=COZE_API_TOKEN`.
|
|
78
|
+
|
|
73
79
|
**At hook execution time** (Claude Code / Codex), the Python hook script automatically:
|
|
74
80
|
1. Reads `~/.cozeloop/credentials.json`
|
|
75
81
|
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: false };
|
|
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,37 @@ async function main() {
|
|
|
5394
5420
|
|
|
5395
5421
|
// Step 1: Authorize.
|
|
5396
5422
|
// 云端(--cloud):token 取自 sandbox 注入的环境变量,跳过 OAuth / credentials.json。
|
|
5397
|
-
//
|
|
5423
|
+
// trace 上报需要 COZELOOP_API_TOKEN;COZE_API_TOKEN 只保留为历史兼容来源,
|
|
5424
|
+
// onboard 会写入 hook,但自检会明确标记它不是 CozeLoop ingest token。
|
|
5398
5425
|
// 本地:load cached → refresh → device code。
|
|
5399
5426
|
// 注意:workspace_id 始终用写死的 WORKSPACE_ID(团队固定上报 workspace),不读环境。
|
|
5400
5427
|
let token;
|
|
5428
|
+
let tokenSource = '';
|
|
5429
|
+
let cloudTraceUsableToken = true;
|
|
5401
5430
|
if (args.cloud) {
|
|
5402
|
-
info('Step 1/5: 云端模式,从环境变量读取
|
|
5403
|
-
|
|
5431
|
+
info('Step 1/5: 云端模式,从环境变量读取 trace token...');
|
|
5432
|
+
const tokenInfo = getCloudTokenInfo();
|
|
5433
|
+
token = tokenInfo.token;
|
|
5434
|
+
tokenSource = tokenInfo.source;
|
|
5435
|
+
cloudTraceUsableToken = tokenInfo.traceUsable;
|
|
5404
5436
|
if (!token) {
|
|
5405
5437
|
errorBox([
|
|
5406
5438
|
'ERROR: --cloud 模式要求环境变量 COZELOOP_API_TOKEN',
|
|
5407
5439
|
'',
|
|
5408
|
-
'云端 sandbox 应在进程环境中注入 COZELOOP_API_TOKEN
|
|
5440
|
+
'云端 sandbox 应在进程环境中注入 COZELOOP_API_TOKEN。',
|
|
5409
5441
|
'未检测到该变量,无法配置 trace 上报。',
|
|
5410
5442
|
]);
|
|
5411
5443
|
}
|
|
5412
|
-
|
|
5444
|
+
cloudResult.token_source = tokenSource;
|
|
5445
|
+
if (cloudTraceUsableToken) {
|
|
5446
|
+
ok(`已从环境变量读取 ${tokenSource}`);
|
|
5447
|
+
} else {
|
|
5448
|
+
warn('仅检测到 COZE_API_TOKEN;它不是 CozeLoop SDK trace ingest token,自检预计会失败。');
|
|
5449
|
+
}
|
|
5413
5450
|
} else {
|
|
5414
5451
|
info('Step 1/5: 检查授权状态...');
|
|
5415
5452
|
token = await getValidToken();
|
|
5453
|
+
tokenSource = 'credentials';
|
|
5416
5454
|
}
|
|
5417
5455
|
console.log('');
|
|
5418
5456
|
|
|
@@ -5496,9 +5534,24 @@ async function main() {
|
|
|
5496
5534
|
|
|
5497
5535
|
// Step 5: Verify trace reporting end-to-end
|
|
5498
5536
|
info('Step 5/5: 验证 trace 上报链路...');
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5537
|
+
let verifyResult;
|
|
5538
|
+
if (args.cloud && !cloudTraceUsableToken) {
|
|
5539
|
+
verifyResult = {
|
|
5540
|
+
success: false,
|
|
5541
|
+
status: 0,
|
|
5542
|
+
body: '云端仅提供 COZE_API_TOKEN。Coze API token 不能作为 CozeLoop Python SDK 的 COZELOOP_API_TOKEN 上报 trace;请注入具备 CozeLoop trace ingest 权限的 COZELOOP_API_TOKEN。',
|
|
5543
|
+
traceId: '',
|
|
5544
|
+
pairCode: args.pairCode || '',
|
|
5545
|
+
tokenSource,
|
|
5546
|
+
apiBaseUrl: getCozeloopApiBaseUrl(true),
|
|
5547
|
+
};
|
|
5548
|
+
warn('trace 上报跳过:缺少 COZELOOP_API_TOKEN。');
|
|
5549
|
+
console.log(verifyResult.body);
|
|
5550
|
+
} else {
|
|
5551
|
+
verifyResult = args.cloud
|
|
5552
|
+
? await verifyTraceReportViaSdk(token, WORKSPACE_ID, args.pairCode, pythonCmd || 'python3', tokenSource)
|
|
5553
|
+
: await verifyTraceReport(token, WORKSPACE_ID, args.pairCode, getOtelTracesUrl(false));
|
|
5554
|
+
}
|
|
5502
5555
|
if (verifyResult.success) {
|
|
5503
5556
|
cloudResult.verify = 'ok';
|
|
5504
5557
|
} else if (CLOUD_MODE) {
|
package/package.json
CHANGED
|
@@ -269,11 +269,10 @@ 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
|
|
277
276
|
creds = _load_credentials()
|
|
278
277
|
if creds:
|
|
279
278
|
expires_at_sec = creds.get("expires_at", 0) / 1000
|
|
@@ -287,12 +286,8 @@ def get_fresh_token() -> Optional[str]:
|
|
|
287
286
|
if new_token:
|
|
288
287
|
return new_token
|
|
289
288
|
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
289
|
if env_coze_token:
|
|
295
|
-
debug_log("COZE_API_TOKEN present but ignored
|
|
290
|
+
debug_log("COZE_API_TOKEN present but ignored; CozeLoop trace upload requires COZELOOP_API_TOKEN")
|
|
296
291
|
return None
|
|
297
292
|
|
|
298
293
|
# -------------------------------------------------------------------------
|
|
@@ -230,11 +230,10 @@ 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
|
|
238
237
|
creds = _load_credentials()
|
|
239
238
|
if creds:
|
|
240
239
|
remaining = creds.get("expires_at", 0) / 1000 - time.time()
|
|
@@ -244,12 +243,8 @@ def get_fresh_token():
|
|
|
244
243
|
new_token = _refresh_token(creds["refresh_token"])
|
|
245
244
|
if new_token:
|
|
246
245
|
return new_token
|
|
247
|
-
if env_token:
|
|
248
|
-
return env_token
|
|
249
|
-
if api_base_url:
|
|
250
|
-
return env_coze_token
|
|
251
246
|
if env_coze_token:
|
|
252
|
-
hook_log("COZE_API_TOKEN present but ignored
|
|
247
|
+
hook_log("COZE_API_TOKEN present but ignored; CozeLoop trace upload requires COZELOOP_API_TOKEN")
|
|
253
248
|
return None
|
|
254
249
|
# -------------------------------------------------------------------------
|
|
255
250
|
|