coze_lab 0.1.34 → 0.1.36
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/index.js +102 -4736
- package/package.json +1 -1
- package/scripts/claude-code/cozeloop_hook.py +49 -15
- package/scripts/codex/cozeloop_hook.py +18 -14
- package/scripts/openclaw/dist/cozeloop-exporter.js +53 -18
- package/scripts/openclaw/dist/index.js +1 -0
- package/scripts/openclaw/openclaw.plugin.json +6 -1
- package/scripts/openclaw/package.json +1 -1
- package/scripts/shared/cozeloop_refresh.py +1 -3
package/package.json
CHANGED
|
@@ -105,11 +105,11 @@ else:
|
|
|
105
105
|
|
|
106
106
|
# --- Configuration ---
|
|
107
107
|
DEBUG = os.environ.get("CC_COZELOOP_DEBUG", "").lower() == "true"
|
|
108
|
-
_COZELOOP_CLIENT_ID = "
|
|
108
|
+
_COZELOOP_CLIENT_ID = "08972682140163281554629748278108.app.coze"
|
|
109
109
|
_COZE_API = "https://api.coze.cn"
|
|
110
110
|
_OTEL_SUFFIX = "/v1/loop/opentelemetry"
|
|
111
111
|
_REFRESH_THRESHOLD = 10 * 60 # refresh when < 10 minutes remain
|
|
112
|
-
_DEFAULT_WORKSPACE_ID = "
|
|
112
|
+
_DEFAULT_WORKSPACE_ID = "7649231955045072915" # hardcoded spaceID fallback
|
|
113
113
|
|
|
114
114
|
|
|
115
115
|
# --- coze-context parsing -------------------------------------------------
|
|
@@ -212,16 +212,41 @@ def _make_finish_event_processor(upload_events: Optional[List[str]] = None):
|
|
|
212
212
|
logid = _extract_logid(detail)
|
|
213
213
|
if logid:
|
|
214
214
|
print(f"[CozeLoop] 上报失败 logid={logid} (可用 bytedcli log get-logid-log {logid} 排查)", file=sys.stderr)
|
|
215
|
+
hook_log(f"upload FAILED logid={logid} detail={detail[:300]}")
|
|
215
216
|
else:
|
|
216
217
|
print(f"[CozeLoop] 上报失败: {detail[:300]}", file=sys.stderr)
|
|
218
|
+
hook_log(f"upload FAILED detail={detail[:300]}")
|
|
217
219
|
except Exception:
|
|
218
220
|
pass
|
|
219
221
|
return _processor
|
|
220
222
|
|
|
221
223
|
|
|
222
224
|
|
|
225
|
+
def _log_file_path() -> str:
|
|
226
|
+
return os.environ.get("COZELOOP_HOOK_LOG", "").strip()
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def hook_log(message: str):
|
|
230
|
+
"""Append one diagnostic line to the hook log, if configured.
|
|
231
|
+
|
|
232
|
+
onboard writes COZELOOP_HOOK_LOG into settings.local.json (env), so this
|
|
233
|
+
captures runtime upload diagnostics even when stderr is not visible.
|
|
234
|
+
"""
|
|
235
|
+
log_path = _log_file_path()
|
|
236
|
+
if not log_path:
|
|
237
|
+
return
|
|
238
|
+
try:
|
|
239
|
+
p = Path(log_path).expanduser()
|
|
240
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
241
|
+
with p.open("a", encoding="utf-8") as f:
|
|
242
|
+
f.write(f"{datetime.now().isoformat()} {message}\n")
|
|
243
|
+
except Exception:
|
|
244
|
+
pass
|
|
245
|
+
|
|
246
|
+
|
|
223
247
|
def debug_log(message: str):
|
|
224
|
-
"""Print debug message if debug mode is enabled."""
|
|
248
|
+
"""Print debug message if debug mode is enabled; always append to hook log."""
|
|
249
|
+
hook_log(f"DEBUG {message}")
|
|
225
250
|
if DEBUG:
|
|
226
251
|
print(f"[COZELOOP_HOOK_DEBUG] {datetime.now().isoformat()} - {message}", file=sys.stderr)
|
|
227
252
|
|
|
@@ -258,8 +283,6 @@ def _refresh_token(refresh_token: str) -> Optional[str]:
|
|
|
258
283
|
data=payload,
|
|
259
284
|
headers={
|
|
260
285
|
"Content-Type": "application/json",
|
|
261
|
-
"x-tt-env": "ppe_cozelab",
|
|
262
|
-
"x-use-ppe": "1",
|
|
263
286
|
},
|
|
264
287
|
)
|
|
265
288
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
@@ -281,16 +304,22 @@ def _refresh_token(refresh_token: str) -> Optional[str]:
|
|
|
281
304
|
|
|
282
305
|
def _normalize_api_base_url(url: str) -> str:
|
|
283
306
|
base = (url or "").strip().rstrip("/")
|
|
284
|
-
if base
|
|
285
|
-
return base
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
307
|
+
if not base:
|
|
308
|
+
return base
|
|
309
|
+
# 剥除已知 loop 路径后缀,还原纯 API base。
|
|
310
|
+
# 关键:含 /api 的变体必须连 /api 整体剥掉,否则残留 /api 会拼出 /api/v1/loop/... → 后端 400。
|
|
311
|
+
# 顺序:更长 / 带 /api 的在前,裸 /v1 最后,避免误匹配短后缀。
|
|
312
|
+
suffixes = (
|
|
313
|
+
"/api/v1/loop/opentelemetry/v1/traces",
|
|
314
|
+
_OTEL_SUFFIX + "/v1/traces",
|
|
315
|
+
"/api/v1/loop/opentelemetry",
|
|
316
|
+
_OTEL_SUFFIX,
|
|
317
|
+
"/api/v1",
|
|
318
|
+
"/v1",
|
|
319
|
+
)
|
|
320
|
+
for suffix in suffixes:
|
|
321
|
+
if base.endswith(suffix):
|
|
322
|
+
return base[:-len(suffix)].rstrip("/")
|
|
294
323
|
return base
|
|
295
324
|
|
|
296
325
|
def get_api_base_url() -> str:
|
|
@@ -970,6 +999,11 @@ def send_turns_to_cozeloop(turns: List[Dict[str, Any]], session_id: str, history
|
|
|
970
999
|
api_base_url = get_api_base_url()
|
|
971
1000
|
if api_base_url:
|
|
972
1001
|
client_kwargs["api_base_url"] = api_base_url
|
|
1002
|
+
hook_log(
|
|
1003
|
+
f"init client session={session_id} workspace={workspace_id} "
|
|
1004
|
+
f"token={(token[:12] + '...') if token else 'none'} "
|
|
1005
|
+
f"api_base_url={api_base_url or '(default)'}"
|
|
1006
|
+
)
|
|
973
1007
|
client = cozeloop.new_client(**client_kwargs)
|
|
974
1008
|
|
|
975
1009
|
try:
|
|
@@ -42,10 +42,10 @@ from pathlib import Path
|
|
|
42
42
|
from typing import Optional, List, Dict, Any
|
|
43
43
|
|
|
44
44
|
# --- Token refresh --------------------------------------------------------
|
|
45
|
-
_COZELOOP_CLIENT_ID = "
|
|
45
|
+
_COZELOOP_CLIENT_ID = "08972682140163281554629748278108.app.coze"
|
|
46
46
|
_COZE_API = "https://api.coze.cn"
|
|
47
47
|
_REFRESH_THRESHOLD = 10 * 60
|
|
48
|
-
_DEFAULT_WORKSPACE_ID = "
|
|
48
|
+
_DEFAULT_WORKSPACE_ID = "7649231955045072915" # hardcoded spaceID fallback
|
|
49
49
|
_OTEL_SUFFIX = "/v1/loop/opentelemetry"
|
|
50
50
|
|
|
51
51
|
|
|
@@ -195,8 +195,6 @@ def _refresh_token(refresh_tok: str):
|
|
|
195
195
|
data=payload,
|
|
196
196
|
headers={
|
|
197
197
|
"Content-Type": "application/json",
|
|
198
|
-
"x-tt-env": "ppe_cozelab",
|
|
199
|
-
"x-use-ppe": "1",
|
|
200
198
|
},
|
|
201
199
|
)
|
|
202
200
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
@@ -218,16 +216,22 @@ def _refresh_token(refresh_tok: str):
|
|
|
218
216
|
|
|
219
217
|
def _normalize_api_base_url(url: str) -> str:
|
|
220
218
|
base = (url or "").strip().rstrip("/")
|
|
221
|
-
if base
|
|
222
|
-
return base
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
219
|
+
if not base:
|
|
220
|
+
return base
|
|
221
|
+
# 剥除已知 loop 路径后缀,还原纯 API base。
|
|
222
|
+
# 关键:含 /api 的变体必须连 /api 整体剥掉,否则残留 /api 会拼出 /api/v1/loop/... → 后端 400。
|
|
223
|
+
# 顺序:更长 / 带 /api 的在前,裸 /v1 最后,避免误匹配短后缀。
|
|
224
|
+
suffixes = (
|
|
225
|
+
"/api/v1/loop/opentelemetry/v1/traces",
|
|
226
|
+
_OTEL_SUFFIX + "/v1/traces",
|
|
227
|
+
"/api/v1/loop/opentelemetry",
|
|
228
|
+
_OTEL_SUFFIX,
|
|
229
|
+
"/api/v1",
|
|
230
|
+
"/v1",
|
|
231
|
+
)
|
|
232
|
+
for suffix in suffixes:
|
|
233
|
+
if base.endswith(suffix):
|
|
234
|
+
return base[:-len(suffix)].rstrip("/")
|
|
231
235
|
return base
|
|
232
236
|
|
|
233
237
|
|
|
@@ -5,16 +5,24 @@ import { ATTR_SERVICE_NAME, ATTR_SERVICE_INSTANCE_ID } from "@opentelemetry/sema
|
|
|
5
5
|
import { hostname } from "os";
|
|
6
6
|
import { basename, join } from "path";
|
|
7
7
|
import { createRequire } from "node:module";
|
|
8
|
-
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
8
|
+
import { readFileSync, writeFileSync, mkdirSync, appendFileSync } from "fs";
|
|
9
9
|
import { homedir } from "os";
|
|
10
10
|
import http from "http";
|
|
11
11
|
import https from "https";
|
|
12
12
|
|
|
13
13
|
const require = createRequire(import.meta.url);
|
|
14
14
|
const { version: PLUGIN_VERSION } = require("../package.json");
|
|
15
|
+
const CLIENT_USER_AGENT = {
|
|
16
|
+
version: PLUGIN_VERSION,
|
|
17
|
+
lang: "nodejs",
|
|
18
|
+
lang_version: process.versions.node,
|
|
19
|
+
os_name: process.platform,
|
|
20
|
+
scene: "cozeloop",
|
|
21
|
+
source: "openapi",
|
|
22
|
+
};
|
|
15
23
|
|
|
16
24
|
// ── Token refresh helpers ─────────────────────────────────────────────────
|
|
17
|
-
const _CLIENT_ID = "
|
|
25
|
+
const _CLIENT_ID = "08972682140163281554629748278108.app.coze";
|
|
18
26
|
const _COZE_API = "https://api.coze.cn";
|
|
19
27
|
const _REFRESH_THRESHOLD_MS = 10 * 60 * 1000;
|
|
20
28
|
const _CREDS_PATH = join(homedir(), ".cozeloop", "credentials.json");
|
|
@@ -36,7 +44,7 @@ async function _refreshToken(refreshTok) {
|
|
|
36
44
|
const body = JSON.stringify({ grant_type: "refresh_token", client_id: _CLIENT_ID, refresh_token: refreshTok });
|
|
37
45
|
const req = https.request(`${_COZE_API}/api/permission/oauth2/token`, {
|
|
38
46
|
method: "POST",
|
|
39
|
-
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body)
|
|
47
|
+
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) },
|
|
40
48
|
}, (res) => {
|
|
41
49
|
let buf = "";
|
|
42
50
|
res.on("data", c => buf += c);
|
|
@@ -82,24 +90,41 @@ const EXPORT_SUCCESS = 0;
|
|
|
82
90
|
const EXPORT_FAILED = 1;
|
|
83
91
|
const INGEST_TRACE_PATH = "/v1/loop/traces/ingest";
|
|
84
92
|
|
|
93
|
+
// 把一行诊断写入 onboard 配置的 logFile(pluginConfig.logFile)。云端 gateway 日志常拿不到
|
|
94
|
+
// (无 DBUS / 无 stdout 落盘),这个文件是排查上报失败的唯一依据。无 logFile 或写失败都静默。
|
|
95
|
+
function fileLog(logFile, message) {
|
|
96
|
+
if (!logFile)
|
|
97
|
+
return;
|
|
98
|
+
try {
|
|
99
|
+
appendFileSync(logFile, `${new Date().toISOString()} ${message}\n`);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
/* best-effort, never throw from logging */
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
85
106
|
function normalizeApiBaseUrl(endpoint) {
|
|
86
107
|
const base = String(endpoint || "").replace(/\/+$/, "");
|
|
87
108
|
if (!base)
|
|
88
109
|
return _COZE_API;
|
|
110
|
+
// 剥除已知 loop 路径后缀,还原纯 API base。
|
|
111
|
+
// 关键:cozeloop ingest 正确端点 <host>/v1/loop/traces/ingest,【不带 /api 前缀】。
|
|
112
|
+
// 含 /api 的变体必须连 /api 整体剥掉 —— 否则残留 /api → /api/v1/loop/traces/ingest → 后端 400。
|
|
113
|
+
// 顺序:更长 / 带 /api 的在前,裸 /v1 最后,避免 endsWith 误匹配短后缀。
|
|
89
114
|
const suffixes = [
|
|
90
|
-
"/v1/loop/traces/ingest",
|
|
91
115
|
"/api/v1/loop/traces/ingest",
|
|
92
|
-
"/v1/loop/
|
|
116
|
+
"/v1/loop/traces/ingest",
|
|
93
117
|
"/api/v1/loop/opentelemetry/v1/traces",
|
|
94
|
-
"/v1/loop/opentelemetry",
|
|
118
|
+
"/v1/loop/opentelemetry/v1/traces",
|
|
95
119
|
"/api/v1/loop/opentelemetry",
|
|
120
|
+
"/v1/loop/opentelemetry",
|
|
96
121
|
"/v1/traces",
|
|
97
122
|
"/api/v1",
|
|
123
|
+
"/v1",
|
|
98
124
|
];
|
|
99
125
|
for (const suffix of suffixes) {
|
|
100
126
|
if (base.endsWith(suffix)) {
|
|
101
|
-
|
|
102
|
-
return base.slice(0, -trimSuffix.length).replace(/\/+$/, "");
|
|
127
|
+
return base.slice(0, -suffix.length).replace(/\/+$/, "");
|
|
103
128
|
}
|
|
104
129
|
}
|
|
105
130
|
return base;
|
|
@@ -195,7 +220,7 @@ function spanToUploadSpan(span, config) {
|
|
|
195
220
|
const attrs = span.attributes || {};
|
|
196
221
|
const { tags, systemTags } = splitAttributes(attrs);
|
|
197
222
|
const resourceAttrs = span.resource?.attributes || {};
|
|
198
|
-
const serviceName = resourceAttrs["service.name"] || config.serviceName || "
|
|
223
|
+
const serviceName = resourceAttrs["service.name"] || config.serviceName || "";
|
|
199
224
|
const spanType = mapSpanType(attrs["cozeloop.span_type"]);
|
|
200
225
|
const input = attrs["cozeloop.input"] !== undefined ? safeStringify(attrs["cozeloop.input"]) : "";
|
|
201
226
|
const output = attrs["cozeloop.output"] !== undefined ? safeStringify(attrs["cozeloop.output"]) : "";
|
|
@@ -204,7 +229,6 @@ function spanToUploadSpan(span, config) {
|
|
|
204
229
|
return {
|
|
205
230
|
started_at_micros: hrTimeToUnixMicros(span.startTime),
|
|
206
231
|
log_id: "",
|
|
207
|
-
traceId: spanContext.traceId,
|
|
208
232
|
trace_id: spanContext.traceId,
|
|
209
233
|
span_id: spanContext.spanId,
|
|
210
234
|
parent_id: span.parentSpanId || "0",
|
|
@@ -260,9 +284,11 @@ class CozeloopIngestExporter {
|
|
|
260
284
|
this.url = normalizeIngestUrl(config.endpoint || config.url);
|
|
261
285
|
this.headers = config.headers || {};
|
|
262
286
|
this.logger = config.logger;
|
|
287
|
+
this.logFile = config.logFile;
|
|
263
288
|
this.workspaceId = config.workspaceId;
|
|
264
289
|
this.serviceName = config.serviceName;
|
|
265
290
|
this.shutdownRequested = false;
|
|
291
|
+
fileLog(this.logFile, `[ingest] exporter ready url=${this.url} workspaceId=${this.workspaceId}`);
|
|
266
292
|
}
|
|
267
293
|
export(spans, resultCallback) {
|
|
268
294
|
if (!spans || spans.length === 0 || this.shutdownRequested) {
|
|
@@ -273,6 +299,7 @@ class CozeloopIngestExporter {
|
|
|
273
299
|
.then(() => resultCallback({ code: EXPORT_SUCCESS }))
|
|
274
300
|
.catch((err) => {
|
|
275
301
|
this.logger?.error?.(`[CozeloopTrace] CozeLoop ingest export failed: ${err?.message || err}`);
|
|
302
|
+
fileLog(this.logFile, `[ingest] export FAILED url=${this.url} spans=${spans.length} err=${err?.message || err}`);
|
|
276
303
|
resultCallback({ code: EXPORT_FAILED, error: err });
|
|
277
304
|
});
|
|
278
305
|
}
|
|
@@ -283,15 +310,18 @@ class CozeloopIngestExporter {
|
|
|
283
310
|
serviceName: this.serviceName,
|
|
284
311
|
})),
|
|
285
312
|
};
|
|
313
|
+
fileLog(this.logFile, `[ingest] POST url=${this.url} spans=${body.spans.length}`);
|
|
286
314
|
const res = await postJson(this.url, body, this.headers);
|
|
287
315
|
if (res.status < 200 || res.status >= 300) {
|
|
288
316
|
const snippet = String(res.body || "").slice(0, 300);
|
|
317
|
+
fileLog(this.logFile, `[ingest] HTTP ${res.status} url=${this.url}${snippet ? ` body=${snippet}` : ""}`);
|
|
289
318
|
throw new Error(`HTTP ${res.status}${snippet ? `: ${snippet}` : ""}`);
|
|
290
319
|
}
|
|
291
320
|
if (res.body) {
|
|
292
321
|
try {
|
|
293
322
|
const parsed = JSON.parse(res.body);
|
|
294
323
|
if (parsed && parsed.code !== undefined && parsed.code !== 0) {
|
|
324
|
+
fileLog(this.logFile, `[ingest] biz-error code=${parsed.code} msg=${parsed.msg || ""} url=${this.url}`);
|
|
295
325
|
throw new Error(`code ${parsed.code}: ${parsed.msg || res.body.slice(0, 300)}`);
|
|
296
326
|
}
|
|
297
327
|
}
|
|
@@ -301,6 +331,7 @@ class CozeloopIngestExporter {
|
|
|
301
331
|
}
|
|
302
332
|
}
|
|
303
333
|
}
|
|
334
|
+
fileLog(this.logFile, `[ingest] OK HTTP ${res.status} spans=${body.spans.length}`);
|
|
304
335
|
}
|
|
305
336
|
async forceFlush() {
|
|
306
337
|
return;
|
|
@@ -405,15 +436,17 @@ export class CozeloopExporter {
|
|
|
405
436
|
const authorization = this.config.authorization;
|
|
406
437
|
const workspaceId = this.config.workspaceId;
|
|
407
438
|
this.api.logger.info(`[CozeloopTrace] Using authorization, workspaceId=${workspaceId}, tokenLength=${authorization?.length}`);
|
|
439
|
+
fileLog(this.config.logFile, `[init] ingestUrl=${normalizeIngestUrl(this.config.endpoint)} workspaceId=${workspaceId} tokenLength=${authorization?.length || 0} plugin=${PLUGIN_VERSION}`);
|
|
408
440
|
const exporter = new CozeloopIngestExporter({
|
|
409
441
|
endpoint: this.config.endpoint,
|
|
410
442
|
logger: this.api.logger,
|
|
443
|
+
logFile: this.config.logFile,
|
|
411
444
|
workspaceId,
|
|
412
445
|
serviceName: this.config.serviceName,
|
|
413
446
|
headers: {
|
|
414
447
|
"Authorization": authorization,
|
|
415
|
-
"
|
|
416
|
-
"
|
|
448
|
+
"User-Agent": `openclaw-cozeloop-trace/${PLUGIN_VERSION} node/${process.versions.node}`,
|
|
449
|
+
"X-Coze-Client-User-Agent": JSON.stringify(CLIENT_USER_AGENT),
|
|
417
450
|
},
|
|
418
451
|
});
|
|
419
452
|
this.provider = new BasicTracerProvider({ resource });
|
|
@@ -473,10 +506,11 @@ export class CozeloopExporter {
|
|
|
473
506
|
const runtimeTag = {
|
|
474
507
|
language: "nodejs",
|
|
475
508
|
library: "openclaw",
|
|
509
|
+
scene: process.env.COZELOOP_SCENE || "custom",
|
|
510
|
+
library_version: null,
|
|
511
|
+
loop_sdk_version: `v${PLUGIN_VERSION}`,
|
|
512
|
+
extra: null,
|
|
476
513
|
};
|
|
477
|
-
if (process.env.COZELOOP_SCENE) {
|
|
478
|
-
runtimeTag.scene = process.env.COZELOOP_SCENE;
|
|
479
|
-
}
|
|
480
514
|
const systemTagRuntime = JSON.stringify(runtimeTag);
|
|
481
515
|
const span = this.tracer.startSpan(spanData.name, {
|
|
482
516
|
kind: spanKind,
|
|
@@ -579,10 +613,11 @@ export class CozeloopExporter {
|
|
|
579
613
|
const runtimeTag = {
|
|
580
614
|
language: "nodejs",
|
|
581
615
|
library: "openclaw",
|
|
616
|
+
scene: process.env.COZELOOP_SCENE || "custom",
|
|
617
|
+
library_version: null,
|
|
618
|
+
loop_sdk_version: `v${PLUGIN_VERSION}`,
|
|
619
|
+
extra: null,
|
|
582
620
|
};
|
|
583
|
-
if (process.env.COZELOOP_SCENE) {
|
|
584
|
-
runtimeTag.scene = process.env.COZELOOP_SCENE;
|
|
585
|
-
}
|
|
586
621
|
const systemTagRuntime = JSON.stringify(runtimeTag);
|
|
587
622
|
const span = this.tracer.startSpan(spanData.name, {
|
|
588
623
|
kind: spanKind,
|
|
@@ -534,6 +534,7 @@ const cozeloopTracePlugin = {
|
|
|
534
534
|
batchInterval: pluginConfig.batchInterval || 5000,
|
|
535
535
|
enabledHooks: pluginConfig.enabledHooks,
|
|
536
536
|
disableLocalCredentials: pluginConfig.disableLocalCredentials === true,
|
|
537
|
+
logFile: pluginConfig.logFile,
|
|
537
538
|
};
|
|
538
539
|
const exporter = new CozeloopExporter(api, config);
|
|
539
540
|
// per-agent trace 放行:traceAgentIds 为 onboard 写入的 allowlist(小写归一)。
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "openclaw-cozeloop-trace",
|
|
3
3
|
"name": "OpenClaw CozeLoop Trace",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.18",
|
|
5
5
|
"description": "Report OpenClaw execution traces to CozeLoop via OpenTelemetry",
|
|
6
6
|
"type": "plugin",
|
|
7
7
|
"entry": "./dist/index.js",
|
|
@@ -54,6 +54,11 @@
|
|
|
54
54
|
"type": "boolean",
|
|
55
55
|
"default": false,
|
|
56
56
|
"description": "Disable ~/.cozeloop credentials refresh and use configured authorization only"
|
|
57
|
+
},
|
|
58
|
+
"logFile": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"default": "",
|
|
61
|
+
"description": "Append trace ingest diagnostics (URL, span count, HTTP status, failure body) to this file"
|
|
57
62
|
}
|
|
58
63
|
}
|
|
59
64
|
}
|
|
@@ -7,7 +7,7 @@ Writes the fresh token back so subsequent Stop hooks pick it up.
|
|
|
7
7
|
import json, os, sys, time, urllib.request
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
-
CLIENT_ID = "
|
|
10
|
+
CLIENT_ID = "08972682140163281554629748278108.app.coze"
|
|
11
11
|
COZE_API = "https://api.coze.cn"
|
|
12
12
|
THRESHOLD = 10 * 60 # 10 minutes
|
|
13
13
|
CREDS = Path.home() / ".cozeloop" / "credentials.json"
|
|
@@ -27,8 +27,6 @@ def refresh(rt):
|
|
|
27
27
|
req = urllib.request.Request(f"{COZE_API}/api/permission/oauth2/token",
|
|
28
28
|
data=body, headers={
|
|
29
29
|
"Content-Type":"application/json",
|
|
30
|
-
"x-tt-env":"ppe_cozelab",
|
|
31
|
-
"x-use-ppe":"1",
|
|
32
30
|
})
|
|
33
31
|
with urllib.request.urlopen(req, timeout=10) as r:
|
|
34
32
|
d = json.loads(r.read())
|