@sylphx/flow 2.1.3 → 2.1.5
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/CHANGELOG.md +28 -0
- package/README.md +44 -0
- package/package.json +79 -73
- package/src/commands/flow/execute-v2.ts +37 -29
- package/src/commands/flow/prompt.ts +5 -3
- package/src/commands/flow/types.ts +0 -2
- package/src/commands/flow-command.ts +20 -13
- package/src/commands/hook-command.ts +1 -3
- package/src/commands/settings/checkbox-config.ts +128 -0
- package/src/commands/settings/index.ts +6 -0
- package/src/commands/settings-command.ts +84 -156
- package/src/config/ai-config.ts +60 -41
- package/src/core/agent-loader.ts +11 -6
- package/src/core/attach/file-attacher.ts +172 -0
- package/src/core/attach/index.ts +5 -0
- package/src/core/attach-manager.ts +117 -171
- package/src/core/backup-manager.ts +35 -29
- package/src/core/cleanup-handler.ts +11 -8
- package/src/core/error-handling.ts +23 -30
- package/src/core/flow-executor.ts +58 -76
- package/src/core/formatting/bytes.ts +2 -4
- package/src/core/functional/async.ts +5 -4
- package/src/core/functional/error-handler.ts +2 -2
- package/src/core/git-stash-manager.ts +21 -10
- package/src/core/installers/file-installer.ts +0 -1
- package/src/core/installers/mcp-installer.ts +0 -1
- package/src/core/project-manager.ts +24 -18
- package/src/core/secrets-manager.ts +54 -73
- package/src/core/session-manager.ts +20 -22
- package/src/core/state-detector.ts +139 -80
- package/src/core/template-loader.ts +13 -31
- package/src/core/upgrade-manager.ts +122 -69
- package/src/index.ts +8 -5
- package/src/services/auto-upgrade.ts +1 -1
- package/src/services/config-service.ts +41 -29
- package/src/services/global-config.ts +3 -3
- package/src/services/target-installer.ts +11 -26
- package/src/targets/claude-code.ts +35 -81
- package/src/targets/opencode.ts +28 -68
- package/src/targets/shared/index.ts +7 -0
- package/src/targets/shared/mcp-transforms.ts +132 -0
- package/src/targets/shared/target-operations.ts +135 -0
- package/src/types/cli.types.ts +2 -2
- package/src/types/provider.types.ts +1 -7
- package/src/types/session.types.ts +11 -11
- package/src/types/target.types.ts +3 -1
- package/src/types/todo.types.ts +1 -1
- package/src/types.ts +1 -1
- package/src/utils/__tests__/package-manager-detector.test.ts +6 -6
- package/src/utils/agent-enhancer.ts +4 -4
- package/src/utils/config/paths.ts +3 -1
- package/src/utils/config/target-utils.ts +2 -2
- package/src/utils/display/banner.ts +2 -2
- package/src/utils/display/notifications.ts +58 -45
- package/src/utils/display/status.ts +29 -12
- package/src/utils/files/file-operations.ts +1 -1
- package/src/utils/files/sync-utils.ts +38 -41
- package/src/utils/index.ts +19 -27
- package/src/utils/package-manager-detector.ts +15 -5
- package/src/utils/security/security.ts +8 -4
- package/src/utils/target-selection.ts +6 -8
- package/src/utils/version.ts +4 -2
- package/src/commands/flow-orchestrator.ts +0 -328
- package/src/commands/init-command.ts +0 -92
- package/src/commands/init-core.ts +0 -331
- package/src/core/agent-manager.ts +0 -174
- package/src/core/loop-controller.ts +0 -200
- package/src/core/rule-loader.ts +0 -147
- package/src/core/rule-manager.ts +0 -240
- package/src/services/claude-config-service.ts +0 -252
- package/src/services/first-run-setup.ts +0 -220
- package/src/services/smart-config-service.ts +0 -269
- package/src/types/api.types.ts +0 -9
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { exec } from 'node:child_process';
|
|
8
|
-
import { promisify } from 'node:util';
|
|
9
|
-
import fs from 'node:fs/promises';
|
|
10
|
-
import path from 'node:path';
|
|
11
8
|
import { existsSync } from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { promisify } from 'node:util';
|
|
12
11
|
import chalk from 'chalk';
|
|
13
12
|
|
|
14
13
|
const execAsync = promisify(exec);
|
|
@@ -39,7 +38,10 @@ export class GitStashManager {
|
|
|
39
38
|
if (existsSync(claudeDir)) {
|
|
40
39
|
try {
|
|
41
40
|
const { stdout } = await execAsync('git ls-files .claude', { cwd: projectPath });
|
|
42
|
-
const claudeFiles = stdout
|
|
41
|
+
const claudeFiles = stdout
|
|
42
|
+
.trim()
|
|
43
|
+
.split('\n')
|
|
44
|
+
.filter((f) => f);
|
|
43
45
|
files.push(...claudeFiles);
|
|
44
46
|
} catch {
|
|
45
47
|
// Directory not tracked in git
|
|
@@ -51,7 +53,10 @@ export class GitStashManager {
|
|
|
51
53
|
if (existsSync(opencodeDir)) {
|
|
52
54
|
try {
|
|
53
55
|
const { stdout } = await execAsync('git ls-files .opencode', { cwd: projectPath });
|
|
54
|
-
const opencodeFiles = stdout
|
|
56
|
+
const opencodeFiles = stdout
|
|
57
|
+
.trim()
|
|
58
|
+
.split('\n')
|
|
59
|
+
.filter((f) => f);
|
|
55
60
|
files.push(...opencodeFiles);
|
|
56
61
|
} catch {
|
|
57
62
|
// Directory not tracked in git
|
|
@@ -90,9 +95,11 @@ export class GitStashManager {
|
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
if (this.skipWorktreeFiles.length > 0) {
|
|
93
|
-
console.log(
|
|
98
|
+
console.log(
|
|
99
|
+
chalk.dim(` ✓ Hiding ${this.skipWorktreeFiles.length} settings file(s) from git\n`)
|
|
100
|
+
);
|
|
94
101
|
}
|
|
95
|
-
} catch (
|
|
102
|
+
} catch (_error) {
|
|
96
103
|
console.log(chalk.yellow(' ⚠️ Could not hide settings from git\n'));
|
|
97
104
|
}
|
|
98
105
|
}
|
|
@@ -116,11 +123,15 @@ export class GitStashManager {
|
|
|
116
123
|
}
|
|
117
124
|
}
|
|
118
125
|
|
|
119
|
-
console.log(
|
|
126
|
+
console.log(
|
|
127
|
+
chalk.dim(` ✓ Restored git tracking for ${this.skipWorktreeFiles.length} file(s)\n`)
|
|
128
|
+
);
|
|
120
129
|
this.skipWorktreeFiles = [];
|
|
121
|
-
} catch
|
|
130
|
+
} catch {
|
|
122
131
|
console.log(chalk.yellow(' ⚠️ Could not restore git tracking'));
|
|
123
|
-
console.log(
|
|
132
|
+
console.log(
|
|
133
|
+
chalk.yellow(' Run manually: git update-index --no-skip-worktree .claude/* .opencode/*\n')
|
|
134
|
+
);
|
|
124
135
|
}
|
|
125
136
|
}
|
|
126
137
|
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import crypto from 'node:crypto';
|
|
8
|
-
import path from 'node:path';
|
|
9
|
-
import os from 'node:os';
|
|
10
8
|
import { existsSync } from 'node:fs';
|
|
11
9
|
import fs from 'node:fs/promises';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import type { Target } from '../types/target.types.js';
|
|
13
|
+
import { targetManager } from './target-manager.js';
|
|
12
14
|
|
|
13
15
|
export interface ProjectPaths {
|
|
14
16
|
sessionFile: string;
|
|
@@ -31,11 +33,7 @@ export class ProjectManager {
|
|
|
31
33
|
*/
|
|
32
34
|
getProjectHash(projectPath: string): string {
|
|
33
35
|
const absolutePath = path.resolve(projectPath);
|
|
34
|
-
return crypto
|
|
35
|
-
.createHash('sha256')
|
|
36
|
-
.update(absolutePath)
|
|
37
|
-
.digest('hex')
|
|
38
|
-
.substring(0, 16);
|
|
36
|
+
return crypto.createHash('sha256').update(absolutePath).digest('hex').substring(0, 16);
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
/**
|
|
@@ -128,12 +126,9 @@ export class ProjectManager {
|
|
|
128
126
|
/**
|
|
129
127
|
* Save project-specific target preference to global config
|
|
130
128
|
*/
|
|
131
|
-
async saveProjectTargetPreference(
|
|
132
|
-
projectHash: string,
|
|
133
|
-
target: 'claude-code' | 'opencode'
|
|
134
|
-
): Promise<void> {
|
|
129
|
+
async saveProjectTargetPreference(projectHash: string, target: string): Promise<void> {
|
|
135
130
|
const prefsPath = path.join(this.flowHomeDir, 'project-preferences.json');
|
|
136
|
-
let prefs:
|
|
131
|
+
let prefs: { projects: Record<string, { target?: string }> } = { projects: {} };
|
|
137
132
|
|
|
138
133
|
if (existsSync(prefsPath)) {
|
|
139
134
|
try {
|
|
@@ -156,11 +151,11 @@ export class ProjectManager {
|
|
|
156
151
|
}
|
|
157
152
|
|
|
158
153
|
/**
|
|
159
|
-
* Detect target platform
|
|
154
|
+
* Detect target platform
|
|
160
155
|
* New strategy: Detect based on installed commands, not folders
|
|
161
156
|
* Priority: saved preference > installed commands > global default
|
|
162
157
|
*/
|
|
163
|
-
async detectTarget(projectPath: string): Promise<
|
|
158
|
+
async detectTarget(projectPath: string): Promise<string> {
|
|
164
159
|
const projectHash = this.getProjectHash(projectPath);
|
|
165
160
|
|
|
166
161
|
// 1. Check if we already have a saved preference for this project
|
|
@@ -217,11 +212,22 @@ export class ProjectManager {
|
|
|
217
212
|
|
|
218
213
|
/**
|
|
219
214
|
* Get target config directory for project
|
|
215
|
+
* @param projectPath - The project root path
|
|
216
|
+
* @param target - Either a Target object or target ID string
|
|
220
217
|
*/
|
|
221
|
-
getTargetConfigDir(projectPath: string, target:
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
218
|
+
getTargetConfigDir(projectPath: string, target: Target | string): string {
|
|
219
|
+
// If target is a string, look up the Target object
|
|
220
|
+
const targetObj =
|
|
221
|
+
typeof target === 'string'
|
|
222
|
+
? targetManager.getTarget(target)
|
|
223
|
+
: { _tag: 'Some' as const, value: target };
|
|
224
|
+
|
|
225
|
+
if (targetObj._tag === 'None') {
|
|
226
|
+
// Fallback for unknown targets - use the ID as directory name
|
|
227
|
+
return path.join(projectPath, `.${target}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return path.join(projectPath, targetObj.value.config.configDir);
|
|
225
231
|
}
|
|
226
232
|
|
|
227
233
|
/**
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* Stores secrets in ~/.sylphx-flow/secrets/{project-hash}/
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
7
8
|
import fs from 'node:fs/promises';
|
|
8
9
|
import path from 'node:path';
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import { ProjectManager } from './project-manager.js';
|
|
10
|
+
import type { Target } from '../types/target.types.js';
|
|
11
|
+
import type { ProjectManager } from './project-manager.js';
|
|
12
|
+
import { targetManager } from './target-manager.js';
|
|
13
13
|
|
|
14
14
|
export interface MCPSecrets {
|
|
15
15
|
version: string;
|
|
@@ -30,19 +30,29 @@ export class SecretsManager {
|
|
|
30
30
|
this.projectManager = projectManager;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Resolve target from ID string to Target object
|
|
35
|
+
*/
|
|
36
|
+
private resolveTarget(targetId: string): Target {
|
|
37
|
+
const targetOption = targetManager.getTarget(targetId);
|
|
38
|
+
if (targetOption._tag === 'None') {
|
|
39
|
+
throw new Error(`Unknown target: ${targetId}`);
|
|
40
|
+
}
|
|
41
|
+
return targetOption.value;
|
|
42
|
+
}
|
|
43
|
+
|
|
33
44
|
/**
|
|
34
45
|
* Extract MCP secrets from project config
|
|
35
46
|
*/
|
|
36
47
|
async extractMCPSecrets(
|
|
37
48
|
projectPath: string,
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
_projectHash: string,
|
|
50
|
+
targetOrId: Target | string
|
|
40
51
|
): Promise<MCPSecrets> {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
: path.join(targetDir, '.mcp.json');
|
|
52
|
+
const target = typeof targetOrId === 'string' ? this.resolveTarget(targetOrId) : targetOrId;
|
|
53
|
+
// configFile is at project root, not in targetDir
|
|
54
|
+
const configPath = path.join(projectPath, target.config.configFile);
|
|
55
|
+
const mcpPath = target.config.mcpConfigPath;
|
|
46
56
|
|
|
47
57
|
const secrets: MCPSecrets = {
|
|
48
58
|
version: '1.0.0',
|
|
@@ -57,28 +67,32 @@ export class SecretsManager {
|
|
|
57
67
|
try {
|
|
58
68
|
const config = JSON.parse(await fs.readFile(configPath, 'utf-8'));
|
|
59
69
|
|
|
60
|
-
// Extract MCP server secrets
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
// Extract MCP server secrets using target's mcpConfigPath
|
|
71
|
+
const mcpServers = config[mcpPath] as Record<string, unknown> | undefined;
|
|
72
|
+
if (mcpServers) {
|
|
73
|
+
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
63
74
|
const server = serverConfig as any;
|
|
64
75
|
|
|
65
|
-
// Extract env vars (sensitive)
|
|
66
|
-
|
|
76
|
+
// Extract env vars (sensitive) - handle both 'env' and 'environment' keys
|
|
77
|
+
const envVars = server.env || server.environment;
|
|
78
|
+
if (envVars && Object.keys(envVars).length > 0) {
|
|
67
79
|
secrets.servers[serverName] = {
|
|
68
|
-
env:
|
|
80
|
+
env: envVars,
|
|
69
81
|
};
|
|
70
82
|
}
|
|
71
83
|
|
|
72
|
-
// Extract args (may contain secrets)
|
|
73
|
-
|
|
84
|
+
// Extract args (may contain secrets) - handle both 'args' and 'command' array
|
|
85
|
+
const args =
|
|
86
|
+
server.args || (Array.isArray(server.command) ? server.command.slice(1) : undefined);
|
|
87
|
+
if (args && Array.isArray(args) && args.length > 0) {
|
|
74
88
|
if (!secrets.servers[serverName]) {
|
|
75
89
|
secrets.servers[serverName] = {};
|
|
76
90
|
}
|
|
77
|
-
secrets.servers[serverName].args =
|
|
91
|
+
secrets.servers[serverName].args = args;
|
|
78
92
|
}
|
|
79
93
|
}
|
|
80
94
|
}
|
|
81
|
-
} catch (
|
|
95
|
+
} catch (_error) {
|
|
82
96
|
// Config file exists but cannot be parsed, skip
|
|
83
97
|
}
|
|
84
98
|
|
|
@@ -123,19 +137,18 @@ export class SecretsManager {
|
|
|
123
137
|
*/
|
|
124
138
|
async restoreSecrets(
|
|
125
139
|
projectPath: string,
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
_projectHash: string,
|
|
141
|
+
targetOrId: Target | string,
|
|
128
142
|
secrets: MCPSecrets
|
|
129
143
|
): Promise<void> {
|
|
130
144
|
if (Object.keys(secrets.servers).length === 0) {
|
|
131
145
|
return;
|
|
132
146
|
}
|
|
133
147
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
: path.join(targetDir, '.mcp.json');
|
|
148
|
+
const target = typeof targetOrId === 'string' ? this.resolveTarget(targetOrId) : targetOrId;
|
|
149
|
+
// configFile is at project root, not in targetDir
|
|
150
|
+
const configPath = path.join(projectPath, target.config.configFile);
|
|
151
|
+
const mcpPath = target.config.mcpConfigPath;
|
|
139
152
|
|
|
140
153
|
if (!existsSync(configPath)) {
|
|
141
154
|
return;
|
|
@@ -144,18 +157,24 @@ export class SecretsManager {
|
|
|
144
157
|
try {
|
|
145
158
|
const config = JSON.parse(await fs.readFile(configPath, 'utf-8'));
|
|
146
159
|
|
|
147
|
-
// Restore secrets to MCP servers
|
|
148
|
-
|
|
160
|
+
// Restore secrets to MCP servers using target's mcpConfigPath
|
|
161
|
+
const mcpServers = config[mcpPath] as Record<string, unknown> | undefined;
|
|
162
|
+
if (mcpServers) {
|
|
149
163
|
for (const [serverName, serverSecrets] of Object.entries(secrets.servers)) {
|
|
150
|
-
|
|
151
|
-
|
|
164
|
+
const serverConfig = mcpServers[serverName] as Record<string, unknown> | undefined;
|
|
165
|
+
if (serverConfig) {
|
|
166
|
+
// Restore env vars - use the key that exists in config
|
|
152
167
|
if (serverSecrets.env) {
|
|
153
|
-
|
|
168
|
+
if ('environment' in serverConfig) {
|
|
169
|
+
serverConfig.environment = serverSecrets.env;
|
|
170
|
+
} else {
|
|
171
|
+
serverConfig.env = serverSecrets.env;
|
|
172
|
+
}
|
|
154
173
|
}
|
|
155
174
|
|
|
156
175
|
// Restore args
|
|
157
176
|
if (serverSecrets.args) {
|
|
158
|
-
|
|
177
|
+
serverConfig.args = serverSecrets.args;
|
|
159
178
|
}
|
|
160
179
|
}
|
|
161
180
|
}
|
|
@@ -163,49 +182,11 @@ export class SecretsManager {
|
|
|
163
182
|
|
|
164
183
|
// Write updated config
|
|
165
184
|
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
166
|
-
} catch (
|
|
185
|
+
} catch (_error) {
|
|
167
186
|
// Config restore failed, skip
|
|
168
187
|
}
|
|
169
188
|
}
|
|
170
189
|
|
|
171
|
-
/**
|
|
172
|
-
* Encrypt secrets (optional - for enhanced security)
|
|
173
|
-
*/
|
|
174
|
-
private async encrypt(data: string): Promise<string> {
|
|
175
|
-
// Use machine ID + user HOME as stable key source
|
|
176
|
-
const keySource = `${os.homedir()}-${os.hostname()}`;
|
|
177
|
-
const key = crypto.scryptSync(keySource, 'sylphx-flow-salt', 32);
|
|
178
|
-
const iv = crypto.randomBytes(16);
|
|
179
|
-
|
|
180
|
-
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
181
|
-
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
182
|
-
encrypted += cipher.final('hex');
|
|
183
|
-
|
|
184
|
-
const authTag = cipher.getAuthTag();
|
|
185
|
-
|
|
186
|
-
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Decrypt secrets (optional)
|
|
191
|
-
*/
|
|
192
|
-
private async decrypt(encrypted: string): Promise<string> {
|
|
193
|
-
const [ivHex, authTagHex, encryptedData] = encrypted.split(':');
|
|
194
|
-
|
|
195
|
-
const keySource = `${os.homedir()}-${os.hostname()}`;
|
|
196
|
-
const key = crypto.scryptSync(keySource, 'sylphx-flow-salt', 32);
|
|
197
|
-
const iv = Buffer.from(ivHex, 'hex');
|
|
198
|
-
const authTag = Buffer.from(authTagHex, 'hex');
|
|
199
|
-
|
|
200
|
-
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
|
|
201
|
-
decipher.setAuthTag(authTag);
|
|
202
|
-
|
|
203
|
-
let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
|
|
204
|
-
decrypted += decipher.final('utf8');
|
|
205
|
-
|
|
206
|
-
return decrypted;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
190
|
/**
|
|
210
191
|
* Clear secrets for a project
|
|
211
192
|
*/
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
* All sessions stored in ~/.sylphx-flow/sessions/
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
7
8
|
import fs from 'node:fs/promises';
|
|
8
9
|
import path from 'node:path';
|
|
9
|
-
import {
|
|
10
|
-
import { ProjectManager } from './project-manager.js';
|
|
10
|
+
import type { Target } from '../types/target.types.js';
|
|
11
|
+
import type { ProjectManager } from './project-manager.js';
|
|
11
12
|
|
|
12
13
|
export interface Session {
|
|
13
14
|
projectHash: string;
|
|
@@ -17,13 +18,13 @@ export interface Session {
|
|
|
17
18
|
startTime: string;
|
|
18
19
|
backupPath: string;
|
|
19
20
|
status: 'active' | 'completed' | 'crashed';
|
|
20
|
-
target:
|
|
21
|
+
target: string;
|
|
21
22
|
cleanupRequired: boolean;
|
|
22
23
|
// Multi-session support
|
|
23
|
-
isOriginal: boolean;
|
|
24
|
-
sharedBackupId: string;
|
|
25
|
-
refCount: number;
|
|
26
|
-
activePids: number[];
|
|
24
|
+
isOriginal: boolean; // First session that created backup
|
|
25
|
+
sharedBackupId: string; // Shared backup ID for all sessions
|
|
26
|
+
refCount: number; // Number of active sessions
|
|
27
|
+
activePids: number[]; // All active PIDs sharing this session
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export class SessionManager {
|
|
@@ -39,10 +40,12 @@ export class SessionManager {
|
|
|
39
40
|
async startSession(
|
|
40
41
|
projectPath: string,
|
|
41
42
|
projectHash: string,
|
|
42
|
-
|
|
43
|
+
targetOrId: Target | string,
|
|
43
44
|
backupPath: string,
|
|
44
45
|
sessionId?: string
|
|
45
46
|
): Promise<{ session: Session; isFirstSession: boolean }> {
|
|
47
|
+
// Get target ID for storage
|
|
48
|
+
const targetId = typeof targetOrId === 'string' ? targetOrId : targetOrId.id;
|
|
46
49
|
const paths = this.projectManager.getProjectPaths(projectHash);
|
|
47
50
|
|
|
48
51
|
// Ensure sessions directory exists
|
|
@@ -74,7 +77,7 @@ export class SessionManager {
|
|
|
74
77
|
startTime: new Date().toISOString(),
|
|
75
78
|
backupPath,
|
|
76
79
|
status: 'active',
|
|
77
|
-
target,
|
|
80
|
+
target: targetId,
|
|
78
81
|
cleanupRequired: true,
|
|
79
82
|
isOriginal: true,
|
|
80
83
|
sharedBackupId: newSessionId,
|
|
@@ -93,7 +96,9 @@ export class SessionManager {
|
|
|
93
96
|
/**
|
|
94
97
|
* Mark session as completed (with reference counting)
|
|
95
98
|
*/
|
|
96
|
-
async endSession(
|
|
99
|
+
async endSession(
|
|
100
|
+
projectHash: string
|
|
101
|
+
): Promise<{ shouldRestore: boolean; session: Session | null }> {
|
|
97
102
|
try {
|
|
98
103
|
const session = await this.getActiveSession(projectHash);
|
|
99
104
|
|
|
@@ -104,7 +109,7 @@ export class SessionManager {
|
|
|
104
109
|
const paths = this.projectManager.getProjectPaths(projectHash);
|
|
105
110
|
|
|
106
111
|
// Remove current PID from active PIDs
|
|
107
|
-
session.activePids = session.activePids.filter(pid => pid !== process.pid);
|
|
112
|
+
session.activePids = session.activePids.filter((pid) => pid !== process.pid);
|
|
108
113
|
session.refCount = session.activePids.length;
|
|
109
114
|
|
|
110
115
|
if (session.refCount === 0) {
|
|
@@ -115,12 +120,7 @@ export class SessionManager {
|
|
|
115
120
|
const flowHome = this.projectManager.getFlowHomeDir();
|
|
116
121
|
|
|
117
122
|
// Archive to history
|
|
118
|
-
const historyPath = path.join(
|
|
119
|
-
flowHome,
|
|
120
|
-
'sessions',
|
|
121
|
-
'history',
|
|
122
|
-
`${session.sessionId}.json`
|
|
123
|
-
);
|
|
123
|
+
const historyPath = path.join(flowHome, 'sessions', 'history', `${session.sessionId}.json`);
|
|
124
124
|
await fs.mkdir(path.dirname(historyPath), { recursive: true });
|
|
125
125
|
await fs.writeFile(historyPath, JSON.stringify(session, null, 2));
|
|
126
126
|
|
|
@@ -134,7 +134,7 @@ export class SessionManager {
|
|
|
134
134
|
|
|
135
135
|
return { shouldRestore: false, session };
|
|
136
136
|
}
|
|
137
|
-
} catch (
|
|
137
|
+
} catch (_error) {
|
|
138
138
|
// Session file might not exist
|
|
139
139
|
return { shouldRestore: false, session: null };
|
|
140
140
|
}
|
|
@@ -203,7 +203,7 @@ export class SessionManager {
|
|
|
203
203
|
// Send signal 0 to check if process exists
|
|
204
204
|
process.kill(pid, 0);
|
|
205
205
|
return true;
|
|
206
|
-
} catch (
|
|
206
|
+
} catch (_error) {
|
|
207
207
|
return false;
|
|
208
208
|
}
|
|
209
209
|
}
|
|
@@ -254,9 +254,7 @@ export class SessionManager {
|
|
|
254
254
|
|
|
255
255
|
// Sort by start time (newest first)
|
|
256
256
|
sessions.sort(
|
|
257
|
-
(a, b) =>
|
|
258
|
-
new Date(b.session.startTime).getTime() -
|
|
259
|
-
new Date(a.session.startTime).getTime()
|
|
257
|
+
(a, b) => new Date(b.session.startTime).getTime() - new Date(a.session.startTime).getTime()
|
|
260
258
|
);
|
|
261
259
|
|
|
262
260
|
// Remove old sessions
|