coze_lab 0.1.44 → 0.1.46
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 +55 -59
- package/index.js +130 -414
- package/package.json +1 -2
- package/scripts/claude-code/cozeloop_hook.py +6 -244
- package/scripts/codex/cozeloop_hook.py +7 -371
- package/scripts/openclaw/dist/cozeloop-exporter.js +11 -183
- package/scripts/openclaw/dist/index.js +1 -1
- package/scripts/openclaw/openclaw.plugin.json +2 -2
- package/scripts/openclaw/package.json +1 -1
- package/scripts/shared/cozeloop_refresh.py +0 -57
package/index.js
CHANGED
|
@@ -2,13 +2,9 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
// ─── 0. Constants ─────────────────────────────────────────────────────────────
|
|
5
|
-
const CLIENT_ID = '08972682140163281554629748278108.app.coze';
|
|
6
5
|
const WORKSPACE_ID = '7649231955045072915';
|
|
7
6
|
const COZE_API = 'https://api.coze.cn';
|
|
8
|
-
const CREDS_PATH = require('path').join(require('os').homedir(), '.cozeloop', 'credentials.json');
|
|
9
7
|
const PACKAGE_VERSION = require('./package.json').version;
|
|
10
|
-
// Refresh when less than 10 minutes remain
|
|
11
|
-
const REFRESH_THRESHOLD_MS = 10 * 60 * 1000;
|
|
12
8
|
const TOKEN_SOURCE_AGENT_PAT = 'agent_config.patToken';
|
|
13
9
|
|
|
14
10
|
// ─── 1. Cloud structured output ──────────────────────────────────────────────
|
|
@@ -118,7 +114,7 @@ const OPENCLAW_PLUGIN_VERSION = (() => {
|
|
|
118
114
|
}
|
|
119
115
|
})();
|
|
120
116
|
|
|
121
|
-
// Read a single script file
|
|
117
|
+
// Read a single script file as a UTF-8 string.
|
|
122
118
|
function readScript(relPath) {
|
|
123
119
|
return require('fs').readFileSync(require('path').join(SCRIPTS_DIR, relPath), 'utf8');
|
|
124
120
|
}
|
|
@@ -147,6 +143,8 @@ function loadOpenclawFiles() {
|
|
|
147
143
|
function parseArgs() {
|
|
148
144
|
const args = {};
|
|
149
145
|
for (const arg of process.argv.slice(2)) {
|
|
146
|
+
// Legacy auth-only commands are still parsed so validateArgs can return a
|
|
147
|
+
// clear "removed" error instead of falling through to generic usage.
|
|
150
148
|
if (arg === '--logout') { args['logout'] = true; continue; }
|
|
151
149
|
if (arg === '--login') { args['login'] = true; continue; }
|
|
152
150
|
if (arg === '--status') { args['status'] = true; continue; }
|
|
@@ -225,11 +223,16 @@ function resolveAgent(agentId, soft) {
|
|
|
225
223
|
}
|
|
226
224
|
|
|
227
225
|
function validateArgs(args) {
|
|
228
|
-
|
|
229
|
-
if (
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
226
|
+
const legacyAuthCmd = ['login', 'status', 'refresh', 'logout'].find((name) => args[name]);
|
|
227
|
+
if (legacyAuthCmd) {
|
|
228
|
+
errorBox([
|
|
229
|
+
`ERROR: --${legacyAuthCmd} 已移除`,
|
|
230
|
+
'',
|
|
231
|
+
'coze_lab 不再提供 Device Code / OAuth 本地授权兜底链路。',
|
|
232
|
+
'本地请使用 --agent-id=<id>,并确保 ~/.coze/agents/<id>/config.json 中存在 patToken。',
|
|
233
|
+
'云端请通过环境变量 COZELOOP_API_TOKEN 或 COZE_API_TOKEN 注入 trace token。',
|
|
234
|
+
]);
|
|
235
|
+
}
|
|
233
236
|
|
|
234
237
|
// --agent-id:优先读 coze-bridge 的 ~/.coze/agents/<id>/config.json 拿 framework/workspace。
|
|
235
238
|
// 云端判定优先看 deployType / CLOUD_ENV;兼容老 config 时再看 cloud-only 落盘字段。
|
|
@@ -251,6 +254,7 @@ function validateArgs(args) {
|
|
|
251
254
|
pairCode: args['pair-code'],
|
|
252
255
|
cloud,
|
|
253
256
|
force: !!args['force'],
|
|
257
|
+
verify: !!args['verify'],
|
|
254
258
|
};
|
|
255
259
|
}
|
|
256
260
|
// 显式 --cloud 或 CLOUD_ENV=1 且 config.json 缺失:回退到显式 --agent
|
|
@@ -274,6 +278,15 @@ function validateArgs(args) {
|
|
|
274
278
|
pairCode: args['pair-code'],
|
|
275
279
|
cloud: true,
|
|
276
280
|
force: !!args['force'],
|
|
281
|
+
verify: !!args['verify'],
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (args['verify']) {
|
|
286
|
+
return {
|
|
287
|
+
verify: true,
|
|
288
|
+
pairCode: args['pair-code'],
|
|
289
|
+
cloud: !!args['cloud'] || isCloudRuntimeEnv() || !!getCloudTokenInfo().token,
|
|
277
290
|
};
|
|
278
291
|
}
|
|
279
292
|
|
|
@@ -282,18 +295,15 @@ function validateArgs(args) {
|
|
|
282
295
|
'ERROR: --agent 或 --agent-id 至少提供一个',
|
|
283
296
|
'',
|
|
284
297
|
'Usage:',
|
|
285
|
-
' --agent
|
|
286
|
-
' --agent-
|
|
298
|
+
' --agent-id=<id> (本地/云端按 ~/.coze/agents/<id>/config.json 自动路由)',
|
|
299
|
+
' --cloud --agent=claude-code|codex|openclaw (云端兼容调用,token 来自环境变量)',
|
|
287
300
|
'',
|
|
288
301
|
'Flags:',
|
|
289
302
|
' --force 强制重装(OpenClaw 跳过幂等检查,无条件重写插件 + 重装依赖 + 重启 gateway)',
|
|
290
303
|
'',
|
|
291
304
|
'Other commands:',
|
|
292
|
-
' --status Show authorization status',
|
|
293
|
-
' --login Login (Device Code flow)',
|
|
294
|
-
' --refresh Force refresh access token',
|
|
295
|
-
' --logout Clear cached credentials',
|
|
296
305
|
' --verify Send a test trace to verify the reporting pipeline',
|
|
306
|
+
' 需要 --agent-id 的 patToken,或环境变量 COZELOOP_API_TOKEN/COZE_API_TOKEN',
|
|
297
307
|
' 可带 --pair-code=<值> 写入 trace metadata(缺省自动生成),供查询方回查',
|
|
298
308
|
]);
|
|
299
309
|
}
|
|
@@ -307,7 +317,17 @@ function validateArgs(args) {
|
|
|
307
317
|
' --agent=openclaw',
|
|
308
318
|
]);
|
|
309
319
|
}
|
|
310
|
-
|
|
320
|
+
const cloud = !!args['cloud'] || isCloudRuntimeEnv();
|
|
321
|
+
if (!cloud) {
|
|
322
|
+
errorBox([
|
|
323
|
+
'ERROR: 本地模式不再支持仅使用 --agent=<type> 配置 trace',
|
|
324
|
+
'',
|
|
325
|
+
'本地配置必须通过 --agent-id=<id> 读取对应 agent config 中的 patToken。',
|
|
326
|
+
'请使用:',
|
|
327
|
+
' npx coze_lab --agent-id=<id>',
|
|
328
|
+
]);
|
|
329
|
+
}
|
|
330
|
+
return { agent: args['agent'], 'codex-home': args['codex-home'], pairCode: args['pair-code'], cloud, force: !!args['force'] };
|
|
311
331
|
}
|
|
312
332
|
|
|
313
333
|
// ─── 4. Agent detection ──────────────────────────────────────────────────────
|
|
@@ -439,12 +459,16 @@ function checkCozeloopSdk(pythonCmd, options = {}) {
|
|
|
439
459
|
}
|
|
440
460
|
|
|
441
461
|
// ─── 6. Version whitelist check ──────────────────────────────────────────────
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
'
|
|
446
|
-
'
|
|
462
|
+
// Forward-compatible lower bounds. Newer CLI releases are treated as
|
|
463
|
+
// supported by default because the hook selfcheck is the authoritative guard.
|
|
464
|
+
const VERSION_SUPPORT = {
|
|
465
|
+
'claude-code': { min: '1.0.0', display: ['>= 1.0.0'] },
|
|
466
|
+
'codex': { min: '0.134.0', display: ['>= 0.134.0'] },
|
|
467
|
+
'openclaw': { min: '2026.3.0', display: ['>= 2026.3.0'] },
|
|
447
468
|
};
|
|
469
|
+
const VERSION_WHITELIST = Object.fromEntries(
|
|
470
|
+
Object.entries(VERSION_SUPPORT).map(([agent, cfg]) => [agent, cfg.display]),
|
|
471
|
+
);
|
|
448
472
|
|
|
449
473
|
const UPGRADE_CMD = {
|
|
450
474
|
'claude-code': 'npm install -g @anthropic-ai/claude-code@latest',
|
|
@@ -453,15 +477,15 @@ const UPGRADE_CMD = {
|
|
|
453
477
|
};
|
|
454
478
|
|
|
455
479
|
function checkVersionWhitelist(agent, version) {
|
|
456
|
-
const
|
|
457
|
-
const supported =
|
|
480
|
+
const support = VERSION_SUPPORT[agent];
|
|
481
|
+
const supported = support && isVersionAtLeast(version, support.min);
|
|
458
482
|
|
|
459
483
|
if (supported) {
|
|
460
484
|
ok(`${agent} v${version} — OK`);
|
|
461
485
|
return;
|
|
462
486
|
}
|
|
463
487
|
|
|
464
|
-
const supportedList =
|
|
488
|
+
const supportedList = (support?.display || []).map(v => ` • ${v}`);
|
|
465
489
|
warnBox([
|
|
466
490
|
`⚠ WARNING: Unsupported ${agent} version: ${version}`,
|
|
467
491
|
'',
|
|
@@ -471,10 +495,29 @@ function checkVersionWhitelist(agent, version) {
|
|
|
471
495
|
'Trace reporting may not work correctly.',
|
|
472
496
|
'Please upgrade and re-run:',
|
|
473
497
|
` ${UPGRADE_CMD[agent]}`,
|
|
474
|
-
|
|
498
|
+
' npx coze_lab --agent-id=<id>',
|
|
475
499
|
]);
|
|
476
500
|
}
|
|
477
501
|
|
|
502
|
+
function parseVersionNumbers(version) {
|
|
503
|
+
const m = String(version || '').match(/(\d+(?:\.\d+){0,3})/);
|
|
504
|
+
return m ? m[1].split('.').map((part) => Number(part)) : [];
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function isVersionAtLeast(version, minVersion) {
|
|
508
|
+
const got = parseVersionNumbers(version);
|
|
509
|
+
const min = parseVersionNumbers(minVersion);
|
|
510
|
+
if (!got.length || !min.length) return false;
|
|
511
|
+
const len = Math.max(got.length, min.length);
|
|
512
|
+
for (let i = 0; i < len; i += 1) {
|
|
513
|
+
const a = got[i] || 0;
|
|
514
|
+
const b = min[i] || 0;
|
|
515
|
+
if (a > b) return true;
|
|
516
|
+
if (a < b) return false;
|
|
517
|
+
}
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
|
|
478
521
|
// ─── 7. Hook writers ─────────────────────────────────────────────────────────
|
|
479
522
|
const fs = require('fs');
|
|
480
523
|
const path = require('path');
|
|
@@ -545,15 +588,13 @@ function writeClaudeCodeHook(token, workspaceId, pythonCmd, configBaseDir, cloud
|
|
|
545
588
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
546
589
|
const localSettingsPath = path.join(baseDir, '.claude', 'settings.local.json');
|
|
547
590
|
|
|
548
|
-
// 1. Write Python hook
|
|
591
|
+
// 1. Write Python hook script — 脚本放全局 ~/.claude/hooks,可共享
|
|
549
592
|
ensureDir(hooksDir);
|
|
550
593
|
writeHookScript(hookScript, readScript('claude-code/cozeloop_hook.py'));
|
|
551
|
-
const refreshScript = path.join(hooksDir, 'cozeloop_refresh.py');
|
|
552
|
-
writeHookScript(refreshScript, readScript('shared/cozeloop_refresh.py'));
|
|
553
594
|
|
|
554
|
-
// 2. Merge settings.json — Stop (trace 收尾) + PostToolUse (trace 增量)
|
|
595
|
+
// 2. Merge settings.json — Stop (trace 收尾) + PostToolUse (trace 增量)。用绝对路径。
|
|
596
|
+
// 同时清掉旧版本写入的 cozeloop_refresh.py hook,避免继续走 OAuth refresh 兜底。
|
|
555
597
|
const hookCmd = `${pythonCmd} ${hookScript}`;
|
|
556
|
-
const refreshCmd = `${pythonCmd} ${refreshScript}`;
|
|
557
598
|
|
|
558
599
|
ensureDir(claudeDir);
|
|
559
600
|
let settings;
|
|
@@ -576,12 +617,11 @@ function writeClaudeCodeHook(token, workspaceId, pythonCmd, configBaseDir, cloud
|
|
|
576
617
|
);
|
|
577
618
|
existing.hooks.PostToolUse.push({ matcher: '', hooks: [{ type: 'command', command: hookCmd }] });
|
|
578
619
|
|
|
579
|
-
//
|
|
620
|
+
// Remove legacy token-refresh hook entries.
|
|
580
621
|
if (!existing.hooks.UserPromptSubmit) existing.hooks.UserPromptSubmit = [];
|
|
581
622
|
existing.hooks.UserPromptSubmit = existing.hooks.UserPromptSubmit.filter(
|
|
582
623
|
entry => !entry.hooks?.some(h => h.command?.includes('cozeloop_refresh.py'))
|
|
583
624
|
);
|
|
584
|
-
existing.hooks.UserPromptSubmit.push({ matcher: '', hooks: [{ type: 'command', command: refreshCmd }] });
|
|
585
625
|
|
|
586
626
|
return existing;
|
|
587
627
|
});
|
|
@@ -596,7 +636,7 @@ function writeClaudeCodeHook(token, workspaceId, pythonCmd, configBaseDir, cloud
|
|
|
596
636
|
}
|
|
597
637
|
ok(`Hook registered in ${settingsPath}`);
|
|
598
638
|
|
|
599
|
-
// 3. Write
|
|
639
|
+
// 3. Write hook environment into <baseDir>/.claude/settings.local.json
|
|
600
640
|
ensureDir(path.join(baseDir, '.claude'));
|
|
601
641
|
ensureDir(path.dirname(logFile));
|
|
602
642
|
let localSettings;
|
|
@@ -648,9 +688,9 @@ function writeClaudeCodeHook(token, workspaceId, pythonCmd, configBaseDir, cloud
|
|
|
648
688
|
try {
|
|
649
689
|
atomicWriteFileSync(localSettingsPath, JSON.stringify(localSettings, null, 2));
|
|
650
690
|
} catch (e) {
|
|
651
|
-
errorBox([`ERROR: Cannot write
|
|
691
|
+
errorBox([`ERROR: Cannot write hook environment to ${localSettingsPath}`, '', e.message]);
|
|
652
692
|
}
|
|
653
|
-
ok(`
|
|
693
|
+
ok(`Hook environment written to ${localSettingsPath}`);
|
|
654
694
|
|
|
655
695
|
|
|
656
696
|
return { hookScript, settingsPath, localSettingsPath, logFile };
|
|
@@ -658,8 +698,7 @@ function writeClaudeCodeHook(token, workspaceId, pythonCmd, configBaseDir, cloud
|
|
|
658
698
|
|
|
659
699
|
// writeCodexHook 把 hook 写进指定的 CODEX_HOME。codexHome 缺省 ~/.codex;
|
|
660
700
|
// 传入动态目录(如 coze-bridge 的 /tmp/coze-bridge-codex-home-xxx)即可 per-agent 生效。
|
|
661
|
-
//
|
|
662
|
-
// 本地 --agent-id 且 config.patToken 存在时写入该 PAT,并用 COZELOOP_TOKEN_SOURCE 标记来源。
|
|
701
|
+
// 本地 --agent-id 从 config.patToken 写入 token,并用 COZELOOP_TOKEN_SOURCE 标记来源。
|
|
663
702
|
// cloud=true 时写 COZELAB_ONBOARD_CLOUD,并带入 sandbox 注入的 trace token。
|
|
664
703
|
function writeCodexHook(token, workspaceId, pythonCmd, codexHome, cloud, tokenSource) {
|
|
665
704
|
const home = codexHome || path.join(os.homedir(), '.codex');
|
|
@@ -669,12 +708,9 @@ function writeCodexHook(token, workspaceId, pythonCmd, codexHome, cloud, tokenSo
|
|
|
669
708
|
const logFile = path.join(hooksDir, 'cozeloop.log');
|
|
670
709
|
const hooksJson = path.join(home, 'hooks.json');
|
|
671
710
|
|
|
672
|
-
// 1. Write Python hook
|
|
673
|
-
// Token is read from ~/.cozeloop/credentials.json at runtime
|
|
711
|
+
// 1. Write Python hook script
|
|
674
712
|
ensureDir(hooksDir);
|
|
675
713
|
writeHookScript(hookScript, readScript('codex/cozeloop_hook.py'));
|
|
676
|
-
const refreshScript = path.join(hooksDir, 'cozeloop_refresh.py');
|
|
677
|
-
writeHookScript(refreshScript, readScript('shared/cozeloop_refresh.py'));
|
|
678
714
|
|
|
679
715
|
// 2. Write env file with chmod 600
|
|
680
716
|
const envLines = [
|
|
@@ -707,14 +743,14 @@ function writeCodexHook(token, workspaceId, pythonCmd, codexHome, cloud, tokenSo
|
|
|
707
743
|
try {
|
|
708
744
|
fs.writeFileSync(envFile, envContent, { mode: 0o600 });
|
|
709
745
|
} catch (e) {
|
|
710
|
-
errorBox([`ERROR: Cannot write
|
|
746
|
+
errorBox([`ERROR: Cannot write hook environment to ${envFile}`, '', e.message]);
|
|
711
747
|
}
|
|
712
|
-
ok(`
|
|
748
|
+
ok(`Hook environment written to ${envFile} (chmod 600)`);
|
|
713
749
|
|
|
714
|
-
// 3. Merge hooks.json — Stop (trace 收尾) + PostToolUse (trace 增量)
|
|
750
|
+
// 3. Merge hooks.json — Stop (trace 收尾) + PostToolUse (trace 增量)
|
|
715
751
|
// 命令用绝对路径(CODEX_HOME 不一定是 ~/.codex)。
|
|
752
|
+
// 同时清掉旧版本写入的 cozeloop_refresh.py SessionStart hook。
|
|
716
753
|
const hookCmd = `set -a && . ${envFile} && set +a && ${pythonCmd} ${hookScript}`;
|
|
717
|
-
const refreshCmd = `${pythonCmd} ${refreshScript}`;
|
|
718
754
|
|
|
719
755
|
const hooks = mergeJson(hooksJson, (existing) => {
|
|
720
756
|
if (!existing.hooks) existing.hooks = {};
|
|
@@ -734,12 +770,11 @@ function writeCodexHook(token, workspaceId, pythonCmd, codexHome, cloud, tokenSo
|
|
|
734
770
|
);
|
|
735
771
|
existing.hooks.PostToolUse.push({ matcher: null, hooks: [{ type: 'command', command: hookCmd, timeout: 60 }] });
|
|
736
772
|
|
|
737
|
-
//
|
|
773
|
+
// Remove legacy token-refresh hook entries.
|
|
738
774
|
if (!existing.hooks.SessionStart) existing.hooks.SessionStart = [];
|
|
739
775
|
existing.hooks.SessionStart = existing.hooks.SessionStart.filter(
|
|
740
776
|
entry => !entry.hooks?.some(h => h.command?.includes('cozeloop_refresh.py'))
|
|
741
777
|
);
|
|
742
|
-
existing.hooks.SessionStart.push({ matcher: null, hooks: [{ type: 'command', command: refreshCmd, timeout: 15 }] });
|
|
743
778
|
|
|
744
779
|
return existing;
|
|
745
780
|
});
|
|
@@ -1063,7 +1098,7 @@ function writeOpenClawHook(token, workspaceId, agentId, cloud, force, tokenSourc
|
|
|
1063
1098
|
}
|
|
1064
1099
|
}
|
|
1065
1100
|
|
|
1066
|
-
// ─── 8.
|
|
1101
|
+
// ─── 8. Trace verification HTTP helpers ─────────────────────────────────────
|
|
1067
1102
|
const https = require('https');
|
|
1068
1103
|
const crypto = require('crypto');
|
|
1069
1104
|
|
|
@@ -1377,18 +1412,15 @@ async function verifyTraceReport(token, workspaceId, pairCode, tracesUrl) {
|
|
|
1377
1412
|
}
|
|
1378
1413
|
|
|
1379
1414
|
// ── OpenClaw 专属上报链路校验 ──────────────────────────────────────────────
|
|
1380
|
-
// 为什么单独一条:claude-code/codex 的 verify
|
|
1415
|
+
// 为什么单独一条:claude-code/codex 的 verify 用主流程解析到的 token
|
|
1381
1416
|
// token 直发,而 openclaw 运行时上报用的是【写死在 openclaw.json 插件 config.authorization
|
|
1382
1417
|
// 里的静态 token】。两者是不同 token —— 插件那个失效(401/4100)时通用 verify 照样 ok,
|
|
1383
1418
|
// 这就是“verify=ok 但实际查不到 trace”假象的根因。本函数改为读插件实际配置的 token 打
|
|
1384
1419
|
// ingest,真实反映运行时会不会 401。
|
|
1385
1420
|
//
|
|
1386
1421
|
// cloud/local 兼容:插件配置位置都在 resolveHomeDir(cloud)/.openclaw/openclaw.json,
|
|
1387
|
-
// endpoint 都走 getOtelEndpointBase(cloud)
|
|
1388
|
-
//
|
|
1389
|
-
// 所以额外检测【实际加载的插件是否含刷新逻辑 getRefreshedToken】,无则告警。
|
|
1390
|
-
// - cloud:disableLocalCredentials=true,插件只用写死的 token、不刷新,token 失效需重注入,
|
|
1391
|
-
// 刷新能力检测对 cloud 无意义(跳过)。
|
|
1422
|
+
// endpoint 都走 getOtelEndpointBase(cloud),逻辑统一。OpenClaw 插件只使用 onboard 写入
|
|
1423
|
+
// 的 authorization,不再读取或刷新本地 credentials。
|
|
1392
1424
|
async function verifyOpenClawTraceLink(cloud, pairCode) {
|
|
1393
1425
|
const home = resolveHomeDir(cloud);
|
|
1394
1426
|
const configPath = path.join(home, '.openclaw', 'openclaw.json');
|
|
@@ -1480,60 +1512,14 @@ async function verifyOpenClawTraceLink(cloud, pairCode) {
|
|
|
1480
1512
|
if (snippet) console.log(snippet);
|
|
1481
1513
|
// 4100/401 = 该 token 已失效。指出根因与修复方式。
|
|
1482
1514
|
if (res.status === 401 || /\b4100\b/.test(res.body || '')) {
|
|
1483
|
-
info('插件配置的 token 已失效。运行时上报会 401
|
|
1484
|
-
info('修复:重跑 `
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
// 2) 仅 local:检测实际加载的插件是否具备 token 自动刷新能力。
|
|
1489
|
-
// cloud 主动 disableLocalCredentials,不刷新,检测无意义。
|
|
1490
|
-
if (!cloud) {
|
|
1491
|
-
const refreshOk = openClawPluginHasRefresh(home);
|
|
1492
|
-
if (refreshOk === true) {
|
|
1493
|
-
ok('openclaw 插件具备 token 自动刷新能力 (getRefreshedToken)。');
|
|
1494
|
-
} else if (refreshOk === false) {
|
|
1495
|
-
warn('本机加载的 openclaw 插件【无 token 刷新逻辑】,token 过期后会反复 401 崩 gateway。');
|
|
1496
|
-
info('修复:重跑 `node index.js --agent=openclaw --force` 安装带刷新逻辑的新插件。');
|
|
1515
|
+
info('插件配置的 token 已失效。运行时上报会 401,span 会丢失。');
|
|
1516
|
+
info('修复:重跑 `npx coze_lab --agent-id=<id> --force` 写入 agent config 中最新 patToken。');
|
|
1497
1517
|
}
|
|
1498
|
-
// refreshOk === null:定位不到插件文件,不下结论(不误报)。
|
|
1499
1518
|
}
|
|
1500
1519
|
|
|
1501
1520
|
return { success, status: res.status, body: res.body || '' };
|
|
1502
1521
|
}
|
|
1503
1522
|
|
|
1504
|
-
// 检测本机【实际加载的】openclaw trace 插件是否含 token 刷新逻辑(getRefreshedToken)。
|
|
1505
|
-
// 返回 true=有 / false=无 / null=定位不到插件文件(不下结论)。
|
|
1506
|
-
// 探测顺序:openclaw plugins list 给出的真实路径 > onboard 安装位置 ~/.cozeloop/openclaw-plugin
|
|
1507
|
-
// > 历史手改位置 ~/.openclaw/workspace/cozeloop-trace-fix。
|
|
1508
|
-
function openClawPluginHasRefresh(home) {
|
|
1509
|
-
const candidates = [];
|
|
1510
|
-
// openclaw plugins list 拿实际加载路径(最准——能发现 cozeloop-trace-fix 这类残留旧插件)
|
|
1511
|
-
try {
|
|
1512
|
-
const { execSync } = require('child_process');
|
|
1513
|
-
const out = execSync('openclaw plugins list', { stdio: ['ignore', 'pipe', 'ignore'] }).toString();
|
|
1514
|
-
for (const line of out.split(/\r?\n/)) {
|
|
1515
|
-
if (!/cozeloop|trace/i.test(line)) continue;
|
|
1516
|
-
const m = line.match(/(\/[^\s'"]+)/); // 抓行内绝对路径
|
|
1517
|
-
if (m) candidates.push(m[1]);
|
|
1518
|
-
}
|
|
1519
|
-
} catch { /* CLI 不可用则回退已知路径 */ }
|
|
1520
|
-
candidates.push(path.join(home, '.cozeloop', 'openclaw-plugin'));
|
|
1521
|
-
candidates.push(path.join(home, '.openclaw', 'workspace', 'cozeloop-trace-fix'));
|
|
1522
|
-
|
|
1523
|
-
let foundAny = false;
|
|
1524
|
-
for (const base of candidates) {
|
|
1525
|
-
for (const rel of ['dist/cozeloop-exporter.js', 'dist/index.js', 'cozeloop-exporter.js', 'index.js']) {
|
|
1526
|
-
const f = path.isAbsolute(rel) ? rel : path.join(base, rel);
|
|
1527
|
-
try {
|
|
1528
|
-
if (!fs.existsSync(f)) continue;
|
|
1529
|
-
foundAny = true;
|
|
1530
|
-
if (fs.readFileSync(f, 'utf8').includes('getRefreshedToken')) return true;
|
|
1531
|
-
} catch { /* ignore */ }
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
return foundAny ? false : null;
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
1523
|
function httpsGet(url, headers) {
|
|
1538
1524
|
return new Promise((resolve, reject) => {
|
|
1539
1525
|
const h = { ...(headers || {}) };
|
|
@@ -1547,254 +1533,10 @@ function httpsGet(url, headers) {
|
|
|
1547
1533
|
});
|
|
1548
1534
|
}
|
|
1549
1535
|
|
|
1550
|
-
// ── Credentials store ──────────────────────────────────────────────────────
|
|
1551
|
-
function loadCredentials() {
|
|
1552
|
-
try {
|
|
1553
|
-
return JSON.parse(fs.readFileSync(CREDS_PATH, 'utf8'));
|
|
1554
|
-
} catch {
|
|
1555
|
-
return null;
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
function saveCredentials(creds) {
|
|
1560
|
-
// 0o700:凭证目录仅 owner 可读/进入,其他用户无法枚举 ~/.cozeloop 内容。
|
|
1561
|
-
fs.mkdirSync(path.dirname(CREDS_PATH), { recursive: true, mode: 0o700 });
|
|
1562
|
-
atomicWriteFileSync(CREDS_PATH, JSON.stringify(creds, null, 2), { mode: 0o600 });
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
function deleteCredentials() {
|
|
1566
|
-
try { fs.unlinkSync(CREDS_PATH); } catch { /* already gone */ }
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
function isExpired(creds) {
|
|
1570
|
-
if (!creds || !creds.expires_at) return true;
|
|
1571
|
-
return Date.now() >= creds.expires_at - REFRESH_THRESHOLD_MS;
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
// ── Refresh token ──────────────────────────────────────────────────────────
|
|
1575
|
-
async function refreshToken(creds) {
|
|
1576
|
-
info('Access token expiring soon, refreshing...');
|
|
1577
|
-
let res;
|
|
1578
|
-
try {
|
|
1579
|
-
res = await httpsPost(`${COZE_API}/api/permission/oauth2/token`, {
|
|
1580
|
-
grant_type: 'refresh_token',
|
|
1581
|
-
client_id: CLIENT_ID,
|
|
1582
|
-
refresh_token: creds.refresh_token,
|
|
1583
|
-
});
|
|
1584
|
-
} catch (e) {
|
|
1585
|
-
errorBox(['ERROR: Could not refresh token', '', e.message]);
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
let data;
|
|
1589
|
-
try { data = JSON.parse(res.body); } catch {
|
|
1590
|
-
errorBox(['ERROR: Unexpected response while refreshing token']);
|
|
1591
|
-
}
|
|
1592
|
-
|
|
1593
|
-
if (data.error || res.status !== 200) {
|
|
1594
|
-
warn(`Token refresh failed (${data.error || res.status}), re-authorizing...`);
|
|
1595
|
-
return null; // caller will re-run device code flow
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
const updated = {
|
|
1599
|
-
access_token: data.access_token,
|
|
1600
|
-
refresh_token: data.refresh_token ?? creds.refresh_token,
|
|
1601
|
-
expires_at: (data.expires_in ?? 0) * 1000, // expires_at stored in milliseconds (Python 端按毫秒读)
|
|
1602
|
-
workspace_id: creds.workspace_id ?? WORKSPACE_ID, // preserve workspace_id
|
|
1603
|
-
};
|
|
1604
|
-
saveCredentials(updated);
|
|
1605
|
-
ok('Token refreshed successfully.');
|
|
1606
|
-
return updated;
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
// ── Device Code flow ───────────────────────────────────────────────────────
|
|
1610
|
-
async function deviceCodeAuth() {
|
|
1611
|
-
// Step 1: Get device code
|
|
1612
|
-
let res;
|
|
1613
|
-
try {
|
|
1614
|
-
res = await httpsPost(`${COZE_API}/api/permission/oauth2/device/code`, {
|
|
1615
|
-
client_id: CLIENT_ID,
|
|
1616
|
-
});
|
|
1617
|
-
} catch (e) {
|
|
1618
|
-
errorBox(['ERROR: Could not reach Coze API', '', e.message]);
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
let data;
|
|
1622
|
-
try { data = JSON.parse(res.body); } catch {
|
|
1623
|
-
errorBox(['ERROR: Unexpected response from Coze API']);
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
if (res.status !== 200 || data.error) {
|
|
1627
|
-
errorBox([
|
|
1628
|
-
'ERROR: Failed to start device authorization',
|
|
1629
|
-
'',
|
|
1630
|
-
data.error_description || data.error || `HTTP ${res.status}`,
|
|
1631
|
-
]);
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
const { device_code, user_code, verification_uri, expires_in, interval = 5 } = data;
|
|
1635
|
-
const activation_url = `${verification_uri}?user_code=${encodeURIComponent(user_code)}`;
|
|
1636
|
-
|
|
1637
|
-
console.log('');
|
|
1638
|
-
box([
|
|
1639
|
-
' 请在浏览器中打开以下链接完成授权:',
|
|
1640
|
-
'',
|
|
1641
|
-
` ${activation_url}`,
|
|
1642
|
-
'',
|
|
1643
|
-
` 验证码将在 ${expires_in} 秒后过期`,
|
|
1644
|
-
], C.cyan);
|
|
1645
|
-
console.log('');
|
|
1646
|
-
|
|
1647
|
-
// Try to open browser automatically
|
|
1648
|
-
try {
|
|
1649
|
-
const { execSync } = require('child_process');
|
|
1650
|
-
const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
1651
|
-
execSync(`${opener} "${activation_url}"`, { stdio: 'ignore' });
|
|
1652
|
-
info('已自动打开浏览器,请在浏览器中完成授权...');
|
|
1653
|
-
} catch {
|
|
1654
|
-
info('请手动在浏览器中打开上方链接完成授权...');
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
|
-
// Step 2: Poll for token
|
|
1658
|
-
const deadline = Date.now() + expires_in * 1000;
|
|
1659
|
-
let pollInterval = interval * 1000;
|
|
1660
|
-
|
|
1661
|
-
process.stdout.write(`${C.cyan}[i]${C.reset} 等待授权中`);
|
|
1662
|
-
|
|
1663
|
-
while (Date.now() < deadline) {
|
|
1664
|
-
await new Promise(r => setTimeout(r, pollInterval));
|
|
1665
|
-
process.stdout.write('.');
|
|
1666
|
-
|
|
1667
|
-
let pollRes;
|
|
1668
|
-
try {
|
|
1669
|
-
pollRes = await httpsPost(`${COZE_API}/api/permission/oauth2/token`, {
|
|
1670
|
-
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
1671
|
-
client_id: CLIENT_ID,
|
|
1672
|
-
device_code,
|
|
1673
|
-
});
|
|
1674
|
-
} catch { continue; }
|
|
1675
|
-
|
|
1676
|
-
let pollData;
|
|
1677
|
-
try { pollData = JSON.parse(pollRes.body); } catch { continue; }
|
|
1678
|
-
|
|
1679
|
-
if (pollData.access_token) {
|
|
1680
|
-
process.stdout.write('\n');
|
|
1681
|
-
const creds = {
|
|
1682
|
-
access_token: pollData.access_token,
|
|
1683
|
-
refresh_token: pollData.refresh_token,
|
|
1684
|
-
expires_at: (pollData.expires_in ?? 0) * 1000, // expires_at stored in milliseconds (Python 端按毫秒读)
|
|
1685
|
-
workspace_id: WORKSPACE_ID,
|
|
1686
|
-
};
|
|
1687
|
-
saveCredentials(creds);
|
|
1688
|
-
console.log('');
|
|
1689
|
-
ok('授权成功!Token 已保存到 ~/.cozeloop/credentials.json');
|
|
1690
|
-
return creds;
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
if (pollData.error === 'slow_down') {
|
|
1694
|
-
pollInterval += 5000;
|
|
1695
|
-
} else if (pollData.error === 'access_denied') {
|
|
1696
|
-
process.stdout.write('\n');
|
|
1697
|
-
errorBox(['ERROR: 用户拒绝了授权请求']);
|
|
1698
|
-
} else if (pollData.error === 'expired_token') {
|
|
1699
|
-
process.stdout.write('\n');
|
|
1700
|
-
errorBox(['ERROR: 验证码已过期,请重新运行命令']);
|
|
1701
|
-
}
|
|
1702
|
-
// authorization_pending → keep polling
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
process.stdout.write('\n');
|
|
1706
|
-
errorBox(['ERROR: 授权超时,请重新运行命令']);
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
// ── Get valid token (load → refresh → re-auth) ────────────────────────────
|
|
1710
|
-
async function getValidToken() {
|
|
1711
|
-
let creds = loadCredentials();
|
|
1712
|
-
|
|
1713
|
-
if (creds && !isExpired(creds)) {
|
|
1714
|
-
ok('已找到有效的本地授权,跳过授权步骤。');
|
|
1715
|
-
return creds.access_token;
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
if (creds && creds.refresh_token) {
|
|
1719
|
-
const refreshed = await refreshToken(creds);
|
|
1720
|
-
if (refreshed) return refreshed.access_token;
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
// Need fresh authorization
|
|
1724
|
-
info('未找到本地授权,启动 Device Code 授权流程...');
|
|
1725
|
-
const fresh = await deviceCodeAuth();
|
|
1726
|
-
return fresh.access_token;
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
// ── Logout ────────────────────────────────────────────────────────────────
|
|
1730
|
-
function logout() {
|
|
1731
|
-
const creds = loadCredentials();
|
|
1732
|
-
if (!creds) {
|
|
1733
|
-
info('本地没有保存的授权信息,无需退出。');
|
|
1734
|
-
return;
|
|
1735
|
-
}
|
|
1736
|
-
deleteCredentials();
|
|
1737
|
-
successBox(['已成功退出登录', '', '本地 Token 已清除 (~/.cozeloop/credentials.json)']);
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
// ── Auth status ───────────────────────────────────────────────────────────
|
|
1741
|
-
function authStatus() {
|
|
1742
|
-
const creds = loadCredentials();
|
|
1743
|
-
if (!creds) {
|
|
1744
|
-
box([
|
|
1745
|
-
'Auth Status: NOT LOGGED IN',
|
|
1746
|
-
'',
|
|
1747
|
-
'~/.cozeloop/credentials.json not found.',
|
|
1748
|
-
'Run with --agent=<type> to authorize.',
|
|
1749
|
-
], C.yellow);
|
|
1750
|
-
return;
|
|
1751
|
-
}
|
|
1752
|
-
|
|
1753
|
-
const expiresAt = creds.expires_at ? new Date(creds.expires_at) : null;
|
|
1754
|
-
const now = Date.now();
|
|
1755
|
-
const remainMs = creds.expires_at ? creds.expires_at - now : null;
|
|
1756
|
-
const remainMin = remainMs != null ? Math.floor(remainMs / 60000) : null;
|
|
1757
|
-
|
|
1758
|
-
function formatRemaining(ms) {
|
|
1759
|
-
if (ms == null) return 'unknown';
|
|
1760
|
-
const min = Math.floor(ms / 60000);
|
|
1761
|
-
if (min < 60) return `${min} 分钟`;
|
|
1762
|
-
const hr = Math.floor(min / 60);
|
|
1763
|
-
if (hr < 24) return `${hr} 小时`;
|
|
1764
|
-
const day = Math.floor(hr / 24);
|
|
1765
|
-
if (day < 365) return `${day} 天`;
|
|
1766
|
-
return `${Math.floor(day / 365)} 年`;
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
let statusLabel, statusColor;
|
|
1770
|
-
if (!expiresAt) {
|
|
1771
|
-
statusLabel = 'UNKNOWN (no expiry info)';
|
|
1772
|
-
statusColor = C.yellow;
|
|
1773
|
-
} else if (remainMs < 0) {
|
|
1774
|
-
statusLabel = 'EXPIRED';
|
|
1775
|
-
statusColor = C.red;
|
|
1776
|
-
} else if (remainMs < REFRESH_THRESHOLD_MS) {
|
|
1777
|
-
statusLabel = `EXPIRING SOON (剩余 ${formatRemaining(remainMs)})`;
|
|
1778
|
-
statusColor = C.yellow;
|
|
1779
|
-
} else {
|
|
1780
|
-
statusLabel = `VALID (剩余 ${formatRemaining(remainMs)})`;
|
|
1781
|
-
statusColor = C.green;
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
const lines = [
|
|
1785
|
-
`Auth Status: ${statusLabel}`,
|
|
1786
|
-
'',
|
|
1787
|
-
`Token: ${creds.access_token ? creds.access_token.slice(0, 20) + '...' : 'n/a'}`,
|
|
1788
|
-
`Expires at: ${expiresAt ? expiresAt.toLocaleString() : 'unknown'}`,
|
|
1789
|
-
`Refresh: ${creds.refresh_token ? 'available' : 'not available'}`,
|
|
1790
|
-
];
|
|
1791
|
-
box(lines, statusColor);
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
1536
|
// ─── 9. Main ─────────────────────────────────────────────────────────────────
|
|
1795
1537
|
const NEXT_STEP = {
|
|
1796
1538
|
'claude-code': 'Hook 已写入。Claude Code 会自动热重载 hooks,当前会话即刻生效,无需 /new 或重启。',
|
|
1797
|
-
'codex': 'Hook 已写入。Codex 在会话启动时加载 hook,当前会话不会即时生效;请重开 Codex
|
|
1539
|
+
'codex': 'Hook 已写入。Codex 在会话启动时加载 hook,当前会话不会即时生效;请重开 Codex 会话。',
|
|
1798
1540
|
};
|
|
1799
1541
|
|
|
1800
1542
|
function openClawNextStep(written) {
|
|
@@ -1814,64 +1556,31 @@ async function main() {
|
|
|
1814
1556
|
|
|
1815
1557
|
const args = validateArgs(parseArgs());
|
|
1816
1558
|
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
if (
|
|
1833
|
-
|
|
1834
|
-
|
|
1559
|
+
if (args.verify) {
|
|
1560
|
+
info('验证 trace 上报链路...');
|
|
1561
|
+
let token = '';
|
|
1562
|
+
let tokenSource = '';
|
|
1563
|
+
if (args.cloud) {
|
|
1564
|
+
const tokenInfo = getCloudTokenInfo();
|
|
1565
|
+
token = tokenInfo.token;
|
|
1566
|
+
tokenSource = tokenInfo.source;
|
|
1567
|
+
if (!token) {
|
|
1568
|
+
errorBox([
|
|
1569
|
+
'ERROR: verify 需要环境变量 COZELOOP_API_TOKEN 或 COZE_API_TOKEN',
|
|
1570
|
+
'',
|
|
1571
|
+
'coze_lab 不再读取本地 OAuth credentials。',
|
|
1572
|
+
]);
|
|
1573
|
+
}
|
|
1574
|
+
} else if (args.agentId && args.patToken) {
|
|
1575
|
+
token = args.patToken;
|
|
1576
|
+
tokenSource = TOKEN_SOURCE_AGENT_PAT;
|
|
1577
|
+
} else {
|
|
1578
|
+
errorBox([
|
|
1579
|
+
'ERROR: 本地 verify 需要 --agent-id 且 config.json 中存在 patToken',
|
|
1835
1580
|
'',
|
|
1836
|
-
'
|
|
1581
|
+
'请确认 ~/.coze/agents/<id>/config.json 包含 patToken 后重试。',
|
|
1837
1582
|
]);
|
|
1838
|
-
return;
|
|
1839
1583
|
}
|
|
1840
|
-
deleteCredentials();
|
|
1841
|
-
await deviceCodeAuth();
|
|
1842
|
-
return;
|
|
1843
|
-
}
|
|
1844
|
-
|
|
1845
|
-
if (args.refresh) {
|
|
1846
|
-
const creds = loadCredentials();
|
|
1847
|
-
if (!creds) {
|
|
1848
|
-
box([
|
|
1849
|
-
'ERROR: 未找到本地授权信息',
|
|
1850
|
-
'',
|
|
1851
|
-
'请先运行 --agent=<type> 完成授权。',
|
|
1852
|
-
], C.red);
|
|
1853
|
-
process.exit(1);
|
|
1854
|
-
}
|
|
1855
|
-
if (!creds.refresh_token) {
|
|
1856
|
-
box([
|
|
1857
|
-
'ERROR: 当前凭证没有 refresh_token',
|
|
1858
|
-
'',
|
|
1859
|
-
'请运行 --logout 后重新 --agent=<type> 授权。',
|
|
1860
|
-
], C.red);
|
|
1861
|
-
process.exit(1);
|
|
1862
|
-
}
|
|
1863
|
-
const refreshed = await refreshToken(creds);
|
|
1864
|
-
if (!refreshed) {
|
|
1865
|
-
warn('Refresh token 已失效,启动 Device Code 重新授权...');
|
|
1866
|
-
console.log('');
|
|
1867
|
-
await deviceCodeAuth();
|
|
1868
|
-
}
|
|
1869
|
-
return;
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
|
-
if (args.verify) {
|
|
1873
|
-
info('验证 trace 上报链路...');
|
|
1874
|
-
const token = await getValidToken(); // 无凭证会自动走登录/刷新
|
|
1875
1584
|
console.log('');
|
|
1876
1585
|
const result = await verifyTraceReport(token, WORKSPACE_ID, args.pairCode, getOtelTracesUrl(false));
|
|
1877
1586
|
// 若本机装了 openclaw 插件,额外校验插件【实际配置的静态 token】——通用 verify 用刚刷新的
|
|
@@ -1884,7 +1593,7 @@ async function main() {
|
|
|
1884
1593
|
if (cfg?.plugins?.entries?.['openclaw-cozeloop-trace']?.config?.authorization) {
|
|
1885
1594
|
console.log('');
|
|
1886
1595
|
info('检测到 openclaw cozeloop-trace 插件,校验其实际 token...');
|
|
1887
|
-
const ocRes = await verifyOpenClawTraceLink(
|
|
1596
|
+
const ocRes = await verifyOpenClawTraceLink(args.cloud, args.pairCode);
|
|
1888
1597
|
ocOk = ocRes.success;
|
|
1889
1598
|
}
|
|
1890
1599
|
} catch { /* 读不了就跳过 openclaw 校验 */ }
|
|
@@ -1906,11 +1615,10 @@ async function main() {
|
|
|
1906
1615
|
console.log('');
|
|
1907
1616
|
}
|
|
1908
1617
|
|
|
1909
|
-
// Step 1:
|
|
1910
|
-
// 云端模式:token 取自 sandbox
|
|
1618
|
+
// Step 1: Resolve trace token.
|
|
1619
|
+
// 云端模式:token 取自 sandbox 注入的环境变量。
|
|
1911
1620
|
// 优先使用 COZELOOP_API_TOKEN;兼容使用 COZE_API_TOKEN,并以真实 selfcheck 为准。
|
|
1912
|
-
//
|
|
1913
|
-
// 其它本地模式:load cached → refresh → device code。
|
|
1621
|
+
// 本地模式:必须通过 --agent-id 读取 config.patToken。
|
|
1914
1622
|
// 注意:workspace_id 始终用写死的 WORKSPACE_ID(团队固定上报 workspace),不读环境。
|
|
1915
1623
|
let token;
|
|
1916
1624
|
let tokenSource = '';
|
|
@@ -1933,14 +1641,20 @@ async function main() {
|
|
|
1933
1641
|
info('将兼容使用 COZE_API_TOKEN 作为 trace token,并通过 selfcheck 验证实际可用性。');
|
|
1934
1642
|
}
|
|
1935
1643
|
} else {
|
|
1936
|
-
info('Step 1/5:
|
|
1644
|
+
info('Step 1/5: 从 agent config 读取 patToken...');
|
|
1937
1645
|
if (args.agentId && args.patToken) {
|
|
1938
1646
|
token = args.patToken;
|
|
1939
1647
|
tokenSource = TOKEN_SOURCE_AGENT_PAT;
|
|
1940
|
-
ok(`已从 ~/.coze/agents/${args.agentId}/config.json 读取 patToken
|
|
1648
|
+
ok(`已从 ~/.coze/agents/${args.agentId}/config.json 读取 patToken。`);
|
|
1941
1649
|
} else {
|
|
1942
|
-
|
|
1943
|
-
|
|
1650
|
+
errorBox([
|
|
1651
|
+
'ERROR: 本地模式要求 ~/.coze/agents/<agentId>/config.json 中存在 patToken',
|
|
1652
|
+
'',
|
|
1653
|
+
'coze_lab 不再提供 Device Code / OAuth 本地授权兜底。',
|
|
1654
|
+
args.agentId
|
|
1655
|
+
? `请确认 ~/.coze/agents/${args.agentId}/config.json 包含 patToken 后重试。`
|
|
1656
|
+
: '请使用 --agent-id=<id> 运行,以便读取对应 agent config。',
|
|
1657
|
+
]);
|
|
1944
1658
|
}
|
|
1945
1659
|
}
|
|
1946
1660
|
console.log('');
|
|
@@ -2025,7 +1739,7 @@ async function main() {
|
|
|
2025
1739
|
|
|
2026
1740
|
// Step 5: Verify trace reporting end-to-end
|
|
2027
1741
|
info('Step 5/5: 验证 trace 上报链路...');
|
|
2028
|
-
// openclaw 走专属校验:claude-code/codex 的 verify
|
|
1742
|
+
// openclaw 走专属校验:claude-code/codex 的 verify 用主流程解析到的
|
|
2029
1743
|
// 有效 token 直发,测不到 openclaw 插件【写死在 openclaw.json 里的静态 token】是否失效
|
|
2030
1744
|
// (插件不读这个临时 token)。openclaw 必须用插件实际配置的 authorization 打 ingest,
|
|
2031
1745
|
// 才能真实反映运行时上报会不会 401。cloud/local 配置位置一致,统一走这条。
|
|
@@ -2089,5 +1803,7 @@ module.exports = {
|
|
|
2089
1803
|
getAgentPatToken,
|
|
2090
1804
|
mergeJson,
|
|
2091
1805
|
atomicWriteFileSync,
|
|
2092
|
-
|
|
1806
|
+
VERSION_WHITELIST,
|
|
1807
|
+
VERSION_SUPPORT,
|
|
1808
|
+
isVersionAtLeast,
|
|
2093
1809
|
};
|