claude-code-session-manager 0.83.0 → 0.85.0
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/dist/assets/{AgentLibrary-B-OM-4gk.js → AgentLibrary-Bkv-HcP1.js} +1 -1
- package/dist/assets/{DataModel-BC6ppcBr.js → DataModel-DRH-Ty20.js} +1 -1
- package/dist/assets/{History-D3Qw2BWn.js → History-CfRhT1Im.js} +2 -2
- package/dist/assets/{Hooks-OxBQ0nIc.js → Hooks-wEmh_U6c.js} +1 -1
- package/dist/assets/{HostBilko-BNFC6bNZ.js → HostBilko-D_t7Rbi7.js} +1 -1
- package/dist/assets/{Library-DAMyrUtY.js → Library-CpArQ-OJ.js} +1 -1
- package/dist/assets/{ListDetail-BwywrpvE.js → ListDetail-pjaKYs84.js} +1 -1
- package/dist/assets/{MarkdownEditor-HEwjN1bp.js → MarkdownEditor-Xc141kjj.js} +1 -1
- package/dist/assets/{McpServers-D1VZF7Ix.js → McpServers-ftqaV3kn.js} +1 -1
- package/dist/assets/{Memory-CgYJ_QqD.js → Memory-ChMWkNd0.js} +6 -6
- package/dist/assets/{Panel-dTNWC59Q.js → Panel-D9Kr40Ai.js} +1 -1
- package/dist/assets/{Permissions-qI_NKi1b.js → Permissions-DKoNVgzj.js} +1 -1
- package/dist/assets/{Plugins-DZ0_CHiF.js → Plugins-BtChISho.js} +2 -2
- package/dist/assets/{ProvenanceBadge-B2i_uxCu.js → ProvenanceBadge-DBA5EcYy.js} +1 -1
- package/dist/assets/{SaveBar-4tNMCjUd.js → SaveBar-I0_dWNTX.js} +1 -1
- package/dist/assets/{Scheduler-CUen18Gc.js → Scheduler-CbES7MC8.js} +7 -7
- package/dist/assets/{ScopeSwitcher-Co3tkLTT.js → ScopeSwitcher-5GTEveb2.js} +1 -1
- package/dist/assets/Settings-BX3FElXk.js +3 -0
- package/dist/assets/{SkillReferenceGraph-BKcHtMPc.js → SkillReferenceGraph-DNBFGrYE.js} +1 -1
- package/dist/assets/{Skills-DHb4INtF.js → Skills-DJB6-bBM.js} +2 -2
- package/dist/assets/SystemPrompt-BiDDrJUA.js +1 -0
- package/dist/assets/{TagLibrary-xLP5dJvh.js → TagLibrary-_Wrevtop.js} +1 -1
- package/dist/assets/{TiptapBody-T-ULTjK4.js → TiptapBody-OWWXdLRy.js} +1 -1
- package/dist/assets/{Toggle-B-5M2hGe.js → Toggle-B122N0HL.js} +1 -1
- package/dist/assets/{index-CYhdtisq.css → index-CDo9xBR9.css} +1 -1
- package/dist/assets/{index-CzAxC432.js → index-DhvuQL4C.js} +316 -314
- package/dist/assets/{settingsSchema-C_nsemcF.js → settingsSchema-sGoCTd7J.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +4 -1
- package/scripts/hooks/guard-destructive-git.cjs +514 -0
- package/scripts/hooks/guard-inline-implementation.cjs +219 -0
- package/scripts/hooks/guard-prd-writes.cjs +200 -0
- package/src/main/__tests__/epicMintTelemetryTap.test.cjs +64 -0
- package/src/main/__tests__/health-delegation-chain.test.cjs +2 -1
- package/src/main/__tests__/health-queue-dispatch.test.cjs +84 -0
- package/src/main/__tests__/health-usage-poller.test.cjs +97 -0
- package/src/main/__tests__/health-worktree-cap-blocked.test.cjs +65 -0
- package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +149 -0
- package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +120 -0
- package/src/main/__tests__/promptSessionTranscript.test.cjs +0 -0
- package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +143 -0
- package/src/main/__tests__/rateLimitPollerStreak.test.cjs +79 -0
- package/src/main/__tests__/scheduleJobTransitionsTelemetryTap.test.cjs +72 -0
- package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +74 -0
- package/src/main/__tests__/scheduler-job-overrun.test.cjs +58 -0
- package/src/main/__tests__/scheduler-notify-originating-tab-transcript.test.cjs +1 -0
- package/src/main/__tests__/scheduler-periodic-reverify-guard.test.cjs +134 -2
- package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +33 -0
- package/src/main/__tests__/scheduler-stuck-failed-escalation.test.cjs +136 -0
- package/src/main/__tests__/telemetryClient.test.cjs +242 -1
- package/src/main/__tests__/telemetryContract.test.cjs +930 -0
- package/src/main/crashDiagnostics.cjs +29 -1
- package/src/main/health.cjs +197 -3
- package/src/main/index.cjs +65 -6
- package/src/main/ipcSchemas.cjs +19 -2
- package/src/main/lib/__tests__/crashTelemetry.test.cjs +103 -0
- package/src/main/lib/__tests__/delegationReadiness.test.cjs +302 -4
- package/src/main/lib/__tests__/fixtures/scheduler-machine.json.corrupt-1789147548 +34 -0
- package/src/main/lib/__tests__/gitWorktree.test.cjs +413 -1
- package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +71 -0
- package/src/main/lib/__tests__/queueStoreAtomicWrite.test.cjs +88 -0
- package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +123 -0
- package/src/main/lib/__tests__/reaperHelpers.test.cjs +58 -0
- package/src/main/lib/__tests__/telemetryBacklog.test.cjs +626 -0
- package/src/main/lib/__tests__/telemetryBoot.test.cjs +123 -0
- package/src/main/lib/__tests__/telemetryConsent.test.cjs +136 -0
- package/src/main/lib/__tests__/telemetryCounters.test.cjs +57 -0
- package/src/main/lib/__tests__/telemetryCountersMetadataColumn.test.cjs +95 -0
- package/src/main/lib/crashTelemetry.cjs +37 -0
- package/src/main/lib/delegationReadiness.cjs +119 -1
- package/src/main/lib/epicMint.cjs +2 -0
- package/src/main/lib/gitWorktree.cjs +427 -17
- package/src/main/lib/jobWorktree.cjs +2 -1
- package/src/main/lib/jobWorktreeBootLive.cjs +51 -0
- package/src/main/lib/jobWorktreeTerminalOrphanLive.cjs +68 -0
- package/src/main/lib/opsErrorLog.cjs +78 -25
- package/src/main/lib/queueStore.cjs +241 -22
- package/src/main/lib/reaperHelpers.cjs +23 -1
- package/src/main/lib/scheduleJobSchema.cjs +7 -0
- package/src/main/lib/scheduleJobTransitions.cjs +12 -0
- package/src/main/lib/telemetryBacklog.cjs +601 -0
- package/src/main/lib/telemetryBoot.cjs +76 -0
- package/src/main/lib/telemetryClient.cjs +150 -5
- package/src/main/lib/telemetryConsent.cjs +34 -0
- package/src/main/lib/telemetryCounters.cjs +45 -0
- package/src/main/promptSessionTranscript.cjs +0 -0
- package/src/main/pty.cjs +2 -0
- package/src/main/scheduler.cjs +481 -33
- package/src/preload/api.d.ts +84 -4
- package/src/preload/index.cjs +9 -0
- package/dist/assets/Settings-DXVgKLUx.js +0 -3
- package/dist/assets/SystemPrompt-BjqFOHSk.js +0 -1
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* telemetryClient — the single egress module that ships telemetry to
|
|
3
|
-
* bilko.run's beacon endpoints (POST /api/telemetry/event, /log, /error
|
|
3
|
+
* bilko.run's beacon endpoints (POST /api/telemetry/event, /log, /error,
|
|
4
|
+
* /install). The `install` channel is a durable upsert keyed on install_id
|
|
5
|
+
* (app_installs), not an append like the other three — its wire body is a
|
|
6
|
+
* single flat snake_case object, never `{batch:[...]}` — so it is routed
|
|
7
|
+
* through the same queue/dedup/backoff machinery but sent one record at a
|
|
8
|
+
* time via sendSingle() rather than sendBatch().
|
|
4
9
|
*
|
|
5
10
|
* Records accumulate durably in ~/.config/session-manager/telemetry-queue.jsonl
|
|
6
11
|
* the instant they're accepted, and are only ever sent by flush(reason) — on
|
|
@@ -83,14 +88,42 @@ const S = {
|
|
|
83
88
|
errSeen: new Map(),
|
|
84
89
|
dailyTimer: null,
|
|
85
90
|
flushChain: Promise.resolve(),
|
|
91
|
+
lastError: null,
|
|
92
|
+
lastFlushAt: null,
|
|
93
|
+
lastFlushReason: null,
|
|
86
94
|
};
|
|
87
95
|
|
|
96
|
+
/**
|
|
97
|
+
* SM_TELEMETRY_SPOOL, when set, overrides the spool directory in place of
|
|
98
|
+
* `~/.config/session-manager` — the explicit opt-in a test that genuinely
|
|
99
|
+
* needs to exercise real queue/sent-file I/O uses to prove it isn't about to
|
|
100
|
+
* write into a real user's spool (see isTestEnvironment() below).
|
|
101
|
+
*/
|
|
102
|
+
function spoolDir() {
|
|
103
|
+
return process.env.SM_TELEMETRY_SPOOL || path.join(os.homedir(), '.config', 'session-manager');
|
|
104
|
+
}
|
|
105
|
+
|
|
88
106
|
function queuePath() {
|
|
89
|
-
return path.join(
|
|
107
|
+
return path.join(spoolDir(), 'telemetry-queue.jsonl');
|
|
90
108
|
}
|
|
91
109
|
|
|
92
110
|
function sentPath() {
|
|
93
|
-
return path.join(
|
|
111
|
+
return path.join(spoolDir(), 'telemetry-sent.json');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* True when running under a test runner (VITEST, NODE_ENV=test) with no
|
|
116
|
+
* explicit SM_TELEMETRY_SPOOL override. Gates appendRecord() below so a test
|
|
117
|
+
* that merely exercises epicMint/scheduler/etc. code paths — without itself
|
|
118
|
+
* setting up an isolated spool — can never accumulate fake records into a
|
|
119
|
+
* real user's telemetry-queue.jsonl. A test that deliberately wants to
|
|
120
|
+
* exercise real persistence (telemetryClient's own suite) sets
|
|
121
|
+
* SM_TELEMETRY_SPOOL to an isolated directory, which counts as an explicit
|
|
122
|
+
* opt-in and disables this guard.
|
|
123
|
+
*/
|
|
124
|
+
function isTestEnvironment() {
|
|
125
|
+
if (process.env.SM_TELEMETRY_SPOOL) return false;
|
|
126
|
+
return !!process.env.VITEST || process.env.NODE_ENV === 'test';
|
|
94
127
|
}
|
|
95
128
|
|
|
96
129
|
function getBeaconToken() {
|
|
@@ -365,6 +398,9 @@ function pushRecent(rec) {
|
|
|
365
398
|
}
|
|
366
399
|
|
|
367
400
|
async function appendRecord(channel, wire, recordId) {
|
|
401
|
+
if (isTestEnvironment()) {
|
|
402
|
+
return { accepted: false, reason: 'test-environment', recordId };
|
|
403
|
+
}
|
|
368
404
|
if (S.queueIds.has(recordId) || S.sentIds.has(recordId)) {
|
|
369
405
|
S.dedupedAppends += 1;
|
|
370
406
|
return { accepted: false, reason: 'duplicate', recordId };
|
|
@@ -472,6 +508,47 @@ async function reportError(opts) {
|
|
|
472
508
|
}
|
|
473
509
|
}
|
|
474
510
|
|
|
511
|
+
function buildInstallWire(profile, installId) {
|
|
512
|
+
const p = safeObj(profile);
|
|
513
|
+
return {
|
|
514
|
+
install_id: safeStr(installId, 80),
|
|
515
|
+
app: 'session-manager',
|
|
516
|
+
app_version: safeStr(p.appVersion, 20),
|
|
517
|
+
platform: safeStr(p.platform, 20),
|
|
518
|
+
os_release: safeStr(p.osRelease, 80),
|
|
519
|
+
arch: safeStr(p.arch, 20),
|
|
520
|
+
cpu_count: typeof p.cpuCount === 'number' ? p.cpuCount : 0,
|
|
521
|
+
total_mem_mb: typeof p.totalMemMb === 'number' ? p.totalMemMb : 0,
|
|
522
|
+
node_version: safeStr(p.nodeVersion, 20),
|
|
523
|
+
electron_version: safeStr(p.electronVersion, 20),
|
|
524
|
+
install_channel: safeStr(p.installChannel, 20),
|
|
525
|
+
locale: safeStr(p.locale, 20),
|
|
526
|
+
// machineProfile deliberately never carries an IANA timezone name — only
|
|
527
|
+
// the numeric UTC offset (see machineProfile.cjs) — so that is what maps
|
|
528
|
+
// onto the server's `timezone` column.
|
|
529
|
+
timezone: safeStr(p.timezoneOffsetMinutes, 20),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Reports the once-per-cadence install/liveness record. Unlike track/logLine/
|
|
535
|
+
* reportError this isn't arbitrary user content, so it skips redactDeep() —
|
|
536
|
+
* every field already comes from machineProfile's own anonymity contract —
|
|
537
|
+
* and it never merges an attribution block onto the body, since the server's
|
|
538
|
+
* app_installs upsert doesn't read a recordId.
|
|
539
|
+
*/
|
|
540
|
+
async function reportInstall(profile) {
|
|
541
|
+
try {
|
|
542
|
+
await ensureInit();
|
|
543
|
+
if (!telemetrySettings.isEnabled(S.settings)) return { accepted: false, reason: 'disabled' };
|
|
544
|
+
const recordId = crypto.randomUUID();
|
|
545
|
+
const wire = buildInstallWire(profile, S.settings.installId);
|
|
546
|
+
return await appendRecord('install', wire, recordId);
|
|
547
|
+
} catch {
|
|
548
|
+
return { accepted: false, reason: 'error' };
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
475
552
|
// ─── egress ────────────────────────────────────────────────────────────
|
|
476
553
|
|
|
477
554
|
async function sendBatch(channel, wireBatch) {
|
|
@@ -493,16 +570,42 @@ async function sendBatch(channel, wireBatch) {
|
|
|
493
570
|
}
|
|
494
571
|
}
|
|
495
572
|
|
|
573
|
+
/** Like sendBatch(), but posts `wire` as a flat body — the install route takes one upsert, never a `{batch:[...]}` envelope. */
|
|
574
|
+
async function sendSingle(channel, wire) {
|
|
575
|
+
const fetchFn = S.fetchImpl || (typeof fetch === 'function' ? fetch : null);
|
|
576
|
+
if (typeof fetchFn !== 'function') return { ok: false, status: 0 };
|
|
577
|
+
const base = telemetrySettings.resolveEndpoint(S.settings);
|
|
578
|
+
const url = `${base}/api/telemetry/${channel}`;
|
|
579
|
+
const headers = {
|
|
580
|
+
'Content-Type': 'application/json',
|
|
581
|
+
'X-SM-Beacon': `session-manager/${S.profile.appVersion}`,
|
|
582
|
+
'X-SM-Beacon-Key': getBeaconToken(),
|
|
583
|
+
};
|
|
584
|
+
try {
|
|
585
|
+
const res = await fetchFn(url, { method: 'POST', headers, body: JSON.stringify(wire) });
|
|
586
|
+
if (res && res.ok) return { ok: true };
|
|
587
|
+
return { ok: false, status: res ? res.status : 0 };
|
|
588
|
+
} catch {
|
|
589
|
+
return { ok: false, status: 0 };
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function describeFailureStatus(status) {
|
|
594
|
+
return status === 0 ? 'network error' : `HTTP ${status}`;
|
|
595
|
+
}
|
|
596
|
+
|
|
496
597
|
function applyFailureBackoff(status) {
|
|
497
598
|
if (status === 429 || status === 0 || status >= 500) {
|
|
498
599
|
S.consecutiveFailures += 1;
|
|
499
600
|
const ms = Math.min(BACKOFF_BASE_MS * (2 ** (S.consecutiveFailures - 1)), BACKOFF_MAX_MS);
|
|
500
601
|
S.backoffUntil = Date.now() + ms;
|
|
602
|
+
S.lastError = { status, message: describeFailureStatus(status), at: Date.now() };
|
|
501
603
|
return;
|
|
502
604
|
}
|
|
503
605
|
if (status >= 400 && status < 500) {
|
|
504
606
|
// Malformed-contract circuit breaker (includes 401) — not retried.
|
|
505
607
|
S.disabledForProcess = true;
|
|
608
|
+
S.lastError = { status, message: describeFailureStatus(status), at: Date.now() };
|
|
506
609
|
}
|
|
507
610
|
}
|
|
508
611
|
|
|
@@ -528,21 +631,41 @@ async function flushImpl(reason) {
|
|
|
528
631
|
if (S.disabledForProcess) return result;
|
|
529
632
|
if (Date.now() < S.backoffUntil) return result;
|
|
530
633
|
|
|
531
|
-
const byChannel = { event: [], log: [], error: [] };
|
|
634
|
+
const byChannel = { event: [], log: [], error: [], install: [] };
|
|
532
635
|
for (const r of S.queue) {
|
|
533
636
|
if (byChannel[r.channel]) byChannel[r.channel].push(r);
|
|
534
637
|
}
|
|
535
638
|
|
|
536
639
|
let sawFailure = false;
|
|
537
|
-
for (const channel of ['event', 'log', 'error']) {
|
|
640
|
+
for (const channel of ['event', 'log', 'error', 'install']) {
|
|
538
641
|
if (sawFailure) break;
|
|
539
642
|
const records = byChannel[channel];
|
|
643
|
+
if (channel === 'install') {
|
|
644
|
+
for (const r of records) {
|
|
645
|
+
const res = await sendSingle('install', r.wire);
|
|
646
|
+
if (res.ok) {
|
|
647
|
+
S.consecutiveFailures = 0;
|
|
648
|
+
S.backoffUntil = 0;
|
|
649
|
+
S.lastError = null;
|
|
650
|
+
removeFromQueue(r.recordId);
|
|
651
|
+
addToSent(r.recordId);
|
|
652
|
+
result.sent.push(r.recordId);
|
|
653
|
+
} else {
|
|
654
|
+
result.failed.push(r.recordId);
|
|
655
|
+
applyFailureBackoff(res.status);
|
|
656
|
+
sawFailure = true;
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
540
662
|
for (let i = 0; i < records.length; i += MAX_BATCH) {
|
|
541
663
|
const batch = records.slice(i, i + MAX_BATCH);
|
|
542
664
|
const res = await sendBatch(channel, batch.map((r) => r.wire));
|
|
543
665
|
if (res.ok) {
|
|
544
666
|
S.consecutiveFailures = 0;
|
|
545
667
|
S.backoffUntil = 0;
|
|
668
|
+
S.lastError = null;
|
|
546
669
|
for (const r of batch) {
|
|
547
670
|
removeFromQueue(r.recordId);
|
|
548
671
|
addToSent(r.recordId);
|
|
@@ -564,6 +687,8 @@ async function flushImpl(reason) {
|
|
|
564
687
|
S.settings = await telemetrySettings.save({ ...S.settings, lastDailyFlushAt: new Date().toISOString() });
|
|
565
688
|
}
|
|
566
689
|
} catch { /* fail-inert */ }
|
|
690
|
+
S.lastFlushAt = Date.now();
|
|
691
|
+
S.lastFlushReason = safeReason;
|
|
567
692
|
return result;
|
|
568
693
|
}
|
|
569
694
|
|
|
@@ -584,6 +709,9 @@ function status() {
|
|
|
584
709
|
consecutiveFailures: S.consecutiveFailures,
|
|
585
710
|
backoffUntil: S.backoffUntil,
|
|
586
711
|
profileBuildCount: S.profileBuildCount,
|
|
712
|
+
lastError: S.lastError,
|
|
713
|
+
lastFlushAt: S.lastFlushAt,
|
|
714
|
+
lastFlushReason: S.lastFlushReason,
|
|
587
715
|
};
|
|
588
716
|
}
|
|
589
717
|
|
|
@@ -591,6 +719,21 @@ function recentRecords() {
|
|
|
591
719
|
return S.recent.slice();
|
|
592
720
|
}
|
|
593
721
|
|
|
722
|
+
/**
|
|
723
|
+
* Opt-out side effect: drops every not-yet-sent record from disk + memory so
|
|
724
|
+
* a re-enable later never resends a payload that was only ever queued while
|
|
725
|
+
* the user was opted out. Does NOT touch telemetry-sent.json or the backlog
|
|
726
|
+
* watermarks — those describe history that already left the machine, and
|
|
727
|
+
* clearing them would make a re-enable resend it. Never throws.
|
|
728
|
+
*/
|
|
729
|
+
async function clearQueue() {
|
|
730
|
+
S.queue = [];
|
|
731
|
+
S.queueIds = new Set();
|
|
732
|
+
try {
|
|
733
|
+
await config.writeTextAtomic(queuePath(), '', { mode: 0o600 });
|
|
734
|
+
} catch { /* best-effort */ }
|
|
735
|
+
}
|
|
736
|
+
|
|
594
737
|
async function isPending(recordId) {
|
|
595
738
|
await ensureInit();
|
|
596
739
|
return S.queueIds.has(recordId);
|
|
@@ -610,11 +753,13 @@ module.exports = {
|
|
|
610
753
|
track,
|
|
611
754
|
logLine,
|
|
612
755
|
reportError,
|
|
756
|
+
reportInstall,
|
|
613
757
|
flush,
|
|
614
758
|
shutdown,
|
|
615
759
|
status,
|
|
616
760
|
recentRecords,
|
|
617
761
|
isPending,
|
|
762
|
+
clearQueue,
|
|
618
763
|
queuePath,
|
|
619
764
|
sentPath,
|
|
620
765
|
_setFetchImpl,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* telemetryConsent — the write-path the `telemetry:set-config` IPC handler
|
|
5
|
+
* delegates to, pulled out of src/main/index.cjs so it can be unit-tested
|
|
6
|
+
* without an Electron runtime (index.cjs requires('electron') at module
|
|
7
|
+
* load, which vitest can't satisfy).
|
|
8
|
+
*
|
|
9
|
+
* The one side effect beyond a plain settings save: turning telemetry OFF
|
|
10
|
+
* clears the pending queue file, so a later re-enable never resends a
|
|
11
|
+
* payload that only ever accumulated while the user was opted out. It does
|
|
12
|
+
* NOT touch telemetry-sent.json or the backlog watermarks — those describe
|
|
13
|
+
* history already delivered, and clearing them would make a re-enable
|
|
14
|
+
* resend it. See session-manager-operations/architecture/telemetry.md.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
function resolveDeps(deps = {}) {
|
|
18
|
+
return {
|
|
19
|
+
telemetrySettings: deps.telemetrySettings || require('./telemetrySettings.cjs'),
|
|
20
|
+
telemetryClient: deps.telemetryClient || require('./telemetryClient.cjs'),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function applyConsentUpdate(cfg, depsOverride) {
|
|
25
|
+
const deps = resolveDeps(depsOverride);
|
|
26
|
+
const before = await deps.telemetrySettings.load();
|
|
27
|
+
const saved = await deps.telemetrySettings.save(cfg);
|
|
28
|
+
if (before.enabled && !saved.enabled) {
|
|
29
|
+
await deps.telemetryClient.clearQueue();
|
|
30
|
+
}
|
|
31
|
+
return saved;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { applyConsentUpdate };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* telemetryCounters.cjs — the ONE place every usage counter event is emitted
|
|
5
|
+
* from, so 'session.open'/'epic.create'/'scheduler.job.finish'/'app.launch'
|
|
6
|
+
* each have exactly one call site's worth of shape logic even though they're
|
|
7
|
+
* fired from four different subsystems (pty.cjs, epicMint.cjs,
|
|
8
|
+
* scheduleJobTransitions.cjs, telemetryBoot.cjs).
|
|
9
|
+
*
|
|
10
|
+
* Structural props only — no free text, no cwd, no path — so a counter event
|
|
11
|
+
* can never carry a prompt/transcript/project-name leak by accident. Lazily
|
|
12
|
+
* requires telemetryClient.cjs (mirrors opsErrorLog.cjs's lazy require of
|
|
13
|
+
* opsOwnership.cjs) so this module stays Electron-free and importable from a
|
|
14
|
+
* plain-Node test.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
function track(name, props, deps) {
|
|
18
|
+
try {
|
|
19
|
+
const telemetryClient = (deps && deps.telemetryClient) || require('./telemetryClient.cjs');
|
|
20
|
+
telemetryClient.track(name, props);
|
|
21
|
+
} catch { /* counters must never break the caller */ }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function trackAppLaunch({ installChannel, appVersion } = {}, deps) {
|
|
25
|
+
track('app.launch', { installChannel: installChannel || null, appVersion: appVersion || null }, deps);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function trackSessionOpen(deps) {
|
|
29
|
+
track('session.open', {}, deps);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function trackEpicCreate(deps) {
|
|
33
|
+
track('epic.create', {}, deps);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function trackSchedulerJobFinish({ status } = {}, deps) {
|
|
37
|
+
track('scheduler.job.finish', { status: status || null }, deps);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
trackAppLaunch,
|
|
42
|
+
trackSessionOpen,
|
|
43
|
+
trackEpicCreate,
|
|
44
|
+
trackSchedulerJobFinish,
|
|
45
|
+
};
|
|
Binary file
|
package/src/main/pty.cjs
CHANGED
|
@@ -22,6 +22,7 @@ const { checkInsideHome } = require('./lib/insideHome.cjs');
|
|
|
22
22
|
const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
|
|
23
23
|
const opsErrorLog = require('./lib/opsErrorLog.cjs');
|
|
24
24
|
const { resolveEpicSpawnCwd } = require('./lib/epicSpawnCwd.cjs');
|
|
25
|
+
const telemetryCounters = require('./lib/telemetryCounters.cjs');
|
|
25
26
|
|
|
26
27
|
// Absolute path to the installed package root (src/main/ -> ../../), shown in
|
|
27
28
|
// the remediation message so the user can cd there and rebuild.
|
|
@@ -200,6 +201,7 @@ class PtyManager {
|
|
|
200
201
|
return { pid: null, cwd, reattached: false, error: String(err?.message || 'spawn-failed') };
|
|
201
202
|
}
|
|
202
203
|
console.log('[pty] spawned pid=', proc.pid, 'for tabId=', tabId);
|
|
204
|
+
telemetryCounters.trackSessionOpen();
|
|
203
205
|
|
|
204
206
|
const spawnedAt = Date.now();
|
|
205
207
|
let gotData = false;
|