teleportation-cli 1.4.2 → 1.4.4
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.
|
@@ -294,6 +294,13 @@ const sessionPidCache = new Map(); // sessionId -> number (claude_pid)
|
|
|
294
294
|
const lastPidCheck = new Map(); // sessionId -> number (timestamp of last check)
|
|
295
295
|
const PID_CHECK_INTERVAL_MS = parseInt(process.env.DAEMON_PID_CHECK_INTERVAL_MS || '30000', 10); // 30s default
|
|
296
296
|
|
|
297
|
+
// Sessions discovered from the session log on daemon restart get a grace period
|
|
298
|
+
// to receive a fresh hook registration (with a PID marker file). If no PID marker
|
|
299
|
+
// appears within LOG_RECOVERY_GRACE_MS, the session is treated as stale and removed.
|
|
300
|
+
// This prevents ghost sessions from other projects being heartbeated indefinitely.
|
|
301
|
+
const logRecoveredSessions = new Set(); // sessionIds recovered from session-events.log
|
|
302
|
+
const LOG_RECOVERY_GRACE_MS = parseInt(process.env.DAEMON_LOG_RECOVERY_GRACE_MS || '300000', 10); // 5 min default
|
|
303
|
+
|
|
297
304
|
// Transcript ingestion throttling: Prevents concurrent ingestion runs per session
|
|
298
305
|
// Map<session_id, Promise> tracks in-progress ingestion promises
|
|
299
306
|
// If ingestion takes >5 seconds, prevents stacking multiple concurrent calls
|
|
@@ -844,6 +851,8 @@ async function handleRequest(req, res) {
|
|
|
844
851
|
// Clear stale PID cache so the new session's PID is read fresh from marker file
|
|
845
852
|
sessionPidCache.delete(session_id);
|
|
846
853
|
lastPidCheck.delete(session_id);
|
|
854
|
+
// Fresh registration clears the log-recovery flag — this session now has a live hook
|
|
855
|
+
logRecoveredSessions.delete(session_id);
|
|
847
856
|
|
|
848
857
|
const sessionEntry = {
|
|
849
858
|
session_id,
|
|
@@ -1653,6 +1662,7 @@ async function cleanupDeadSession(session_id, pid) {
|
|
|
1653
1662
|
sessionPidCache.delete(session_id);
|
|
1654
1663
|
lastPidCheck.delete(session_id);
|
|
1655
1664
|
ingestionInProgress.delete(session_id);
|
|
1665
|
+
logRecoveredSessions.delete(session_id);
|
|
1656
1666
|
stoppedSessions.add(session_id); // Prevent re-activation from stale registration attempts
|
|
1657
1667
|
|
|
1658
1668
|
// Delete marker file
|
|
@@ -1721,7 +1731,21 @@ async function pollRelayAPI() {
|
|
|
1721
1731
|
await cleanupDeadSession(session_id, pid);
|
|
1722
1732
|
continue;
|
|
1723
1733
|
}
|
|
1724
|
-
|
|
1734
|
+
if (pid === null && logRecoveredSessions.has(session_id)) {
|
|
1735
|
+
// Session was recovered from the log but has no PID marker file yet.
|
|
1736
|
+
// Give it a grace period to receive a fresh hook registration.
|
|
1737
|
+
// After that, treat it as stale and clean it up to prevent ghost sessions
|
|
1738
|
+
// from other projects being heartbeated indefinitely.
|
|
1739
|
+
const sessionData = sessions.get(session_id);
|
|
1740
|
+
const age = pidCheckNow - (sessionData?.registered_at || pidCheckNow);
|
|
1741
|
+
if (age > LOG_RECOVERY_GRACE_MS) {
|
|
1742
|
+
logInfo(`[daemon] Log-recovered session ${session_id.slice(0, 8)}... has no PID marker after ${Math.round(age / 60000)}min — removing as stale`);
|
|
1743
|
+
await cleanupDeadSession(session_id, null);
|
|
1744
|
+
continue;
|
|
1745
|
+
}
|
|
1746
|
+
// Still within grace period — treat as alive, but don't reset the log-recovery flag
|
|
1747
|
+
}
|
|
1748
|
+
// PID is alive (or no PID file, still within grace period)
|
|
1725
1749
|
sessionActivity.set(session_id, pidCheckNow);
|
|
1726
1750
|
}
|
|
1727
1751
|
// Between PID checks, do NOT refresh sessionActivity - let the cleanup
|
|
@@ -3047,6 +3071,10 @@ async function discoverSessionsFromLog() {
|
|
|
3047
3071
|
for (const session of activeSessions) {
|
|
3048
3072
|
sessions.set(session.session_id, session);
|
|
3049
3073
|
sessionActivity.set(session.session_id, session.last_heartbeat);
|
|
3074
|
+
// Mark as log-recovered so PID check can apply a grace period.
|
|
3075
|
+
// A fresh session_start.mjs hook registration (which creates a PID marker file)
|
|
3076
|
+
// will clear this flag — the session is then treated as fully live.
|
|
3077
|
+
logRecoveredSessions.add(session.session_id);
|
|
3050
3078
|
}
|
|
3051
3079
|
|
|
3052
3080
|
if (activeSessions.length > 0) {
|
|
@@ -12,7 +12,6 @@ import { homedir, tmpdir } from 'os';
|
|
|
12
12
|
import { join } from 'path';
|
|
13
13
|
import { createHash } from 'node:crypto';
|
|
14
14
|
import { sanitizeEventData } from '../utils/log-sanitizer.js';
|
|
15
|
-
import { normalizeTranscriptEvents, normalizeTranscriptEntry } from '../intelligence/schema.js';
|
|
16
15
|
|
|
17
16
|
// ============================================================================
|
|
18
17
|
// Configuration Constants
|
|
@@ -591,13 +590,10 @@ function extractTimelineEvents(transcript, fromIndex = 0, sessionId = '') {
|
|
|
591
590
|
* @param {Object} context - Normalization context
|
|
592
591
|
* @returns {Array} Normalized intelligence events
|
|
593
592
|
*/
|
|
594
|
-
function normalizeTimelineEventsForIntelligence(
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
console.warn(`[transcript] Intelligence normalization failed: ${error.message}`);
|
|
599
|
-
return [];
|
|
600
|
-
}
|
|
593
|
+
function normalizeTimelineEventsForIntelligence(_events, _context = {}) {
|
|
594
|
+
// TODO(transcript-intelligence): re-enable when ../intelligence/schema.js lands.
|
|
595
|
+
// Callers handle empty arrays safely (no-op pipeline).
|
|
596
|
+
return [];
|
|
601
597
|
}
|
|
602
598
|
|
|
603
599
|
/**
|
|
@@ -887,16 +883,9 @@ export async function getTranscriptLength(claude_session_id) {
|
|
|
887
883
|
* Extract normalized transcript entries for the intelligence pipeline.
|
|
888
884
|
* Operates on raw transcript messages (not timeline events).
|
|
889
885
|
*/
|
|
890
|
-
function extractNormalizedTranscriptEntries(
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
normalized.push(normalizeTranscriptEntry(transcript[i], {
|
|
894
|
-
sessionId,
|
|
895
|
-
messageIndex: i,
|
|
896
|
-
harness,
|
|
897
|
-
}));
|
|
898
|
-
}
|
|
899
|
-
return normalized;
|
|
886
|
+
function extractNormalizedTranscriptEntries(_transcript, _fromIndex = 0, _sessionId = '', _harness = 'claude-code') {
|
|
887
|
+
// TODO(transcript-intelligence): re-enable when ../intelligence/schema.js lands.
|
|
888
|
+
return [];
|
|
900
889
|
}
|
|
901
890
|
|
|
902
891
|
function toSessionFilename(sessionId) {
|
|
@@ -100,28 +100,36 @@ export async function installViaUhr(manifestPath, hooksDir, options = {}) {
|
|
|
100
100
|
return { success: false, reason: 'UHR CLI not found (checked PATH and node_modules/.bin/uhr)' };
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
// Pass all supported platforms so UHR lockfile includes cursor + gemini-cli.
|
|
104
|
+
// Without this, UHR only tracks claude-code and warns that cursor/gemini-cli
|
|
105
|
+
// hooks are "not in lockfile platforms" (uhr.gm bug report, PR #10 fix).
|
|
106
|
+
const platforms = 'claude-code,gemini-cli,cursor';
|
|
107
|
+
|
|
103
108
|
const warnings = [];
|
|
104
109
|
try {
|
|
105
|
-
const
|
|
110
|
+
const result = execSync(`"${uhrBin}" install "${tempManifestPath}" --platforms ${platforms}`, {
|
|
106
111
|
encoding: 'utf8',
|
|
107
112
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
108
113
|
timeout: 30000,
|
|
109
114
|
});
|
|
110
115
|
|
|
111
|
-
// Collect
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
for (const line of
|
|
115
|
-
if (/warn/i.test(line))
|
|
116
|
-
warnings.push(line.trim());
|
|
117
|
-
}
|
|
116
|
+
// Collect warning lines from both stdout and stderr (UHR emits warnings to stderr)
|
|
117
|
+
for (const output of [result, result?.stderr]) {
|
|
118
|
+
if (!output) continue;
|
|
119
|
+
for (const line of output.split('\n').filter(Boolean)) {
|
|
120
|
+
if (/warn/i.test(line)) warnings.push(line.trim());
|
|
118
121
|
}
|
|
119
122
|
}
|
|
120
123
|
|
|
121
124
|
return { success: true, warnings };
|
|
122
125
|
} catch (err) {
|
|
126
|
+
// execSync throws when exit code != 0; collect stderr for diagnostics
|
|
127
|
+
const stderr = err?.stderr || '';
|
|
128
|
+
for (const line of stderr.split('\n').filter(Boolean)) {
|
|
129
|
+
if (/warn/i.test(line)) warnings.push(line.trim());
|
|
130
|
+
}
|
|
123
131
|
const msg = err instanceof Error ? err.message : String(err);
|
|
124
|
-
return { success: false, reason: msg || 'uhr install failed' };
|
|
132
|
+
return { success: false, reason: msg || 'uhr install failed', warnings };
|
|
125
133
|
}
|
|
126
134
|
}
|
|
127
135
|
|
package/package.json
CHANGED
package/teleportation-cli.cjs
CHANGED
|
@@ -7,7 +7,7 @@ const path = require('path');
|
|
|
7
7
|
const { execSync, spawnSync } = require('child_process');
|
|
8
8
|
const os = require('os');
|
|
9
9
|
|
|
10
|
-
const CLI_VERSION = '
|
|
10
|
+
const CLI_VERSION = require('./package.json').version;
|
|
11
11
|
const HOME_DIR = os.homedir();
|
|
12
12
|
// Teleportation project directory (for development)
|
|
13
13
|
// In production, hooks will be installed globally
|
|
@@ -3466,6 +3466,20 @@ async function commandInstallHooks() {
|
|
|
3466
3466
|
uhrResult.warnings.forEach(w => console.log(c.yellow(` ⚠️ ${w}`)));
|
|
3467
3467
|
}
|
|
3468
3468
|
installed = true;
|
|
3469
|
+
|
|
3470
|
+
// UHR only handles Claude Code — also install Cursor hooks via legacy installer
|
|
3471
|
+
try {
|
|
3472
|
+
const installerPath = path.join(TELEPORTATION_DIR, 'lib', 'install', 'installer.js');
|
|
3473
|
+
const { checkCursorIde, installCursorHooks } = await import('file://' + installerPath);
|
|
3474
|
+
if (checkCursorIde().valid) {
|
|
3475
|
+
const sourceHooksDir = path.join(TELEPORTATION_DIR, '.claude', 'hooks');
|
|
3476
|
+
await installCursorHooks(sourceHooksDir);
|
|
3477
|
+
console.log(c.green(' ✅ Cursor IDE hooks installed'));
|
|
3478
|
+
console.log(c.dim(' Config: ~/.cursor/hooks.json'));
|
|
3479
|
+
}
|
|
3480
|
+
} catch (e) {
|
|
3481
|
+
// Cursor not installed or hooks install failed — non-fatal
|
|
3482
|
+
}
|
|
3469
3483
|
} else {
|
|
3470
3484
|
console.log(c.yellow(` ⚠️ UHR install failed: ${uhrResult.reason}`));
|
|
3471
3485
|
console.log(c.dim(' Falling back to legacy installer...'));
|