@lark-codem/codem-cli 0.1.238-dev-answer.100102807620.0.g6c5c7f5 → 0.1.238-dev.100102827515.0.g871f708

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.
@@ -1,374 +0,0 @@
1
- import { createHash, randomUUID } from "node:crypto";
2
- import os from "node:os";
3
- import fs from "node:fs";
4
- import path from "node:path";
5
- import { diagnosticId } from "./diagnostic-id.js";
6
- import { readDeviceId } from "./device-identity.js";
7
- import { sendSlardarPcBatch, SLARDAR_PC_AID, SLARDAR_PC_REPORTER_VERSION } from "./slardar-pc-transport.js";
8
-
9
- const CAPACITY = 256;
10
- const BATCH_SIZE = 32;
11
- const INTERVAL_MS = 10_000;
12
- const REQUEST_TIMEOUT_MS = 3_000;
13
- const STRING_FIELDS = new Set([
14
- "request_id", "command_id", "session_id", "task_id", "dispatch_id", "event_id", "sync_id",
15
- "process_instance_id", "run_id", "stage", "phase", "status", "outcome", "reason", "action",
16
- "error_class", "error_code", "error_kind", "error_type", "code", "signal", "stop_reason",
17
- "terminal_event", "event_type", "actor_type", "source", "trigger", "hook_id", "hook_event",
18
- "target_version", "cli_version", "core_version", "version", "previous_state", "state",
19
- "failure_kind", "failure_impact", "failure_type", "failure_subtype", "failure_code", "failure_status", "delay_source", "mode", "provider", "name", "termination", "interaction_id", "interaction_kind", "owner_state", "artifact_id", "resource_type", "delivery_mode", "host", "scheme", "tool_call_id", "tool_name", "background_task_id", "model", "artifact_kind", "codem_user_id", "device_id", "operation", "foreground_error_code"
20
- ]);
21
- const VALUE_FIELDS = new Set([
22
- "pid", "exit_code", "duration_ms", "elapsed_ms", "wait_ms", "run_ms", "total_ms", "took_ms",
23
- "attempt", "attempts", "attempt_count", "retryable", "http_status", "active_task_count",
24
- "pending_event_count", "pending_command_ack_count", "next_retry_ms", "consecutive_failures",
25
- "outage_duration_ms", "startup_duration_ms", "failed_attempts", "active_runs", "completed",
26
- "delivered", "result_delivered", "queued_behind_existing", "expected", "timed_out",
27
- "force_exit_ms", "teardown_ceiling_ms", "recovered", "count", "ms_since_process_start", "owner_pid", "consumed", "delivery_attempt", "ack_deadline_ms", "command_to_bridge_ms", "size_bytes", "before", "after", "omitted_detail_count", "opened", "selected", "foreground"
28
- ]);
29
-
30
- function token(value) {
31
- return typeof value === "string" && /^[a-z\d._:-]{1,128}$/iu.test(value.trim())
32
- ? value.trim() : undefined;
33
- }
34
-
35
- function fieldName(key) {
36
- return key.replace(/[A-Z]/gu, (letter) => `_${letter.toLowerCase()}`);
37
- }
38
-
39
- function optional(read) {
40
- try { return read(); } catch { return undefined; }
41
- }
42
-
43
- function projectFields(input) {
44
- const result = {};
45
- for (const [key, value] of Object.entries(input ?? {}).slice(0, 100)) {
46
- const name = fieldName(key);
47
- if (STRING_FIELDS.has(name)) {
48
- const safe = token(value);
49
- if (safe) result[name] = safe;
50
- } else if (VALUE_FIELDS.has(name) && (typeof value === "boolean" || (typeof value === "number" && Number.isFinite(value)))) {
51
- result[name] = value;
52
- }
53
- }
54
- const error = input?.error ?? input?.terminalError;
55
- const terminal = input?.terminalError ?? error?.terminalError;
56
- if (terminal && typeof terminal === "object") {
57
- if (token(terminal.code)) result.error_code = token(terminal.code);
58
- if (token(terminal.kind)) result.error_kind = token(terminal.kind);
59
- }
60
- if (error) {
61
- if (typeof error === "object") {
62
- if (token(error.name)) result.error_type = token(error.name);
63
- if (token(error.code)) result.error_code = token(error.code);
64
- if (token(error.kind)) result.error_kind = token(error.kind);
65
- }
66
- // Errors can contain prompts, paths and raw upstream response bodies.
67
- // Retain classification + a fingerprint, never their arbitrary text.
68
- const message = typeof error === "string" ? error : error.message;
69
- if (typeof message === "string") result.error_fingerprint = createHash("sha256").update(message.slice(0, 4096)).digest("hex").slice(0, 24);
70
- }
71
- return result;
72
- }
73
-
74
- export function runtimeLogIdentity(config) {
75
- const oauth = optional(() => config?.auth?.oauth);
76
- return {
77
- deviceId: optional(() => token(readDeviceId(config))),
78
- // open_id and lastLoginUser are different identities / historical owners.
79
- userId: optional(() => token(oauth?.slardarUserId)),
80
- tenantId: optional(() => token(oauth?.tenantId ?? oauth?.tenant_id)),
81
- channel: optional(() => token(config?.channel)),
82
- serverEnvironment: optional(() => typeof config?.serverUrl === "string" && /(?:^|[./-])boe(?:[./-]|$)/iu.test(config.serverUrl) ? "boe" : undefined)
83
- };
84
- }
85
-
86
- export class RuntimeLogReporter {
87
- #live = { queue: [], inflight: undefined, abort: undefined };
88
- #replay = { queue: [], inflight: undefined, abort: undefined };
89
- #identity = {};
90
- #timer;
91
- #closed = false;
92
- #dropped = 0;
93
- #send;
94
- #role;
95
- #version;
96
- #instanceId;
97
- #diagnosticId;
98
- #platform;
99
- #osVersion;
100
- #crashDirectory;
101
-
102
- constructor({ role = "cli", version = "unknown", send = sendSlardarPcBatch, instanceId = randomUUID(), diagnosticId: supportId = diagnosticId(), platform = process.platform, osVersion = os.release(), crashDirectory } = {}) {
103
- this.#send = send;
104
- this.#crashDirectory = crashDirectory;
105
- this.#role = token(role) ?? "cli";
106
- this.#version = token(version) ?? "unknown";
107
- this.#instanceId = instanceId;
108
- this.#diagnosticId = supportId;
109
- this.#platform = platform === "win32" ? "Windows" : platform === "darwin" ? "Mac OS X" : platform;
110
- this.#osVersion = osVersion;
111
- this.#timer = setInterval(() => { void this.flush(); }, INTERVAL_MS);
112
- this.#timer.unref?.();
113
- this.#restoreCrashes();
114
- }
115
-
116
- // Crash files hold already-projected records and the identity at failure time.
117
- // Never await network IO in uncaughtExceptionMonitor. Replay is at-least-once.
118
- persistCrash(component, event, fields = {}) {
119
- try {
120
- if (!this.#crashDirectory || this.#closed) return;
121
- if (!this.record("fatal", component, event, fields)) return;
122
- const entry = this.#live.queue.pop();
123
- fs.mkdirSync(this.#crashDirectory, { recursive: true, mode: 0o700 });
124
- const names = fs.readdirSync(this.#crashDirectory).filter(name => /^js-[a-f0-9-]+\.json$/u.test(name));
125
- if (names.length >= 32) return;
126
- fs.writeFileSync(path.join(this.#crashDirectory, `js-${randomUUID()}.json`), JSON.stringify(entry), { flag: "wx", mode: 0o600 });
127
- } catch { /* Crash diagnostics must preserve the original exit behavior. */ }
128
- }
129
-
130
- #restoreCrashes() {
131
- try {
132
- for (const name of fs.readdirSync(this.#crashDirectory).filter(name => /^js-[a-f0-9-]+\.json$/u.test(name)).slice(0, 32)) {
133
- const file = path.join(this.#crashDirectory, name);
134
- try {
135
- if (fs.lstatSync(file).isSymbolicLink() || fs.statSync(file).size > 65536) continue;
136
- const entry = JSON.parse(fs.readFileSync(file, "utf8"));
137
- if (entry?.record?.category?.reporter !== "codem_js" || entry?.record?.log_type !== "log" || !entry.header) continue;
138
- this.#replay.queue.push({ header: entry.header, record: entry.record, attempts: 0, crashFile: file });
139
- } catch { /* A partial crash write cannot prevent startup. */ }
140
- }
141
- } catch { /* First startup or unavailable storage. */ }
142
- }
143
-
144
- observeConfig(config) {
145
- // A sandbox may read the launcher profile for credentials. That profile is
146
- // not the tenant/device owning the sandbox's runtime diagnostics.
147
- if (this.#role === "sandbox") return;
148
- try { this.#identity = runtimeLogIdentity(config); } catch { this.#identity = {}; }
149
- }
150
-
151
- observeSandboxConfig(config) {
152
- this.#role = "sandbox";
153
- this.#identity = {
154
- deviceId: optional(() => token(config?.deviceId)),
155
- tenantId: optional(() => token(config?.tenantId ?? config?.tenantKey)),
156
- serverEnvironment: runtimeLogIdentity(config).serverEnvironment
157
- };
158
- // ownerUserKey is not a Slardar UID. Keep user_id anonymous in this scope.
159
- }
160
-
161
- get pendingCount() { return this.#live.queue.length + this.#replay.queue.length; }
162
- get droppedCount() { return this.#dropped; }
163
-
164
- record(level, component, event, fields = {}) {
165
- try {
166
- if (this.#closed) return false;
167
- let safeFields;
168
- try { safeFields = projectFields(fields); } catch { safeFields = {}; }
169
- const severity = ["info", "warn", "error", "fatal"].includes(level) ? level : "info";
170
- const identity = { ...this.#identity };
171
- const category = {
172
- client_kind: "cli", runtime_role: this.#role, reporter: "codem_js", log_schema: "1",
173
- component: token(component) ?? "unknown", event: token(event) ?? "unknown", severity,
174
- identity_source: identity.deviceId ? "codem" : "process",
175
- diagnostic_id: this.#diagnosticId,
176
- ...(identity.tenantId ? { tenant_id: identity.tenantId } : {}),
177
- ...(identity.serverEnvironment ? { server_environment: identity.serverEnvironment } : {}),
178
- ...Object.fromEntries(["stage", "error_class", "error_code", "error_kind", "outcome"].filter((key) => safeFields[key]).map((key) => [key, safeFields[key]]))
179
- };
180
- const header = {
181
- aid: SLARDAR_PC_AID,
182
- device_id: identity.deviceId ?? this.#instanceId,
183
- user_id: identity.userId ?? "0",
184
- parfait_version: SLARDAR_PC_REPORTER_VERSION,
185
- channel: identity.channel ?? "unknown",
186
- os: this.#platform, os_version: this.#osVersion
187
- };
188
- const timestamp = Date.now();
189
- const payload = {
190
- schema_version: 1, timestamp, component: category.component, event: category.event, level: severity,
191
- process_instance_id: this.#instanceId, diagnostic_id: this.#diagnosticId, pid: process.pid,
192
- ...(identity.tenantId ? { tenant_id: identity.tenantId } : {}),
193
- fields: safeFields
194
- };
195
- const record = {
196
- app_version: this.#version, app_minor_version: "", build_id: "",
197
- process_type: `codem-${this.#role}`, session_id: "", timestamp, category,
198
- log_type: "log", log_level: severity === "fatal" ? "error" : severity,
199
- log_content: `CodeM diagnostic ${severity} ${category.component}/${category.event}\n${JSON.stringify(payload)}`
200
- };
201
- if (this.#live.queue.length >= CAPACITY) {
202
- const expendable = this.#live.queue.findIndex((entry) => entry.record.log_level === "info");
203
- this.#live.queue.splice(expendable >= 0 ? expendable : 0, 1);
204
- this.#dropped += 1;
205
- }
206
- this.#live.queue.push({ header, record, attempts: 0 });
207
- return true;
208
- } catch { return false; }
209
- }
210
-
211
- async flush({ timeoutMs = REQUEST_TIMEOUT_MS } = {}) {
212
- const deadline = Date.now() + timeoutMs;
213
- const live = this.#flushLane(this.#live, timeoutMs).then(() => {
214
- // Cover records added after drain's last queue check but before its
215
- // promise clears. Deferred retries still wait for the regular timer.
216
- const remaining = deadline - Date.now();
217
- if (remaining > 0 && this.#live.queue.some(entry => entry.attempts === 0)) {
218
- return this.#flushLane(this.#live, remaining);
219
- }
220
- });
221
- // Run both lanes concurrently: old replay cannot delay live sends. Waiting
222
- // for both within one budget also gives short-lived invocations a chance
223
- // to finish replay; a timeout leaves its original crash file for next time.
224
- const replay = this.#flushLane(this.#replay, REQUEST_TIMEOUT_MS);
225
- let timer;
226
- try {
227
- await Promise.race([
228
- Promise.all([live, replay]),
229
- new Promise(resolve => { timer = setTimeout(resolve, timeoutMs); timer.unref?.(); })
230
- ]);
231
- } finally { clearTimeout(timer); }
232
- }
233
-
234
- #flushLane(lane, timeoutMs) {
235
- if (lane.inflight) return lane.inflight;
236
- if (this.#closed || lane.queue.length === 0) return Promise.resolve();
237
- lane.inflight = this.#drain(lane, timeoutMs).catch(() => {}).finally(() => { lane.inflight = undefined; });
238
- return lane.inflight;
239
- }
240
-
241
- async #drain(lane, timeoutMs) {
242
- const retry = [];
243
- const controller = new AbortController();
244
- lane.abort = controller;
245
- let expire;
246
- const deadline = new Promise((_, reject) => {
247
- expire = setTimeout(() => { controller.abort(); reject(new Error("Slardar upload timed out")); }, timeoutMs);
248
- expire.unref?.();
249
- });
250
- try {
251
- while (lane.queue.length && !this.#closed) {
252
- const first = lane.queue[0];
253
- const key = JSON.stringify(first.header);
254
- const batch = [];
255
- while (batch.length < BATCH_SIZE && lane.queue.length && JSON.stringify(lane.queue[0].header) === key && Boolean(lane.queue[0].crashFile) === Boolean(first.crashFile)) batch.push(lane.queue.shift());
256
- const data = batch.flatMap(({ record }) => [record, {
257
- app_version: record.app_version, app_minor_version: "", build_id: "",
258
- process_type: record.process_type, session_id: "", timestamp: record.timestamp,
259
- log_type: "flexible", event_name: "codem_runtime_diagnostic", category: record.category, metrics: { count: 1 }
260
- }]);
261
- try {
262
- await Promise.race([Promise.resolve().then(() => this.#send({ header: first.header, data }, { signal: controller.signal })), deadline]);
263
- for (const entry of batch) {
264
- if (entry.crashFile) { try { fs.unlinkSync(entry.crashFile); } catch { /* Replay may duplicate after a crash or concurrent startup. */ } }
265
- }
266
- } catch {
267
- // One later retry, on the regular interval. Bound both memory and work;
268
- // an unavailable collector must not create a retry loop or block exit.
269
- for (const entry of batch) {
270
- if (!this.#closed && entry.attempts === 0 && retry.length + lane.queue.length < CAPACITY) {
271
- entry.attempts += 1;
272
- retry.push(entry);
273
- } else this.#dropped += 1;
274
- }
275
- if (controller.signal.aborted) break;
276
- }
277
- }
278
- } finally {
279
- if (!this.#closed) lane.queue.push(...retry.slice(0, Math.max(0, CAPACITY - lane.queue.length)));
280
- clearTimeout(expire);
281
- lane.abort = undefined;
282
- }
283
- }
284
-
285
- close() {
286
- this.#closed = true;
287
- clearInterval(this.#timer);
288
- for (const lane of [this.#live, this.#replay]) {
289
- lane.abort?.abort();
290
- lane.queue.length = 0;
291
- }
292
- }
293
- }
294
-
295
- let active;
296
-
297
- export function startRuntimeLogs({ role, version, env = process.env, send } = {}) {
298
- try {
299
- if (active || /^(1|true|yes)$/iu.test(env.CODEM_TELEMETRY_DISABLED ?? "") || env.NODE_TEST_CONTEXT) return;
300
- const root = env.CODEM_HOME || env.LINCO_HOME || path.join(os.homedir(), ".codem");
301
- active = new RuntimeLogReporter({ role, version, send, crashDirectory: path.join(root, "runtime-crashes") });
302
- } catch { /* Monitoring initialization never prevents a command from running. */ }
303
- }
304
-
305
- export function observeRuntimeLogConfig(config) {
306
- try { active?.observeConfig(config); } catch { /* best effort */ }
307
- }
308
-
309
- export function observeRuntimeLogSandboxConfig(config) {
310
- try { active?.observeSandboxConfig(config); } catch { /* best effort */ }
311
- }
312
-
313
- export function recordRuntimeLog(level, component, event, fields = {}) {
314
- try { return active?.record(level, component, event, fields) ?? false; } catch { return false; }
315
- }
316
-
317
- export function persistRuntimeCrash(component, event, fields) {
318
- try { active?.persistCrash(component, event, fields); } catch { /* best effort */ }
319
- }
320
-
321
- export async function flushRuntimeLogs(budgetMs = 200) {
322
- if (!active) return;
323
- let timeout;
324
- try {
325
- await Promise.race([
326
- active.flush({ timeoutMs: budgetMs }),
327
- new Promise((resolve) => { timeout = setTimeout(resolve, budgetMs); })
328
- ]);
329
- } catch { /* best effort, including a broken uploader */ } finally { clearTimeout(timeout); }
330
- }
331
-
332
- // Existing debug sites contain both useful operational events and user data.
333
- // Observe the client's final payload; this does not assert server delivery.
334
- export function recordPreparedArtifacts(component, artifacts, context = {}) {
335
- try {
336
- const limit = 32;
337
- recordRuntimeLog("info", component, "artifact.delivery_prepared", { ...context, count: artifacts.length, omitted_detail_count: Math.max(0, artifacts.length - limit) });
338
- for (const artifact of artifacts.slice(0, limit)) {
339
- const file = artifact?.file;
340
- const uri = artifact?.url?.uri ?? file?._downloadUrl ?? artifact?.image?.uri;
341
- let urlFields = {};
342
- try {
343
- const parsed = new URL(uri);
344
- urlFields = { host: parsed.hostname, scheme: parsed.protocol.replace(/:$/u, "") };
345
- } catch { /* Local references have no remote URL. */ }
346
- const route = file?._localArtifactRef ? "local_relay"
347
- : file?._artifactId || artifact?._imgKey ? "uploaded"
348
- : file?._data || artifact?.image?._data ? "inline"
349
- : artifact?.kind === "url" || urlFields.host ? "url" : "reference";
350
- recordRuntimeLog("info", component, "artifact.delivery_item", {
351
- ...context, artifact_kind: artifact?.kind, delivery_mode: route,
352
- artifact_id: file?._artifactId ?? file?._localArtifactRef ?? artifact?._imgKey,
353
- ...urlFields
354
- });
355
- }
356
- } catch { /* Diagnostics cannot interrupt delivery. */ }
357
- }
358
-
359
- export function recordInteractiveProcessExit({ exitCode, signal }) {
360
- const abnormalSignal = signal && signal !== "SIGINT" && signal !== "SIGTERM";
361
- const failed = abnormalSignal || (exitCode && exitCode !== 130);
362
- recordRuntimeLog(failed ? "error" : "info", "cli", "process.exited", { exitCode, signal });
363
- }
364
-
365
- // Select events explicitly, then project fields again at the remote boundary.
366
- export function recordSelectedRuntimeLog(component, event, fields = {}) {
367
- try {
368
- const selectedEvent = event.replace(/^linco\./u, "");
369
- if (!/^(?:startup\.(?:main|update_check|daemon_service_reconcile|login_gate|login_handoff_to_linco)|oauth\.refresh\.(?:relogin_suspect|persist_failed|persisted)|managed_config\.(?:member_refresh_failed|sandbox_refresh_failed|sandbox_fetch_recovered|sandbox_fetch_exhausted|refreshed|resource_failed)|(?:agent|command)\.(?:start|interactive_start|success|error|spawn_error|resolve_error|exit_drain_timeout|cancelled_before_spawn|artifact_oversize|artifact_missing|artifact_dropped|artifact_route_selected|artifact_image_upload|artifact_file_upload|artifact_local_register)|router\.credentials\.(?:ensured|refresh_failed)|binary\.heal_chmod_failed|project\.(?:inject_error|state)|(?:permission_ask|user_question|plan)\.report_failed|process_tree\.(?:force_kill|force_kill_failed)|artifact\.(?:oversize|missing|dropped)|runtime_post\.daemon\.device_rebind_[a-z_]+)$/u.test(selectedEvent)) return;
370
- const expected = optional(() => fields?.errorKind === "user_stop" || fields?.terminalError?.kind === "user_stop" || fields?.exitCode === 130 || fields?.signal === "SIGINT");
371
- const level = expected ? "info" : (fields?.status === "failed" || /(?:failed|error|exhausted|timeout)$/u.test(event)) ? "error" : /(?:suspect|dropped|missing|oversize)$/u.test(event) ? "warn" : "info";
372
- recordRuntimeLog(level, component, event, fields);
373
- } catch { /* best effort */ }
374
- }
@@ -1,38 +0,0 @@
1
- import https from "node:https";
2
- import { gzipSync } from "node:zlib";
3
-
4
- // PC Custom Log / Custom Event wire format, verified against Parfait 1.7.0.0's
5
- // public IUploader callback. This is not the Slardar Web/BID protocol. Keep the
6
- // SDK capture fixture and the platform readback procedure with this adapter.
7
- export const SLARDAR_PC_AID = "1880";
8
- // Use the native envelope's four-part version convention in this legacy field.
9
- // This is our JS adapter version; reporter=codem_js distinguishes it
10
- // from the native Parfait SDK. Do not advertise a native SDK we do not run.
11
- export const SLARDAR_PC_REPORTER_VERSION = "1.0.0.0";
12
- const ENDPOINT = `https://pc-mon.zijieapi.com/monitor_pc/collect/api/pc_log?aid=${SLARDAR_PC_AID}`;
13
-
14
- export function sendSlardarPcBatch(payload, { signal, request = https.request } = {}) {
15
- return new Promise((resolve, reject) => {
16
- const body = gzipSync(Buffer.from(JSON.stringify(payload)));
17
- const req = request(ENDPOINT, {
18
- method: "POST",
19
- signal,
20
- headers: {
21
- "content-type": "application/json",
22
- "content-encoding": "gzip",
23
- "content-length": body.length
24
- }
25
- }, (res) => {
26
- // The collector returns an empty 204. Never buffer arbitrary responses.
27
- res.resume();
28
- res.once("error", reject);
29
- res.once("end", () => {
30
- if (res.statusCode >= 200 && res.statusCode < 300) resolve();
31
- else reject(new Error(`Slardar PC HTTP ${res.statusCode}`));
32
- });
33
- });
34
- req.on("socket", (socket) => socket.unref?.());
35
- req.once("error", reject);
36
- req.end(body);
37
- });
38
- }