editmamei 1.4.0 → 1.5.1
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 -6
- package/dist/bin/editmamei-core-darwin-arm64 +0 -0
- package/dist/bin/editmamei-core-darwin-x64 +0 -0
- package/dist/bin/editmamei-core-win-x64.exe +0 -0
- package/dist/cli/help.js +1 -1
- package/dist/core/server.js +157 -19
- package/dist/core/tool-groups.js +1 -0
- package/dist/core/tool-tiers.js +1 -0
- package/dist/install-channel.js +52 -2
- package/dist/kernel/module-lifecycle.js +113 -26
- package/dist/license/advisory.js +109 -0
- package/dist/license/entitlement.js +34 -6
- package/dist/license/polar-client.js +50 -7
- package/dist/license/store.js +59 -0
- package/dist/skills/editmamei-skill.zip +0 -0
- package/dist/telemetry/activity.js +149 -0
- package/dist/telemetry/client.js +170 -22
- package/dist/telemetry/events.js +48 -1
- package/dist/telemetry/outbox.js +43 -18
- package/dist/tools/document-tools.js +23 -1
- package/dist/update/check.js +24 -10
- package/dist/utils/session-log.js +3 -3
- package/dist/utils/xmp-crs.js +682 -0
- package/dist/version.js +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
export const READ_ONLY_TOOLS = new Set([
|
|
2
|
+
'ps_sequence',
|
|
3
|
+
'ps_batch',
|
|
4
|
+
'ps_ping',
|
|
5
|
+
'ps_overview',
|
|
6
|
+
'ps_list_capabilities',
|
|
7
|
+
'ps_get_preview',
|
|
8
|
+
'ps_get_selection_preview',
|
|
9
|
+
'ps_get_histogram',
|
|
10
|
+
'ps_inspect',
|
|
11
|
+
'ps_read_scene',
|
|
12
|
+
'ps_compare_regions',
|
|
13
|
+
'ps_get_layer_bounds_diff',
|
|
14
|
+
'ps_template_list',
|
|
15
|
+
'ps_template_recall',
|
|
16
|
+
'ps_list_actions',
|
|
17
|
+
'ps_report_problem',
|
|
18
|
+
'ps_detect',
|
|
19
|
+
'ps_detect_landmarks',
|
|
20
|
+
'ps_document',
|
|
21
|
+
'ps_template_verify',
|
|
22
|
+
'ps_resolve_placement',
|
|
23
|
+
'ps_template_save',
|
|
24
|
+
'ps_template_delete',
|
|
25
|
+
'ps_template_create_evidence',
|
|
26
|
+
]);
|
|
27
|
+
export const KEPT_WORK_TOOLS = new Set(['ps_export', 'ps_save_psd']);
|
|
28
|
+
export const MUTATING_TOOLS = new Set([
|
|
29
|
+
'ps_play_action',
|
|
30
|
+
'ps_execute_script',
|
|
31
|
+
'ps_add_adjustment_layer',
|
|
32
|
+
'ps_apply_adjustment',
|
|
33
|
+
'ps_create_document',
|
|
34
|
+
'ps_close_document',
|
|
35
|
+
'ps_open_document',
|
|
36
|
+
'ps_develop_raw',
|
|
37
|
+
'ps_filter',
|
|
38
|
+
'ps_group',
|
|
39
|
+
'ps_clipping_mask',
|
|
40
|
+
'ps_undo',
|
|
41
|
+
'ps_redo',
|
|
42
|
+
'ps_place_image',
|
|
43
|
+
'ps_resize_image',
|
|
44
|
+
'ps_crop_document',
|
|
45
|
+
'ps_convert_image_mode',
|
|
46
|
+
'ps_move_layer_to_position',
|
|
47
|
+
'ps_convert_to_smart_object',
|
|
48
|
+
'ps_rasterize_layer',
|
|
49
|
+
'ps_set_layer',
|
|
50
|
+
'ps_duplicate_layer',
|
|
51
|
+
'ps_copy_to_new_layer',
|
|
52
|
+
'ps_merge',
|
|
53
|
+
'ps_bake_layer',
|
|
54
|
+
'ps_add_layer_style',
|
|
55
|
+
'ps_create_layer',
|
|
56
|
+
'ps_delete_layer',
|
|
57
|
+
'ps_fill_layer',
|
|
58
|
+
'ps_add_fill_layer',
|
|
59
|
+
'ps_select_layer',
|
|
60
|
+
'ps_transform_layer',
|
|
61
|
+
'ps_warp_layer',
|
|
62
|
+
'ps_warp_layer_mesh',
|
|
63
|
+
'ps_warp_layer_along',
|
|
64
|
+
'ps_warp_layer_region',
|
|
65
|
+
'ps_warp_layer_to',
|
|
66
|
+
'ps_apply_camera_raw',
|
|
67
|
+
'ps_transform_canvas',
|
|
68
|
+
'ps_guides',
|
|
69
|
+
'ps_retouch',
|
|
70
|
+
'ps_apply_brush_stroke',
|
|
71
|
+
'ps_edit_object',
|
|
72
|
+
'ps_portrait_touchup',
|
|
73
|
+
'ps_add_text_to_object',
|
|
74
|
+
'ps_select_face_feature',
|
|
75
|
+
'ps_stroke_face_contour',
|
|
76
|
+
'ps_select_by_reference',
|
|
77
|
+
'ps_select',
|
|
78
|
+
'ps_select_subject',
|
|
79
|
+
'ps_select_sky',
|
|
80
|
+
'ps_select_subject_instance',
|
|
81
|
+
'ps_select_object',
|
|
82
|
+
'ps_replace_sky',
|
|
83
|
+
'ps_modify_selection',
|
|
84
|
+
'ps_selection_channel',
|
|
85
|
+
'ps_layer_mask',
|
|
86
|
+
'ps_path',
|
|
87
|
+
'ps_vector_mask',
|
|
88
|
+
'ps_apply_image',
|
|
89
|
+
'ps_calculations',
|
|
90
|
+
'ps_shape',
|
|
91
|
+
'ps_template_apply',
|
|
92
|
+
'ps_text',
|
|
93
|
+
]);
|
|
94
|
+
export function mapClientName(name) {
|
|
95
|
+
if (name === undefined)
|
|
96
|
+
return 'other';
|
|
97
|
+
const n = name.toLowerCase();
|
|
98
|
+
if (n.includes('claude-code') || n.includes('claude_code') || n.includes('claudecode')) {
|
|
99
|
+
return 'claude_code';
|
|
100
|
+
}
|
|
101
|
+
if (n.includes('claude'))
|
|
102
|
+
return 'claude_desktop';
|
|
103
|
+
if (n.includes('cursor'))
|
|
104
|
+
return 'cursor';
|
|
105
|
+
if (n.includes('windsurf'))
|
|
106
|
+
return 'windsurf';
|
|
107
|
+
if (n.includes('vscode') || n.includes('visual studio') || n.includes('code-oss')) {
|
|
108
|
+
return 'vscode';
|
|
109
|
+
}
|
|
110
|
+
return 'other';
|
|
111
|
+
}
|
|
112
|
+
export function parseMajor(version) {
|
|
113
|
+
if (version === undefined)
|
|
114
|
+
return null;
|
|
115
|
+
const m = /^(\d+)/.exec(version.trim());
|
|
116
|
+
return m ? Number(m[1]) : null;
|
|
117
|
+
}
|
|
118
|
+
export function boundMajor(n, max) {
|
|
119
|
+
return n !== null && Number.isFinite(n) && n >= 0 && n <= max ? n : null;
|
|
120
|
+
}
|
|
121
|
+
export function nodeMajor() {
|
|
122
|
+
return parseMajor(process.versions.node);
|
|
123
|
+
}
|
|
124
|
+
export function archToken() {
|
|
125
|
+
if (process.arch === 'x64')
|
|
126
|
+
return 'x64';
|
|
127
|
+
if (process.arch === 'arm64')
|
|
128
|
+
return 'arm64';
|
|
129
|
+
return 'other';
|
|
130
|
+
}
|
|
131
|
+
export function osMajor(platform, release) {
|
|
132
|
+
if (platform === 'win32') {
|
|
133
|
+
const parts = release.split('.');
|
|
134
|
+
const major = Number(parts[0]);
|
|
135
|
+
if (!Number.isFinite(major))
|
|
136
|
+
return null;
|
|
137
|
+
const build = Number(parts[2]);
|
|
138
|
+
return Number.isFinite(build) && build >= 22000 ? 11 : major;
|
|
139
|
+
}
|
|
140
|
+
if (platform === 'darwin') {
|
|
141
|
+
const m = /^(\d+)/.exec(release);
|
|
142
|
+
if (!m)
|
|
143
|
+
return null;
|
|
144
|
+
const darwinMajor = Number(m[1]);
|
|
145
|
+
return darwinMajor <= 24 ? darwinMajor - 9 : darwinMajor + 1;
|
|
146
|
+
}
|
|
147
|
+
const m = /^(\d+)/.exec(release);
|
|
148
|
+
return m ? Number(m[1]) : null;
|
|
149
|
+
}
|
package/dist/telemetry/client.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
+
import { release } from 'node:os';
|
|
1
2
|
import { Logger } from '../utils/logger.js';
|
|
2
3
|
import { EDITION } from '../edition.js';
|
|
3
4
|
import { VERSION } from '../version.js';
|
|
4
5
|
import { resolveInstallChannel } from '../install-channel.js';
|
|
5
|
-
import {
|
|
6
|
+
import { READ_ONLY_TOOLS, KEPT_WORK_TOOLS, nodeMajor, archToken, osMajor, boundMajor, } from './activity.js';
|
|
7
|
+
import { buildClientConnected, buildDiagnosticEvent, buildModuleStatus, buildSessionStart, buildSessionSummary, buildUsageEvent, dayBucket, normalizeDayBucket, isContentSafe, PS_VERSION_UNKNOWN, } from './events.js';
|
|
6
8
|
import { sanitizeMessage, sanitizeSnippet, sanitizeStderrTail } from './sanitize.js';
|
|
7
9
|
import { httpTransport, resolveEndpoint } from './transport.js';
|
|
8
|
-
import { appendOutboxSync, clearOutbox, clearSessionState,
|
|
10
|
+
import { appendOutboxSync, clearOutbox, clearSessionState, readOutboxWithDiscards, readSessionState, rewriteOutbox, writeSessionStateSync, } from './outbox.js';
|
|
9
11
|
const MAX_BATCH_SIZE = 100;
|
|
10
12
|
const MAX_QUEUE_SIZE = 500;
|
|
11
13
|
const DEFAULT_FLUSH_INTERVAL_MS = 5 * 60_000;
|
|
12
14
|
const SESSION_PERSIST_THROTTLE_MS = 10_000;
|
|
15
|
+
const MAX_SESSION_DURATION_S = 604_800;
|
|
16
|
+
const MAX_INSTALL_ASSET_COUNT = 100_000;
|
|
13
17
|
function isTestEnv() {
|
|
14
18
|
return process.env.VITEST !== undefined || process.env.NODE_ENV === 'test';
|
|
15
19
|
}
|
|
@@ -25,12 +29,26 @@ export class TelemetryClient {
|
|
|
25
29
|
active;
|
|
26
30
|
outboxOpts;
|
|
27
31
|
getModuleStatus;
|
|
32
|
+
resolvedModuleStatus = null;
|
|
28
33
|
queue = [];
|
|
29
34
|
timer = null;
|
|
30
35
|
shutdownPromise = null;
|
|
31
36
|
toolCallCount = 0;
|
|
32
37
|
distinctTools = new Set();
|
|
33
38
|
anyFailures = false;
|
|
39
|
+
firstCallAtMs = null;
|
|
40
|
+
lastCallAtMs = null;
|
|
41
|
+
retryCount = 0;
|
|
42
|
+
lastCallSuccess = null;
|
|
43
|
+
editsOk = 0;
|
|
44
|
+
keptWork = 0;
|
|
45
|
+
droppedEvents = 0;
|
|
46
|
+
droppedOutbox = 0;
|
|
47
|
+
droppedUnsafe = 0;
|
|
48
|
+
usageCallsSent = 0;
|
|
49
|
+
behindLatest = null;
|
|
50
|
+
moduleUpdate = 'none';
|
|
51
|
+
installAssets = {};
|
|
34
52
|
lastSessionPersistMs = 0;
|
|
35
53
|
startDayBucket = null;
|
|
36
54
|
constructor(opts) {
|
|
@@ -61,24 +79,98 @@ export class TelemetryClient {
|
|
|
61
79
|
this.timer = setInterval(() => void this.flush(), this.flushIntervalMs);
|
|
62
80
|
this.timer.unref?.();
|
|
63
81
|
if (this.settings.telemetry.usage) {
|
|
64
|
-
|
|
65
|
-
const
|
|
82
|
+
const hostNodeMajor = boundMajor(nodeMajor(), 999);
|
|
83
|
+
const hostOsMajor = boundMajor(osMajor(process.platform, release()), 999);
|
|
84
|
+
this.enqueue(buildSessionStart(this.dims, this.now(), {
|
|
85
|
+
...(hostNodeMajor !== null ? { node_major: hostNodeMajor } : {}),
|
|
86
|
+
arch: archToken(),
|
|
87
|
+
...(hostOsMajor !== null ? { os_major: hostOsMajor } : {}),
|
|
88
|
+
}));
|
|
89
|
+
const moduleStatus = this.moduleStatus();
|
|
66
90
|
if (moduleStatus)
|
|
67
91
|
this.enqueue(buildModuleStatus(this.dims, moduleStatus, this.now()));
|
|
68
92
|
void this.flush();
|
|
69
93
|
}
|
|
70
94
|
}
|
|
95
|
+
recordClientConnected(info) {
|
|
96
|
+
if (!this.active || !this.settings.telemetry.usage)
|
|
97
|
+
return;
|
|
98
|
+
this.enqueue(buildClientConnected(this.dims, info, this.now()));
|
|
99
|
+
}
|
|
71
100
|
recordCall(call) {
|
|
72
101
|
if (!this.active || !this.settings.telemetry.usage)
|
|
73
102
|
return;
|
|
74
103
|
this.ensureStartDayBucket();
|
|
104
|
+
const nowDate = this.now();
|
|
105
|
+
const nowMs = nowDate.getTime();
|
|
75
106
|
this.toolCallCount += 1;
|
|
76
107
|
this.distinctTools.add(call.tool);
|
|
77
108
|
if (!call.success)
|
|
78
109
|
this.anyFailures = true;
|
|
79
|
-
this.
|
|
110
|
+
this.lastCallSuccess = call.success;
|
|
111
|
+
if (call.retry)
|
|
112
|
+
this.retryCount += 1;
|
|
113
|
+
if (call.success) {
|
|
114
|
+
if (!READ_ONLY_TOOLS.has(call.tool))
|
|
115
|
+
this.editsOk += 1;
|
|
116
|
+
if (KEPT_WORK_TOOLS.has(call.tool))
|
|
117
|
+
this.keptWork += 1;
|
|
118
|
+
}
|
|
119
|
+
if (this.firstCallAtMs === null)
|
|
120
|
+
this.firstCallAtMs = nowMs;
|
|
121
|
+
this.lastCallAtMs = nowMs;
|
|
122
|
+
this.enqueue(buildUsageEvent(this.dims, call, nowDate));
|
|
80
123
|
this.persistSessionStateThrottled();
|
|
81
124
|
}
|
|
125
|
+
setBehindLatest(value) {
|
|
126
|
+
this.behindLatest = value;
|
|
127
|
+
}
|
|
128
|
+
setModuleUpdate(outcome) {
|
|
129
|
+
this.moduleUpdate = outcome;
|
|
130
|
+
}
|
|
131
|
+
setInstallAssets(assets) {
|
|
132
|
+
const templatesSaved = assets.templates_saved !== undefined ? clampCount(assets.templates_saved) : null;
|
|
133
|
+
const actionSets = assets.action_sets !== undefined ? clampCount(assets.action_sets) : null;
|
|
134
|
+
this.installAssets = {
|
|
135
|
+
...this.installAssets,
|
|
136
|
+
...(templatesSaved !== null ? { templates_saved: templatesSaved } : {}),
|
|
137
|
+
...(actionSets !== null ? { action_sets: actionSets } : {}),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
moduleStatus() {
|
|
141
|
+
if (this.resolvedModuleStatus === null) {
|
|
142
|
+
this.resolvedModuleStatus = { value: this.getModuleStatus() };
|
|
143
|
+
}
|
|
144
|
+
return this.resolvedModuleStatus.value;
|
|
145
|
+
}
|
|
146
|
+
durationS() {
|
|
147
|
+
if (this.firstCallAtMs === null || this.lastCallAtMs === null)
|
|
148
|
+
return undefined;
|
|
149
|
+
return Math.min(Math.max(0, Math.floor((this.lastCallAtMs - this.firstCallAtMs) / 1000)), MAX_SESSION_DURATION_S);
|
|
150
|
+
}
|
|
151
|
+
summaryFields() {
|
|
152
|
+
const duration = this.durationS();
|
|
153
|
+
const moduleStatus = this.moduleStatus();
|
|
154
|
+
return {
|
|
155
|
+
...(duration !== undefined ? { duration_s: duration } : {}),
|
|
156
|
+
retry_count: this.retryCount,
|
|
157
|
+
...(this.lastCallSuccess !== null ? { ended_after_failure: !this.lastCallSuccess } : {}),
|
|
158
|
+
edits_ok: this.editsOk,
|
|
159
|
+
kept_work: this.keptWork,
|
|
160
|
+
...(this.behindLatest !== null ? { behind_latest: this.behindLatest } : {}),
|
|
161
|
+
dropped_events: this.droppedEvents,
|
|
162
|
+
dropped_outbox: this.droppedOutbox,
|
|
163
|
+
dropped_unsafe: this.droppedUnsafe,
|
|
164
|
+
usage_calls_sent: this.usageCallsSent,
|
|
165
|
+
...(moduleStatus !== null ? { module_update: this.moduleUpdate } : {}),
|
|
166
|
+
...(this.installAssets.templates_saved !== undefined
|
|
167
|
+
? { templates_saved: this.installAssets.templates_saved }
|
|
168
|
+
: {}),
|
|
169
|
+
...(this.installAssets.action_sets !== undefined
|
|
170
|
+
? { action_sets: this.installAssets.action_sets }
|
|
171
|
+
: {}),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
82
174
|
psVersion() {
|
|
83
175
|
const v = this.dims.getPsVersion();
|
|
84
176
|
return v && v.length > 0 ? v : PS_VERSION_UNKNOWN;
|
|
@@ -105,6 +197,7 @@ export class TelemetryClient {
|
|
|
105
197
|
tool_call_count: this.toolCallCount,
|
|
106
198
|
distinct_tools: this.distinctTools.size,
|
|
107
199
|
any_failures: this.anyFailures,
|
|
200
|
+
...this.summaryFields(),
|
|
108
201
|
};
|
|
109
202
|
writeSessionStateSync(state, this.outboxOpts);
|
|
110
203
|
}
|
|
@@ -123,6 +216,9 @@ export class TelemetryClient {
|
|
|
123
216
|
error_message: sanitizeMessage(diag.error_message),
|
|
124
217
|
...(diag.snippet ? { snippet: sanitizeSnippet(diag.snippet) } : {}),
|
|
125
218
|
...(diag.stderr_tail ? { stderr_tail: sanitizeStderrTail(diag.stderr_tail) } : {}),
|
|
219
|
+
...(diag.doc_depth !== undefined ? { doc_depth: diag.doc_depth } : {}),
|
|
220
|
+
...(diag.doc_mode !== undefined ? { doc_mode: diag.doc_mode } : {}),
|
|
221
|
+
...(diag.ps_locale !== undefined ? { ps_locale: diag.ps_locale } : {}),
|
|
126
222
|
}, this.now()));
|
|
127
223
|
}
|
|
128
224
|
restampPsVersion(events) {
|
|
@@ -141,7 +237,9 @@ export class TelemetryClient {
|
|
|
141
237
|
enqueue(event) {
|
|
142
238
|
this.queue.push(event);
|
|
143
239
|
if (this.queue.length > MAX_QUEUE_SIZE) {
|
|
144
|
-
|
|
240
|
+
const excess = this.queue.length - MAX_QUEUE_SIZE;
|
|
241
|
+
this.queue.splice(0, excess);
|
|
242
|
+
this.droppedEvents += excess;
|
|
145
243
|
}
|
|
146
244
|
if (this.queue.length >= this.maxBatchSize)
|
|
147
245
|
void this.flush();
|
|
@@ -149,15 +247,18 @@ export class TelemetryClient {
|
|
|
149
247
|
async flush() {
|
|
150
248
|
if (!this.active || this.queue.length === 0)
|
|
151
249
|
return;
|
|
152
|
-
const
|
|
250
|
+
const picked = this.restampPsVersion(this.queue.splice(0, this.maxBatchSize));
|
|
251
|
+
const batch = picked.filter(isContentSafe);
|
|
252
|
+
this.droppedUnsafe += picked.length - batch.length;
|
|
153
253
|
if (batch.length === 0)
|
|
154
254
|
return;
|
|
155
255
|
try {
|
|
156
256
|
await this.transport(this.endpoint, JSON.stringify({ events: batch }));
|
|
257
|
+
this.usageCallsSent += countUsageCalls(batch);
|
|
157
258
|
}
|
|
158
259
|
catch (err) {
|
|
159
260
|
this.logger.debug(`telemetry flush failed, persisting ${batch.length} event(s) to outbox: ${errMsg(err)}`);
|
|
160
|
-
appendOutboxSync(batch, this.outboxOpts);
|
|
261
|
+
this.droppedOutbox += appendOutboxSync(batch, this.outboxOpts);
|
|
161
262
|
}
|
|
162
263
|
}
|
|
163
264
|
shutdown() {
|
|
@@ -172,15 +273,26 @@ export class TelemetryClient {
|
|
|
172
273
|
}
|
|
173
274
|
if (!this.active)
|
|
174
275
|
return;
|
|
175
|
-
if (this.
|
|
176
|
-
this.
|
|
276
|
+
if (this.queue.length > 0) {
|
|
277
|
+
const picked = this.restampPsVersion(this.queue.splice(0));
|
|
278
|
+
const batch = picked.filter(isContentSafe);
|
|
279
|
+
this.droppedUnsafe += picked.length - batch.length;
|
|
280
|
+
this.droppedOutbox += appendOutboxSync(batch, this.outboxOpts);
|
|
281
|
+
}
|
|
282
|
+
const hasLossToReport = this.droppedEvents > 0 || this.droppedOutbox > 0 || this.droppedUnsafe > 0;
|
|
283
|
+
if (this.settings.telemetry.usage && (this.toolCallCount > 0 || hasLossToReport)) {
|
|
284
|
+
const summary = buildSessionSummary(this.dims, {
|
|
177
285
|
tool_call_count: this.toolCallCount,
|
|
178
286
|
distinct_tools: this.distinctTools.size,
|
|
179
287
|
any_failures: this.anyFailures,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
288
|
+
...this.summaryFields(),
|
|
289
|
+
}, this.ensureStartDayBucket(), this.now());
|
|
290
|
+
if (isContentSafe(summary)) {
|
|
291
|
+
const lost = appendOutboxSync([summary], this.outboxOpts);
|
|
292
|
+
if (lost > 0) {
|
|
293
|
+
this.logger.debug(`outbox discarded ${lost} event(s) while writing the session summary`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
184
296
|
}
|
|
185
297
|
clearSessionState(this.outboxOpts);
|
|
186
298
|
}
|
|
@@ -193,7 +305,8 @@ export class TelemetryClient {
|
|
|
193
305
|
appendOutboxSync([summaryFromState(stale)], this.outboxOpts);
|
|
194
306
|
}
|
|
195
307
|
clearSessionState(this.outboxOpts);
|
|
196
|
-
const pending =
|
|
308
|
+
const { events: pending, discarded } = readOutboxWithDiscards(this.outboxOpts);
|
|
309
|
+
this.droppedOutbox += discarded;
|
|
197
310
|
if (pending.length === 0) {
|
|
198
311
|
clearOutbox(this.outboxOpts);
|
|
199
312
|
return;
|
|
@@ -202,22 +315,27 @@ export class TelemetryClient {
|
|
|
202
315
|
clearOutbox(this.outboxOpts);
|
|
203
316
|
return;
|
|
204
317
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
318
|
+
const sendable = pending.filter(isContentSafe);
|
|
319
|
+
this.droppedUnsafe += pending.length - sendable.length;
|
|
320
|
+
let settled = 0;
|
|
321
|
+
for (let i = 0; i < sendable.length; i += this.maxBatchSize) {
|
|
322
|
+
const batch = sendable.slice(i, i + this.maxBatchSize);
|
|
210
323
|
try {
|
|
211
324
|
await this.transport(this.endpoint, JSON.stringify({ events: batch }));
|
|
325
|
+
this.usageCallsSent += countUsageCalls(batch);
|
|
212
326
|
}
|
|
213
327
|
catch (err) {
|
|
214
328
|
this.logger.debug(`startup outbox flush failed: ${errMsg(err)}`);
|
|
215
|
-
allAccepted = false;
|
|
216
329
|
break;
|
|
217
330
|
}
|
|
331
|
+
settled = i + batch.length;
|
|
218
332
|
}
|
|
219
|
-
if (
|
|
333
|
+
if (settled >= sendable.length) {
|
|
220
334
|
clearOutbox(this.outboxOpts);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
rewriteOutbox(sendable.slice(settled), this.outboxOpts);
|
|
338
|
+
}
|
|
221
339
|
}
|
|
222
340
|
catch (err) {
|
|
223
341
|
this.logger.debug(`startup outbox flush error: ${errMsg(err)}`);
|
|
@@ -228,6 +346,8 @@ export class TelemetryClient {
|
|
|
228
346
|
}
|
|
229
347
|
}
|
|
230
348
|
function summaryFromState(s) {
|
|
349
|
+
const templatesSaved = s.templates_saved !== undefined ? clampCount(s.templates_saved) : null;
|
|
350
|
+
const actionSets = s.action_sets !== undefined ? clampCount(s.action_sets) : null;
|
|
231
351
|
return {
|
|
232
352
|
v: 2,
|
|
233
353
|
type: 'session_summary',
|
|
@@ -240,8 +360,36 @@ function summaryFromState(s) {
|
|
|
240
360
|
tool_call_count: s.tool_call_count,
|
|
241
361
|
distinct_tools: s.distinct_tools,
|
|
242
362
|
any_failures: s.any_failures,
|
|
363
|
+
...(s.duration_s !== undefined ? { duration_s: s.duration_s } : {}),
|
|
364
|
+
...(s.retry_count !== undefined ? { retry_count: s.retry_count } : {}),
|
|
365
|
+
...(s.ended_after_failure !== undefined ? { ended_after_failure: s.ended_after_failure } : {}),
|
|
366
|
+
...(s.edits_ok !== undefined ? { edits_ok: s.edits_ok } : {}),
|
|
367
|
+
...(s.kept_work !== undefined ? { kept_work: s.kept_work } : {}),
|
|
368
|
+
...(s.behind_latest !== undefined ? { behind_latest: s.behind_latest } : {}),
|
|
369
|
+
...(s.dropped_events !== undefined ? { dropped_events: s.dropped_events } : {}),
|
|
370
|
+
...(s.dropped_outbox !== undefined ? { dropped_outbox: s.dropped_outbox } : {}),
|
|
371
|
+
...(s.dropped_unsafe !== undefined ? { dropped_unsafe: s.dropped_unsafe } : {}),
|
|
372
|
+
...(s.usage_calls_sent !== undefined ? { usage_calls_sent: s.usage_calls_sent } : {}),
|
|
373
|
+
...(s.module_update !== undefined ? { module_update: s.module_update } : {}),
|
|
374
|
+
...(templatesSaved !== null ? { templates_saved: templatesSaved } : {}),
|
|
375
|
+
...(actionSets !== null ? { action_sets: actionSets } : {}),
|
|
243
376
|
};
|
|
244
377
|
}
|
|
245
378
|
function errMsg(err) {
|
|
246
379
|
return err instanceof Error ? err.message : String(err);
|
|
247
380
|
}
|
|
381
|
+
function countUsageCalls(events) {
|
|
382
|
+
let n = 0;
|
|
383
|
+
for (const event of events) {
|
|
384
|
+
if (event.type !== 'usage')
|
|
385
|
+
continue;
|
|
386
|
+
const count = event.count;
|
|
387
|
+
n += typeof count === 'number' && Number.isInteger(count) && count > 0 ? count : 1;
|
|
388
|
+
}
|
|
389
|
+
return n;
|
|
390
|
+
}
|
|
391
|
+
function clampCount(n) {
|
|
392
|
+
if (!Number.isFinite(n))
|
|
393
|
+
return null;
|
|
394
|
+
return Math.min(Math.max(0, Math.trunc(n)), MAX_INSTALL_ASSET_COUNT);
|
|
395
|
+
}
|
package/dist/telemetry/events.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { mapClientName, parseMajor, boundMajor } from './activity.js';
|
|
1
2
|
export const TELEMETRY_SCHEMA_VERSION = 2;
|
|
2
3
|
export const PS_VERSION_UNKNOWN = 'unknown';
|
|
4
|
+
export const MAX_RESULT_BYTES = 16_777_216;
|
|
5
|
+
const PS_LOCALE_RE = /^[a-z]{2,3}_[A-Z]{2}$/;
|
|
3
6
|
export function dayBucket(now) {
|
|
4
7
|
return now.toISOString().slice(0, 10);
|
|
5
8
|
}
|
|
@@ -31,6 +34,9 @@ export function buildUsageEvent(dims, call, now) {
|
|
|
31
34
|
success: call.success,
|
|
32
35
|
error_class: call.error_class === null ? null : normalizeErrorClass(call.error_class),
|
|
33
36
|
duration_ms: call.duration_ms,
|
|
37
|
+
...(call.result_bytes !== undefined
|
|
38
|
+
? { result_bytes: Math.min(call.result_bytes, MAX_RESULT_BYTES) }
|
|
39
|
+
: {}),
|
|
34
40
|
};
|
|
35
41
|
}
|
|
36
42
|
export function buildSessionSummary(dims, summary, tsBucket, now) {
|
|
@@ -46,9 +52,26 @@ export function buildSessionSummary(dims, summary, tsBucket, now) {
|
|
|
46
52
|
tool_call_count: summary.tool_call_count,
|
|
47
53
|
distinct_tools: summary.distinct_tools,
|
|
48
54
|
any_failures: summary.any_failures,
|
|
55
|
+
...(summary.duration_s !== undefined ? { duration_s: summary.duration_s } : {}),
|
|
56
|
+
...(summary.retry_count !== undefined ? { retry_count: summary.retry_count } : {}),
|
|
57
|
+
...(summary.ended_after_failure !== undefined
|
|
58
|
+
? { ended_after_failure: summary.ended_after_failure }
|
|
59
|
+
: {}),
|
|
60
|
+
...(summary.edits_ok !== undefined ? { edits_ok: summary.edits_ok } : {}),
|
|
61
|
+
...(summary.kept_work !== undefined ? { kept_work: summary.kept_work } : {}),
|
|
62
|
+
...(summary.behind_latest !== undefined ? { behind_latest: summary.behind_latest } : {}),
|
|
63
|
+
...(summary.dropped_events !== undefined ? { dropped_events: summary.dropped_events } : {}),
|
|
64
|
+
...(summary.dropped_outbox !== undefined ? { dropped_outbox: summary.dropped_outbox } : {}),
|
|
65
|
+
...(summary.dropped_unsafe !== undefined ? { dropped_unsafe: summary.dropped_unsafe } : {}),
|
|
66
|
+
...(summary.usage_calls_sent !== undefined
|
|
67
|
+
? { usage_calls_sent: summary.usage_calls_sent }
|
|
68
|
+
: {}),
|
|
69
|
+
...(summary.module_update !== undefined ? { module_update: summary.module_update } : {}),
|
|
70
|
+
...(summary.templates_saved !== undefined ? { templates_saved: summary.templates_saved } : {}),
|
|
71
|
+
...(summary.action_sets !== undefined ? { action_sets: summary.action_sets } : {}),
|
|
49
72
|
};
|
|
50
73
|
}
|
|
51
|
-
export function buildSessionStart(dims, now) {
|
|
74
|
+
export function buildSessionStart(dims, now, facts = {}) {
|
|
52
75
|
return {
|
|
53
76
|
v: TELEMETRY_SCHEMA_VERSION,
|
|
54
77
|
type: 'session_start',
|
|
@@ -59,6 +82,25 @@ export function buildSessionStart(dims, now) {
|
|
|
59
82
|
platform: dims.platform,
|
|
60
83
|
ps_version: psVersionOf(dims),
|
|
61
84
|
channel: dims.channel,
|
|
85
|
+
...(facts.node_major !== undefined ? { node_major: facts.node_major } : {}),
|
|
86
|
+
...(facts.arch !== undefined ? { arch: facts.arch } : {}),
|
|
87
|
+
...(facts.os_major !== undefined ? { os_major: facts.os_major } : {}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export function buildClientConnected(dims, info, now) {
|
|
91
|
+
return {
|
|
92
|
+
v: TELEMETRY_SCHEMA_VERSION,
|
|
93
|
+
type: 'client_connected',
|
|
94
|
+
install_id: dims.install_id,
|
|
95
|
+
ts_bucket: dayBucket(now),
|
|
96
|
+
editmamei_version: dims.editmamei_version,
|
|
97
|
+
edition: dims.edition,
|
|
98
|
+
platform: dims.platform,
|
|
99
|
+
client: mapClientName(info.clientName),
|
|
100
|
+
client_major: boundMajor(parseMajor(info.clientVersion), 9999),
|
|
101
|
+
cap_sampling: info.capSampling,
|
|
102
|
+
cap_elicitation: info.capElicitation,
|
|
103
|
+
cap_roots: info.capRoots,
|
|
62
104
|
};
|
|
63
105
|
}
|
|
64
106
|
export function buildModuleStatus(dims, status, now) {
|
|
@@ -90,6 +132,11 @@ export function buildDiagnosticEvent(dims, diag, now) {
|
|
|
90
132
|
error_message: diag.error_message,
|
|
91
133
|
...(diag.snippet ? { snippet: diag.snippet } : {}),
|
|
92
134
|
...(diag.stderr_tail ? { stderr_tail: diag.stderr_tail } : {}),
|
|
135
|
+
...(diag.doc_depth !== undefined ? { doc_depth: diag.doc_depth } : {}),
|
|
136
|
+
...(diag.doc_mode !== undefined ? { doc_mode: diag.doc_mode } : {}),
|
|
137
|
+
...(diag.ps_locale !== undefined && PS_LOCALE_RE.test(diag.ps_locale)
|
|
138
|
+
? { ps_locale: diag.ps_locale }
|
|
139
|
+
: {}),
|
|
93
140
|
};
|
|
94
141
|
}
|
|
95
142
|
export function looksLikeAbsolutePath(value) {
|
package/dist/telemetry/outbox.js
CHANGED
|
@@ -6,8 +6,8 @@ const logger = new Logger('TelemetryOutbox');
|
|
|
6
6
|
const DIRNAME = '.editmamei';
|
|
7
7
|
const OUTBOX_FILENAME = 'telemetry-outbox.ndjson';
|
|
8
8
|
const SESSION_STATE_FILENAME = 'telemetry-session.json';
|
|
9
|
-
export const MAX_OUTBOX_EVENTS =
|
|
10
|
-
const MAX_OUTBOX_BYTES = 2_000_000;
|
|
9
|
+
export const MAX_OUTBOX_EVENTS = 5_000;
|
|
10
|
+
export const MAX_OUTBOX_BYTES = 2_000_000;
|
|
11
11
|
function baseDir(opts) {
|
|
12
12
|
return opts.dir ?? join(homedir(), DIRNAME);
|
|
13
13
|
}
|
|
@@ -22,27 +22,31 @@ function ensureDir(path) {
|
|
|
22
22
|
}
|
|
23
23
|
export function appendOutboxSync(events, opts = {}) {
|
|
24
24
|
if (events.length === 0)
|
|
25
|
-
return;
|
|
25
|
+
return 0;
|
|
26
26
|
const path = outboxPath(opts);
|
|
27
|
+
let discarded = 0;
|
|
27
28
|
try {
|
|
28
29
|
ensureDir(path);
|
|
29
30
|
if (existsSync(path) && statSync(path).size > MAX_OUTBOX_BYTES) {
|
|
30
|
-
compactOutbox(opts);
|
|
31
|
+
discarded += compactOutbox(opts);
|
|
31
32
|
}
|
|
32
33
|
const lines = events.map((e) => JSON.stringify(e)).join('\n') + '\n';
|
|
33
34
|
appendFileSync(path, lines, { encoding: 'utf8', mode: 0o600 });
|
|
34
35
|
}
|
|
35
36
|
catch (err) {
|
|
36
37
|
logger.debug(`outbox append dropped ${events.length} event(s): ${errMsg(err)}`);
|
|
38
|
+
discarded += events.length;
|
|
37
39
|
}
|
|
40
|
+
return discarded;
|
|
38
41
|
}
|
|
39
|
-
export function
|
|
42
|
+
export function readOutboxWithDiscards(opts = {}) {
|
|
40
43
|
const path = outboxPath(opts);
|
|
41
44
|
if (!existsSync(path))
|
|
42
|
-
return [];
|
|
45
|
+
return { events: [], discarded: 0 };
|
|
43
46
|
try {
|
|
44
47
|
const raw = readFileSync(path, 'utf8');
|
|
45
48
|
const events = [];
|
|
49
|
+
let discarded = 0;
|
|
46
50
|
for (const line of raw.split('\n')) {
|
|
47
51
|
const trimmed = line.trim();
|
|
48
52
|
if (trimmed.length === 0)
|
|
@@ -51,17 +55,23 @@ export function readOutbox(opts = {}) {
|
|
|
51
55
|
events.push(JSON.parse(trimmed));
|
|
52
56
|
}
|
|
53
57
|
catch {
|
|
58
|
+
discarded += 1;
|
|
54
59
|
}
|
|
55
60
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
: events;
|
|
61
|
+
if (events.length > MAX_OUTBOX_EVENTS) {
|
|
62
|
+
const over = events.length - MAX_OUTBOX_EVENTS;
|
|
63
|
+
return { events: events.slice(over), discarded: discarded + over };
|
|
64
|
+
}
|
|
65
|
+
return { events, discarded };
|
|
59
66
|
}
|
|
60
67
|
catch (err) {
|
|
61
68
|
logger.debug(`outbox read failed: ${errMsg(err)}`);
|
|
62
|
-
return [];
|
|
69
|
+
return { events: [], discarded: 0 };
|
|
63
70
|
}
|
|
64
71
|
}
|
|
72
|
+
export function readOutbox(opts = {}) {
|
|
73
|
+
return readOutboxWithDiscards(opts).events;
|
|
74
|
+
}
|
|
65
75
|
export function clearOutbox(opts = {}) {
|
|
66
76
|
try {
|
|
67
77
|
rmSync(outboxPath(opts), { force: true });
|
|
@@ -70,24 +80,39 @@ export function clearOutbox(opts = {}) {
|
|
|
70
80
|
logger.debug(`outbox clear failed: ${errMsg(err)}`);
|
|
71
81
|
}
|
|
72
82
|
}
|
|
73
|
-
function
|
|
74
|
-
|
|
83
|
+
export function rewriteOutbox(events, opts = {}) {
|
|
84
|
+
if (events.length === 0) {
|
|
85
|
+
clearOutbox(opts);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
75
88
|
const path = outboxPath(opts);
|
|
76
89
|
try {
|
|
77
|
-
|
|
78
|
-
clearOutbox(opts);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
90
|
+
ensureDir(path);
|
|
81
91
|
const tmp = join(dirname(path), `.outbox.${process.pid}.tmp`);
|
|
82
|
-
writeFileSync(tmp,
|
|
92
|
+
writeFileSync(tmp, events.map((e) => JSON.stringify(e)).join('\n') + '\n', {
|
|
83
93
|
encoding: 'utf8',
|
|
84
94
|
mode: 0o600,
|
|
85
95
|
});
|
|
86
96
|
renameSync(tmp, path);
|
|
87
97
|
}
|
|
88
98
|
catch (err) {
|
|
89
|
-
logger.debug(`outbox
|
|
99
|
+
logger.debug(`outbox rewrite failed: ${errMsg(err)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function compactOutbox(opts = {}) {
|
|
103
|
+
const { events, discarded } = readOutboxWithDiscards(opts);
|
|
104
|
+
const target = MAX_OUTBOX_BYTES / 2;
|
|
105
|
+
let bytes = 0;
|
|
106
|
+
let firstKept = Math.max(events.length - 1, 0);
|
|
107
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
108
|
+
bytes += Buffer.byteLength(JSON.stringify(events[i]), 'utf8') + 1;
|
|
109
|
+
if (bytes > target)
|
|
110
|
+
break;
|
|
111
|
+
firstKept = i;
|
|
90
112
|
}
|
|
113
|
+
const kept = events.slice(firstKept);
|
|
114
|
+
rewriteOutbox(kept, opts);
|
|
115
|
+
return discarded + (events.length - kept.length);
|
|
91
116
|
}
|
|
92
117
|
export function writeSessionStateSync(state, opts = {}) {
|
|
93
118
|
const path = sessionStatePath(opts);
|