@codemieai/code 0.0.14 → 0.0.15
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 +8 -6
- package/dist/agents/codemie-code/agent.d.ts.map +1 -1
- package/dist/agents/codemie-code/agent.js +4 -4
- package/dist/agents/codemie-code/agent.js.map +1 -1
- package/dist/agents/codemie-code/config.d.ts.map +1 -1
- package/dist/agents/codemie-code/config.js +2 -1
- package/dist/agents/codemie-code/config.js.map +1 -1
- package/dist/agents/codemie-code/filters.js +12 -12
- package/dist/agents/codemie-code/filters.js.map +1 -1
- package/dist/agents/codemie-code/index.d.ts.map +1 -1
- package/dist/agents/codemie-code/index.js +3 -1
- package/dist/agents/codemie-code/index.js.map +1 -1
- package/dist/agents/codemie-code/storage/todoStorage.js +1 -1
- package/dist/agents/codemie-code/storage/todoStorage.js.map +1 -1
- package/dist/agents/codemie-code/tools/planning.d.ts +1 -1
- package/dist/agents/codemie-code/ui.js +1 -1
- package/dist/agents/codemie-code/ui.js.map +1 -1
- package/dist/agents/core/AgentCLI.js +1 -1
- package/dist/agents/core/AgentCLI.js.map +1 -1
- package/dist/agents/core/types.d.ts +5 -0
- package/dist/agents/core/types.d.ts.map +1 -1
- package/dist/agents/plugins/codemie-code.plugin.js +1 -1
- package/dist/agents/plugins/codemie-code.plugin.js.map +1 -1
- package/dist/analytics/aggregation/adapters/claude.adapter.d.ts.map +1 -1
- package/dist/analytics/aggregation/adapters/claude.adapter.js +75 -15
- package/dist/analytics/aggregation/adapters/claude.adapter.js.map +1 -1
- package/dist/analytics/aggregation/adapters/codex.adapter.d.ts.map +1 -1
- package/dist/analytics/aggregation/adapters/codex.adapter.js +20 -0
- package/dist/analytics/aggregation/adapters/codex.adapter.js.map +1 -1
- package/dist/analytics/aggregation/adapters/gemini.adapter.d.ts +8 -0
- package/dist/analytics/aggregation/adapters/gemini.adapter.d.ts.map +1 -1
- package/dist/analytics/aggregation/adapters/gemini.adapter.js +46 -1
- package/dist/analytics/aggregation/adapters/gemini.adapter.js.map +1 -1
- package/dist/analytics/aggregation/core/BaseAnalyticsAdapter.d.ts +36 -0
- package/dist/analytics/aggregation/core/BaseAnalyticsAdapter.d.ts.map +1 -1
- package/dist/analytics/aggregation/core/BaseAnalyticsAdapter.js +52 -0
- package/dist/analytics/aggregation/core/BaseAnalyticsAdapter.js.map +1 -1
- package/dist/analytics/aggregation/core/adapter.interface.d.ts +11 -0
- package/dist/analytics/aggregation/core/adapter.interface.d.ts.map +1 -1
- package/dist/analytics/aggregation/core/aggregation-utils.d.ts +20 -0
- package/dist/analytics/aggregation/core/aggregation-utils.d.ts.map +1 -1
- package/dist/analytics/aggregation/core/aggregation-utils.js +43 -0
- package/dist/analytics/aggregation/core/aggregation-utils.js.map +1 -1
- package/dist/analytics/aggregation/core/file-utils.d.ts +2 -1
- package/dist/analytics/aggregation/core/file-utils.d.ts.map +1 -1
- package/dist/analytics/aggregation/core/file-utils.js +20 -85
- package/dist/analytics/aggregation/core/file-utils.js.map +1 -1
- package/dist/analytics/aggregation/core/index.d.ts +3 -0
- package/dist/analytics/aggregation/core/index.d.ts.map +1 -1
- package/dist/analytics/aggregation/core/index.js +3 -0
- package/dist/analytics/aggregation/core/index.js.map +1 -1
- package/dist/analytics/aggregation/core/user-prompt-source.d.ts +81 -0
- package/dist/analytics/aggregation/core/user-prompt-source.d.ts.map +1 -0
- package/dist/analytics/aggregation/core/user-prompt-source.js +69 -0
- package/dist/analytics/aggregation/core/user-prompt-source.js.map +1 -0
- package/dist/analytics/aggregation/core/user-prompt-sources/json.d.ts +49 -0
- package/dist/analytics/aggregation/core/user-prompt-sources/json.d.ts.map +1 -0
- package/dist/analytics/aggregation/core/user-prompt-sources/json.js +66 -0
- package/dist/analytics/aggregation/core/user-prompt-sources/json.js.map +1 -0
- package/dist/analytics/aggregation/core/user-prompt-sources/jsonl.d.ts +43 -0
- package/dist/analytics/aggregation/core/user-prompt-sources/jsonl.d.ts.map +1 -0
- package/dist/analytics/aggregation/core/user-prompt-sources/jsonl.js +56 -0
- package/dist/analytics/aggregation/core/user-prompt-sources/jsonl.js.map +1 -0
- package/dist/analytics/aggregation/types.d.ts +20 -0
- package/dist/analytics/aggregation/types.d.ts.map +1 -1
- package/dist/analytics/remote-submission/cursor-manager.d.ts +71 -0
- package/dist/analytics/remote-submission/cursor-manager.d.ts.map +1 -0
- package/dist/analytics/remote-submission/cursor-manager.js +204 -0
- package/dist/analytics/remote-submission/cursor-manager.js.map +1 -0
- package/dist/analytics/remote-submission/index.d.ts +12 -0
- package/dist/analytics/remote-submission/index.d.ts.map +1 -0
- package/dist/analytics/remote-submission/index.js +11 -0
- package/dist/analytics/remote-submission/index.js.map +1 -0
- package/dist/analytics/remote-submission/lock-manager.d.ts +71 -0
- package/dist/analytics/remote-submission/lock-manager.d.ts.map +1 -0
- package/dist/analytics/remote-submission/lock-manager.js +238 -0
- package/dist/analytics/remote-submission/lock-manager.js.map +1 -0
- package/dist/analytics/remote-submission/metric-transformer.d.ts +49 -0
- package/dist/analytics/remote-submission/metric-transformer.d.ts.map +1 -0
- package/dist/analytics/remote-submission/metric-transformer.js +175 -0
- package/dist/analytics/remote-submission/metric-transformer.js.map +1 -0
- package/dist/analytics/remote-submission/submitter.d.ts +78 -0
- package/dist/analytics/remote-submission/submitter.d.ts.map +1 -0
- package/dist/analytics/remote-submission/submitter.js +381 -0
- package/dist/analytics/remote-submission/submitter.js.map +1 -0
- package/dist/analytics/remote-submission/types.d.ts +169 -0
- package/dist/analytics/remote-submission/types.d.ts.map +1 -0
- package/dist/analytics/remote-submission/types.js +13 -0
- package/dist/analytics/remote-submission/types.js.map +1 -0
- package/dist/cli/commands/analytics.d.ts.map +1 -1
- package/dist/cli/commands/analytics.js +181 -12
- package/dist/cli/commands/analytics.js.map +1 -1
- package/dist/cli/commands/doctor/index.d.ts.map +1 -1
- package/dist/cli/commands/doctor/index.js +13 -1
- package/dist/cli/commands/doctor/index.js.map +1 -1
- package/dist/cli/commands/doctor/providers/AIRunSSOProviderCheck.d.ts.map +1 -1
- package/dist/cli/commands/doctor/providers/AIRunSSOProviderCheck.js +81 -9
- package/dist/cli/commands/doctor/providers/AIRunSSOProviderCheck.js.map +1 -1
- package/dist/cli/commands/profile.d.ts.map +1 -1
- package/dist/cli/commands/profile.js +25 -57
- package/dist/cli/commands/profile.js.map +1 -1
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +10 -0
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/workflow.js +1 -1
- package/dist/cli/commands/workflow.js.map +1 -1
- package/dist/cli/index.js +0 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/utils/analytics-reader.js +1 -1
- package/dist/utils/analytics-reader.js.map +1 -1
- package/dist/utils/codemie-model-fetcher.d.ts.map +1 -1
- package/dist/utils/codemie-model-fetcher.js +197 -122
- package/dist/utils/codemie-model-fetcher.js.map +1 -1
- package/dist/utils/codemie-proxy.d.ts +41 -21
- package/dist/utils/codemie-proxy.d.ts.map +1 -1
- package/dist/utils/codemie-proxy.js +151 -86
- package/dist/utils/codemie-proxy.js.map +1 -1
- package/dist/utils/config-loader.d.ts +4 -0
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/config-loader.js +42 -2
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/first-time.d.ts +0 -4
- package/dist/utils/first-time.d.ts.map +1 -1
- package/dist/utils/first-time.js +5 -117
- package/dist/utils/first-time.js.map +1 -1
- package/dist/utils/logger.d.ts +31 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +112 -2
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/proxy/plugins/analytics.plugin.d.ts +19 -0
- package/dist/utils/proxy/plugins/analytics.plugin.d.ts.map +1 -0
- package/dist/utils/proxy/plugins/analytics.plugin.js +84 -0
- package/dist/utils/proxy/plugins/analytics.plugin.js.map +1 -0
- package/dist/utils/proxy/plugins/header-injection.plugin.d.ts +16 -0
- package/dist/utils/proxy/plugins/header-injection.plugin.d.ts.map +1 -0
- package/dist/utils/proxy/plugins/header-injection.plugin.js +48 -0
- package/dist/utils/proxy/plugins/header-injection.plugin.js.map +1 -0
- package/dist/utils/proxy/plugins/index.d.ts +18 -0
- package/dist/utils/proxy/plugins/index.d.ts.map +1 -0
- package/dist/utils/proxy/plugins/index.js +30 -0
- package/dist/utils/proxy/plugins/index.js.map +1 -0
- package/dist/utils/proxy/plugins/registry.d.ts +50 -0
- package/dist/utils/proxy/plugins/registry.d.ts.map +1 -0
- package/dist/utils/proxy/plugins/registry.js +124 -0
- package/dist/utils/proxy/plugins/registry.js.map +1 -0
- package/dist/utils/proxy/plugins/sso-auth.plugin.d.ts +16 -0
- package/dist/utils/proxy/plugins/sso-auth.plugin.d.ts.map +1 -0
- package/dist/utils/proxy/plugins/sso-auth.plugin.js +35 -0
- package/dist/utils/proxy/plugins/sso-auth.plugin.js.map +1 -0
- package/dist/utils/proxy/plugins/types.d.ts +79 -0
- package/dist/utils/proxy/plugins/types.d.ts.map +1 -0
- package/dist/utils/proxy/plugins/types.js +8 -0
- package/dist/utils/proxy/plugins/types.js.map +1 -0
- package/dist/utils/sanitize.d.ts +28 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +213 -0
- package/dist/utils/sanitize.js.map +1 -0
- package/dist/workflows/installer.d.ts.map +1 -1
- package/dist/workflows/installer.js +3 -4
- package/dist/workflows/installer.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/commands/config.d.ts +0 -3
- package/dist/cli/commands/config.d.ts.map +0 -1
- package/dist/cli/commands/config.js +0 -198
- package/dist/cli/commands/config.js.map +0 -1
- package/dist/cli/commands/env.d.ts +0 -3
- package/dist/cli/commands/env.d.ts.map +0 -1
- package/dist/cli/commands/env.js +0 -19
- package/dist/cli/commands/env.js.map +0 -1
- package/dist/utils/proxy/interceptors.d.ts +0 -69
- package/dist/utils/proxy/interceptors.d.ts.map +0 -1
- package/dist/utils/proxy/interceptors.js +0 -308
- package/dist/utils/proxy/interceptors.js.map +0 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor Manager - Event-Level Tracking
|
|
3
|
+
*
|
|
4
|
+
* Manages cursor state for tracking which events have been submitted
|
|
5
|
+
* to prevent duplicate submissions across concurrent proxy instances.
|
|
6
|
+
*
|
|
7
|
+
* Uses event-level tracking (message IDs, tool call IDs) instead of
|
|
8
|
+
* session-level hashes to handle incremental session updates correctly.
|
|
9
|
+
*/
|
|
10
|
+
import type { CursorState, AgentCursorState, SessionSubmissionState } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Cursor Manager
|
|
13
|
+
*/
|
|
14
|
+
export declare class CursorManager {
|
|
15
|
+
private cursorPath;
|
|
16
|
+
constructor(analyticsDir?: string);
|
|
17
|
+
/**
|
|
18
|
+
* Ensure cursor directory exists
|
|
19
|
+
*/
|
|
20
|
+
private ensureDir;
|
|
21
|
+
/**
|
|
22
|
+
* Load cursor state from disk
|
|
23
|
+
*/
|
|
24
|
+
load(): Promise<CursorState>;
|
|
25
|
+
/**
|
|
26
|
+
* Save cursor state to disk
|
|
27
|
+
*/
|
|
28
|
+
save(cursor: CursorState): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Get or create agent cursor state
|
|
31
|
+
*/
|
|
32
|
+
getAgentCursor(cursor: CursorState, agentName: string): AgentCursorState;
|
|
33
|
+
/**
|
|
34
|
+
* Get or create session submission state
|
|
35
|
+
*/
|
|
36
|
+
getSessionState(agentCursor: AgentCursorState, sessionId: string): SessionSubmissionState;
|
|
37
|
+
/**
|
|
38
|
+
* Update session state with sent events
|
|
39
|
+
*/
|
|
40
|
+
updateSessionState(cursor: CursorState, agentName: string, sessionId: string, updates: {
|
|
41
|
+
newMessageIds?: string[];
|
|
42
|
+
newToolCallIds?: string[];
|
|
43
|
+
metricsSubmitted: number;
|
|
44
|
+
lastActivityTime: string;
|
|
45
|
+
sessionMetrics?: Partial<SessionSubmissionState['sessionMetrics']>;
|
|
46
|
+
}): void;
|
|
47
|
+
/**
|
|
48
|
+
* Check if event was already sent
|
|
49
|
+
*/
|
|
50
|
+
isEventSent(sessionState: SessionSubmissionState, eventId: string, eventType: 'message' | 'toolCall'): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Filter events to only include those not yet sent
|
|
53
|
+
*/
|
|
54
|
+
filterNewEvents<T extends {
|
|
55
|
+
messageId?: string;
|
|
56
|
+
toolCallId?: string;
|
|
57
|
+
}>(events: T[], sessionState: SessionSubmissionState, eventType: 'message' | 'toolCall'): T[];
|
|
58
|
+
/**
|
|
59
|
+
* Increment failure count
|
|
60
|
+
*/
|
|
61
|
+
recordFailure(cursor: CursorState): void;
|
|
62
|
+
/**
|
|
63
|
+
* Reset failure count
|
|
64
|
+
*/
|
|
65
|
+
resetFailures(cursor: CursorState): void;
|
|
66
|
+
/**
|
|
67
|
+
* Update scan timestamp for agent
|
|
68
|
+
*/
|
|
69
|
+
updateScanTime(cursor: CursorState, agentName: string): void;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=cursor-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor-manager.d.ts","sourceRoot":"","sources":["../../../src/analytics/remote-submission/cursor-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EACV,WAAW,EACX,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAmDpB;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAAS;gBAEf,YAAY,CAAC,EAAE,MAAM;IAKjC;;OAEG;YACW,SAAS;IAOvB;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC;IAwBlC;;OAEG;IACG,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAa9C;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,gBAAgB;IAOxE;;OAEG;IACH,eAAe,CACb,WAAW,EAAE,gBAAgB,EAC7B,SAAS,EAAE,MAAM,GAChB,sBAAsB;IAOzB;;OAEG;IACH,kBAAkB,CAChB,MAAM,EAAE,WAAW,EACnB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;QACP,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;QACzB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC;QACzB,gBAAgB,EAAE,MAAM,CAAC;QACzB,cAAc,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,gBAAgB,CAAC,CAAC,CAAC;KACpE,GACA,IAAI;IA8BP;;OAEG;IACH,WAAW,CACT,YAAY,EAAE,sBAAsB,EACpC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,GAAG,UAAU,GAChC,OAAO;IAQV;;OAEG;IACH,eAAe,CAAC,CAAC,SAAS;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,EACnE,MAAM,EAAE,CAAC,EAAE,EACX,YAAY,EAAE,sBAAsB,EACpC,SAAS,EAAE,SAAS,GAAG,UAAU,GAChC,CAAC,EAAE;IAYN;;OAEG;IACH,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAIxC;;OAEG;IACH,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAIxC;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;CAI7D"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor Manager - Event-Level Tracking
|
|
3
|
+
*
|
|
4
|
+
* Manages cursor state for tracking which events have been submitted
|
|
5
|
+
* to prevent duplicate submissions across concurrent proxy instances.
|
|
6
|
+
*
|
|
7
|
+
* Uses event-level tracking (message IDs, tool call IDs) instead of
|
|
8
|
+
* session-level hashes to handle incremental session updates correctly.
|
|
9
|
+
*/
|
|
10
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
11
|
+
import { existsSync } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { homedir } from 'node:os';
|
|
14
|
+
import { logger } from '../../utils/logger.js';
|
|
15
|
+
/**
|
|
16
|
+
* Default cursor state
|
|
17
|
+
*/
|
|
18
|
+
function createDefaultCursor() {
|
|
19
|
+
return {
|
|
20
|
+
version: 2,
|
|
21
|
+
lastRun: new Date().toISOString(),
|
|
22
|
+
agents: {},
|
|
23
|
+
stats: {
|
|
24
|
+
totalSessionsSubmitted: 0,
|
|
25
|
+
totalMetricsSubmitted: 0,
|
|
26
|
+
consecutiveFailures: 0
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Default agent cursor state
|
|
32
|
+
*/
|
|
33
|
+
function createDefaultAgentCursor() {
|
|
34
|
+
return {
|
|
35
|
+
lastScan: new Date().toISOString(),
|
|
36
|
+
sessions: {}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Default session submission state
|
|
41
|
+
*/
|
|
42
|
+
function createDefaultSessionState() {
|
|
43
|
+
return {
|
|
44
|
+
submitted: false,
|
|
45
|
+
timestamp: new Date().toISOString(),
|
|
46
|
+
sentEventIds: {
|
|
47
|
+
messages: [],
|
|
48
|
+
toolCalls: []
|
|
49
|
+
},
|
|
50
|
+
sessionMetrics: {
|
|
51
|
+
timeoutSent: false,
|
|
52
|
+
resumedSent: false,
|
|
53
|
+
completedSent: false
|
|
54
|
+
},
|
|
55
|
+
lastActivityTime: new Date().toISOString(),
|
|
56
|
+
metricsSubmitted: 0,
|
|
57
|
+
lastUpdate: new Date().toISOString()
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Cursor Manager
|
|
62
|
+
*/
|
|
63
|
+
export class CursorManager {
|
|
64
|
+
cursorPath;
|
|
65
|
+
constructor(analyticsDir) {
|
|
66
|
+
const baseDir = analyticsDir || join(homedir(), '.codemie', 'analytics');
|
|
67
|
+
this.cursorPath = join(baseDir, '.remote', 'cursor.json');
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Ensure cursor directory exists
|
|
71
|
+
*/
|
|
72
|
+
async ensureDir() {
|
|
73
|
+
const dir = join(this.cursorPath, '..');
|
|
74
|
+
if (!existsSync(dir)) {
|
|
75
|
+
await mkdir(dir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Load cursor state from disk
|
|
80
|
+
*/
|
|
81
|
+
async load() {
|
|
82
|
+
await this.ensureDir();
|
|
83
|
+
if (!existsSync(this.cursorPath)) {
|
|
84
|
+
return createDefaultCursor();
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const content = await readFile(this.cursorPath, 'utf-8');
|
|
88
|
+
const cursor = JSON.parse(content);
|
|
89
|
+
// Validate version
|
|
90
|
+
if (cursor.version !== 2) {
|
|
91
|
+
logger.debug(`Cursor version ${cursor.version} not supported, resetting`);
|
|
92
|
+
return createDefaultCursor();
|
|
93
|
+
}
|
|
94
|
+
return cursor;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
logger.debug(`Failed to load cursor: ${error}`);
|
|
98
|
+
return createDefaultCursor();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Save cursor state to disk
|
|
103
|
+
*/
|
|
104
|
+
async save(cursor) {
|
|
105
|
+
await this.ensureDir();
|
|
106
|
+
try {
|
|
107
|
+
cursor.lastRun = new Date().toISOString();
|
|
108
|
+
const content = JSON.stringify(cursor, null, 2);
|
|
109
|
+
await writeFile(this.cursorPath, content, 'utf-8');
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
logger.debug(`Failed to save cursor: ${error}`);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get or create agent cursor state
|
|
118
|
+
*/
|
|
119
|
+
getAgentCursor(cursor, agentName) {
|
|
120
|
+
if (!cursor.agents[agentName]) {
|
|
121
|
+
cursor.agents[agentName] = createDefaultAgentCursor();
|
|
122
|
+
}
|
|
123
|
+
return cursor.agents[agentName];
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get or create session submission state
|
|
127
|
+
*/
|
|
128
|
+
getSessionState(agentCursor, sessionId) {
|
|
129
|
+
if (!agentCursor.sessions[sessionId]) {
|
|
130
|
+
agentCursor.sessions[sessionId] = createDefaultSessionState();
|
|
131
|
+
}
|
|
132
|
+
return agentCursor.sessions[sessionId];
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Update session state with sent events
|
|
136
|
+
*/
|
|
137
|
+
updateSessionState(cursor, agentName, sessionId, updates) {
|
|
138
|
+
const agentCursor = this.getAgentCursor(cursor, agentName);
|
|
139
|
+
const sessionState = this.getSessionState(agentCursor, sessionId);
|
|
140
|
+
// Append new event IDs
|
|
141
|
+
if (updates.newMessageIds) {
|
|
142
|
+
sessionState.sentEventIds.messages.push(...updates.newMessageIds);
|
|
143
|
+
}
|
|
144
|
+
if (updates.newToolCallIds) {
|
|
145
|
+
sessionState.sentEventIds.toolCalls.push(...updates.newToolCallIds);
|
|
146
|
+
}
|
|
147
|
+
// Update session metrics
|
|
148
|
+
if (updates.sessionMetrics) {
|
|
149
|
+
sessionState.sessionMetrics = {
|
|
150
|
+
...sessionState.sessionMetrics,
|
|
151
|
+
...updates.sessionMetrics
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
// Update metadata
|
|
155
|
+
sessionState.submitted = true;
|
|
156
|
+
sessionState.metricsSubmitted += updates.metricsSubmitted;
|
|
157
|
+
sessionState.lastActivityTime = updates.lastActivityTime;
|
|
158
|
+
sessionState.lastUpdate = new Date().toISOString();
|
|
159
|
+
// Update global stats
|
|
160
|
+
cursor.stats.totalMetricsSubmitted += updates.metricsSubmitted;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Check if event was already sent
|
|
164
|
+
*/
|
|
165
|
+
isEventSent(sessionState, eventId, eventType) {
|
|
166
|
+
const sentIds = eventType === 'message'
|
|
167
|
+
? sessionState.sentEventIds.messages
|
|
168
|
+
: sessionState.sentEventIds.toolCalls;
|
|
169
|
+
return sentIds.includes(eventId);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Filter events to only include those not yet sent
|
|
173
|
+
*/
|
|
174
|
+
filterNewEvents(events, sessionState, eventType) {
|
|
175
|
+
return events.filter(event => {
|
|
176
|
+
const eventId = eventType === 'message'
|
|
177
|
+
? event.messageId
|
|
178
|
+
: event.toolCallId;
|
|
179
|
+
if (!eventId)
|
|
180
|
+
return false;
|
|
181
|
+
return !this.isEventSent(sessionState, eventId, eventType);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Increment failure count
|
|
186
|
+
*/
|
|
187
|
+
recordFailure(cursor) {
|
|
188
|
+
cursor.stats.consecutiveFailures++;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Reset failure count
|
|
192
|
+
*/
|
|
193
|
+
resetFailures(cursor) {
|
|
194
|
+
cursor.stats.consecutiveFailures = 0;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Update scan timestamp for agent
|
|
198
|
+
*/
|
|
199
|
+
updateScanTime(cursor, agentName) {
|
|
200
|
+
const agentCursor = this.getAgentCursor(cursor, agentName);
|
|
201
|
+
agentCursor.lastScan = new Date().toISOString();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=cursor-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor-manager.js","sourceRoot":"","sources":["../../../src/analytics/remote-submission/cursor-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote Analytics Submission System
|
|
3
|
+
*
|
|
4
|
+
* Scheduled submission of analytics metrics to /v1/metrics endpoint
|
|
5
|
+
* with event-level tracking and backend-aligned metric transformation.
|
|
6
|
+
*/
|
|
7
|
+
export { RemoteAnalyticsSubmitter } from './submitter.js';
|
|
8
|
+
export { CursorManager } from './cursor-manager.js';
|
|
9
|
+
export { LockManager } from './lock-manager.js';
|
|
10
|
+
export { transformSessionToMetrics, createSessionMetric } from './metric-transformer.js';
|
|
11
|
+
export type { MetricPayload, MetricName, BaseMetricAttributes, ToolSuccessAttributes, TokenMetricAttributes, ToolErrorAttributes, SessionMetricAttributes, CursorState, AgentCursorState, SessionSubmissionState, LockInfo, RemoteSubmissionConfig, SessionStatus } from './types.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/analytics/remote-submission/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACzF,YAAY,EACV,aAAa,EACb,UAAU,EACV,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACnB,uBAAuB,EACvB,WAAW,EACX,gBAAgB,EAChB,sBAAsB,EACtB,QAAQ,EACR,sBAAsB,EACtB,aAAa,EACd,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote Analytics Submission System
|
|
3
|
+
*
|
|
4
|
+
* Scheduled submission of analytics metrics to /v1/metrics endpoint
|
|
5
|
+
* with event-level tracking and backend-aligned metric transformation.
|
|
6
|
+
*/
|
|
7
|
+
export { RemoteAnalyticsSubmitter } from './submitter.js';
|
|
8
|
+
export { CursorManager } from './cursor-manager.js';
|
|
9
|
+
export { LockManager } from './lock-manager.js';
|
|
10
|
+
export { transformSessionToMetrics, createSessionMetric } from './metric-transformer.js';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/analytics/remote-submission/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lock Manager - File-Based Locking for Concurrency Control
|
|
3
|
+
*
|
|
4
|
+
* Implements file-based locking to ensure only one RemoteAnalyticsSubmitter
|
|
5
|
+
* runs at a time across multiple concurrent proxy instances.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Exclusive lock acquisition with retries
|
|
9
|
+
* - Stale lock detection and recovery
|
|
10
|
+
* - Process liveness checking
|
|
11
|
+
* - Heartbeat to prevent false stale detection
|
|
12
|
+
* - Graceful cleanup on exit signals
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Lock Manager
|
|
16
|
+
*/
|
|
17
|
+
export declare class LockManager {
|
|
18
|
+
private lockPath;
|
|
19
|
+
private lockInfo;
|
|
20
|
+
private heartbeatTimer;
|
|
21
|
+
constructor(analyticsDir?: string);
|
|
22
|
+
/**
|
|
23
|
+
* Ensure lock directory exists
|
|
24
|
+
*/
|
|
25
|
+
private ensureDir;
|
|
26
|
+
/**
|
|
27
|
+
* Check if a process is alive
|
|
28
|
+
*/
|
|
29
|
+
private isProcessAlive;
|
|
30
|
+
/**
|
|
31
|
+
* Read lock file
|
|
32
|
+
*/
|
|
33
|
+
private readLock;
|
|
34
|
+
/**
|
|
35
|
+
* Write lock file
|
|
36
|
+
*/
|
|
37
|
+
private writeLock;
|
|
38
|
+
/**
|
|
39
|
+
* Remove lock file
|
|
40
|
+
*/
|
|
41
|
+
private removeLock;
|
|
42
|
+
/**
|
|
43
|
+
* Check if lock is stale (old or process dead)
|
|
44
|
+
*/
|
|
45
|
+
private isLockStale;
|
|
46
|
+
/**
|
|
47
|
+
* Start heartbeat to refresh lock timestamp
|
|
48
|
+
*/
|
|
49
|
+
private startHeartbeat;
|
|
50
|
+
/**
|
|
51
|
+
* Stop heartbeat
|
|
52
|
+
*/
|
|
53
|
+
private stopHeartbeat;
|
|
54
|
+
/**
|
|
55
|
+
* Acquire lock with retry
|
|
56
|
+
*
|
|
57
|
+
* @param name Lock name (for logging)
|
|
58
|
+
* @param maxRetries Maximum number of retries
|
|
59
|
+
* @returns true if lock acquired, false if failed
|
|
60
|
+
*/
|
|
61
|
+
acquire(name: string, maxRetries?: number): Promise<boolean>;
|
|
62
|
+
/**
|
|
63
|
+
* Release lock
|
|
64
|
+
*/
|
|
65
|
+
release(): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Setup exit handlers to release lock on process termination
|
|
68
|
+
*/
|
|
69
|
+
private setupExitHandlers;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=lock-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock-manager.d.ts","sourceRoot":"","sources":["../../../src/analytics/remote-submission/lock-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,cAAc,CAA+B;gBAEzC,YAAY,CAAC,EAAE,MAAM;IAKjC;;OAEG;YACW,SAAS;IAOvB;;OAEG;IACH,OAAO,CAAC,cAAc;IAUtB;;OAEG;YACW,QAAQ;IActB;;OAEG;YACW,SAAS;IAMvB;;OAEG;YACW,UAAU;IAUxB;;OAEG;YACW,WAAW;IAezB;;OAEG;IACH,OAAO,CAAC,cAAc;IAgBtB;;OAEG;IACH,OAAO,CAAC,aAAa;IAOrB;;;;;;OAMG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,SAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAyE7D;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAe9B;;OAEG;IACH,OAAO,CAAC,iBAAiB;CAU1B"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lock Manager - File-Based Locking for Concurrency Control
|
|
3
|
+
*
|
|
4
|
+
* Implements file-based locking to ensure only one RemoteAnalyticsSubmitter
|
|
5
|
+
* runs at a time across multiple concurrent proxy instances.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Exclusive lock acquisition with retries
|
|
9
|
+
* - Stale lock detection and recovery
|
|
10
|
+
* - Process liveness checking
|
|
11
|
+
* - Heartbeat to prevent false stale detection
|
|
12
|
+
* - Graceful cleanup on exit signals
|
|
13
|
+
*/
|
|
14
|
+
import { readFile, writeFile, unlink, mkdir } from 'node:fs/promises';
|
|
15
|
+
import { existsSync } from 'node:fs';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { homedir, hostname } from 'node:os';
|
|
18
|
+
import { logger } from '../../utils/logger.js';
|
|
19
|
+
const LOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
|
20
|
+
const RETRY_DELAY = 2000; // 2 seconds
|
|
21
|
+
const HEARTBEAT_INTERVAL = 30 * 1000; // 30 seconds
|
|
22
|
+
/**
|
|
23
|
+
* Lock Manager
|
|
24
|
+
*/
|
|
25
|
+
export class LockManager {
|
|
26
|
+
lockPath;
|
|
27
|
+
lockInfo = null;
|
|
28
|
+
heartbeatTimer = null;
|
|
29
|
+
constructor(analyticsDir) {
|
|
30
|
+
const baseDir = analyticsDir || join(homedir(), '.codemie', 'analytics');
|
|
31
|
+
this.lockPath = join(baseDir, '.remote', '.lock');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Ensure lock directory exists
|
|
35
|
+
*/
|
|
36
|
+
async ensureDir() {
|
|
37
|
+
const dir = join(this.lockPath, '..');
|
|
38
|
+
if (!existsSync(dir)) {
|
|
39
|
+
await mkdir(dir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check if a process is alive
|
|
44
|
+
*/
|
|
45
|
+
isProcessAlive(pid) {
|
|
46
|
+
try {
|
|
47
|
+
// kill(pid, 0) doesn't actually kill, just checks if process exists
|
|
48
|
+
process.kill(pid, 0);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Read lock file
|
|
57
|
+
*/
|
|
58
|
+
async readLock() {
|
|
59
|
+
if (!existsSync(this.lockPath)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const content = await readFile(this.lockPath, 'utf-8');
|
|
64
|
+
return JSON.parse(content);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
logger.debug(`Failed to read lock file: ${error}`);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Write lock file
|
|
73
|
+
*/
|
|
74
|
+
async writeLock(lockInfo) {
|
|
75
|
+
await this.ensureDir();
|
|
76
|
+
const content = JSON.stringify(lockInfo, null, 2);
|
|
77
|
+
await writeFile(this.lockPath, content, 'utf-8');
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Remove lock file
|
|
81
|
+
*/
|
|
82
|
+
async removeLock() {
|
|
83
|
+
if (existsSync(this.lockPath)) {
|
|
84
|
+
try {
|
|
85
|
+
await unlink(this.lockPath);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
logger.debug(`Failed to remove lock: ${error}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check if lock is stale (old or process dead)
|
|
94
|
+
*/
|
|
95
|
+
async isLockStale(lockInfo) {
|
|
96
|
+
// Check age
|
|
97
|
+
const age = Date.now() - new Date(lockInfo.timestamp).getTime();
|
|
98
|
+
if (age > LOCK_TIMEOUT) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
// Check if process is alive
|
|
102
|
+
if (!this.isProcessAlive(lockInfo.pid)) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Start heartbeat to refresh lock timestamp
|
|
109
|
+
*/
|
|
110
|
+
startHeartbeat() {
|
|
111
|
+
this.heartbeatTimer = setInterval(async () => {
|
|
112
|
+
if (this.lockInfo) {
|
|
113
|
+
this.lockInfo.timestamp = new Date().toISOString();
|
|
114
|
+
try {
|
|
115
|
+
await this.writeLock(this.lockInfo);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
logger.debug(`Heartbeat failed: ${error}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}, HEARTBEAT_INTERVAL);
|
|
122
|
+
// Ensure heartbeat doesn't prevent process exit
|
|
123
|
+
this.heartbeatTimer.unref();
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Stop heartbeat
|
|
127
|
+
*/
|
|
128
|
+
stopHeartbeat() {
|
|
129
|
+
if (this.heartbeatTimer) {
|
|
130
|
+
clearInterval(this.heartbeatTimer);
|
|
131
|
+
this.heartbeatTimer = null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Acquire lock with retry
|
|
136
|
+
*
|
|
137
|
+
* @param name Lock name (for logging)
|
|
138
|
+
* @param maxRetries Maximum number of retries
|
|
139
|
+
* @returns true if lock acquired, false if failed
|
|
140
|
+
*/
|
|
141
|
+
async acquire(name, maxRetries = 3) {
|
|
142
|
+
let attempts = 0;
|
|
143
|
+
while (attempts < maxRetries) {
|
|
144
|
+
attempts++;
|
|
145
|
+
// Check existing lock
|
|
146
|
+
const existingLock = await this.readLock();
|
|
147
|
+
if (existingLock) {
|
|
148
|
+
// Check if stale
|
|
149
|
+
if (await this.isLockStale(existingLock)) {
|
|
150
|
+
logger.debug(`Removing stale lock from ${existingLock.agent} (PID ${existingLock.pid})`);
|
|
151
|
+
await this.removeLock();
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// Lock is valid, wait and retry
|
|
155
|
+
if (attempts < maxRetries) {
|
|
156
|
+
logger.debug(`Lock held by ${existingLock.agent} (PID ${existingLock.pid}), ` +
|
|
157
|
+
`waiting ${RETRY_DELAY}ms (attempt ${attempts}/${maxRetries})`);
|
|
158
|
+
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
logger.debug(`Could not acquire lock after ${maxRetries} attempts`);
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Try to acquire lock
|
|
168
|
+
this.lockInfo = {
|
|
169
|
+
pid: process.pid,
|
|
170
|
+
timestamp: new Date().toISOString(),
|
|
171
|
+
hostname: hostname(),
|
|
172
|
+
agent: name
|
|
173
|
+
};
|
|
174
|
+
try {
|
|
175
|
+
await this.writeLock(this.lockInfo);
|
|
176
|
+
// Verify we own the lock (race condition check)
|
|
177
|
+
const verifyLock = await this.readLock();
|
|
178
|
+
if (verifyLock?.pid === process.pid) {
|
|
179
|
+
logger.debug(`Lock acquired by ${name} (PID ${process.pid})`);
|
|
180
|
+
this.startHeartbeat();
|
|
181
|
+
this.setupExitHandlers();
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
// Someone else got the lock first
|
|
186
|
+
this.lockInfo = null;
|
|
187
|
+
if (attempts < maxRetries) {
|
|
188
|
+
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
logger.debug(`Failed to acquire lock: ${error}`);
|
|
198
|
+
this.lockInfo = null;
|
|
199
|
+
if (attempts < maxRetries) {
|
|
200
|
+
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Release lock
|
|
212
|
+
*/
|
|
213
|
+
async release() {
|
|
214
|
+
this.stopHeartbeat();
|
|
215
|
+
if (this.lockInfo) {
|
|
216
|
+
// Verify we still own the lock
|
|
217
|
+
const currentLock = await this.readLock();
|
|
218
|
+
if (currentLock?.pid === process.pid) {
|
|
219
|
+
await this.removeLock();
|
|
220
|
+
logger.debug(`Lock released by ${this.lockInfo.agent} (PID ${process.pid})`);
|
|
221
|
+
}
|
|
222
|
+
this.lockInfo = null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Setup exit handlers to release lock on process termination
|
|
227
|
+
*/
|
|
228
|
+
setupExitHandlers() {
|
|
229
|
+
const cleanup = async () => {
|
|
230
|
+
await this.release();
|
|
231
|
+
};
|
|
232
|
+
// Handle various exit signals
|
|
233
|
+
process.once('SIGINT', cleanup);
|
|
234
|
+
process.once('SIGTERM', cleanup);
|
|
235
|
+
process.once('exit', cleanup);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=lock-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock-manager.js","sourceRoot":"","sources":["../../../src/analytics/remote-submission/lock-manager.ts"],"names":[],"mappings":"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"}
|