devchain-cli 0.12.1 → 0.12.2
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/drizzle/0060_huge_proemial_gods.sql +12 -0
- package/dist/drizzle/0061_easy_silver_surfer.sql +2 -0
- package/dist/drizzle/meta/0060_snapshot.json +5170 -0
- package/dist/drizzle/meta/0061_snapshot.json +5163 -0
- package/dist/drizzle/meta/_journal.json +14 -0
- package/dist/server/modules/events/catalog/claude.hooks.session.started.d.ts +2 -2
- package/dist/server/modules/events/catalog/index.d.ts +20 -2
- package/dist/server/modules/events/catalog/index.js +5 -1
- package/dist/server/modules/events/catalog/index.js.map +1 -1
- package/dist/server/modules/events/catalog/session.restored.d.ts +21 -0
- package/dist/server/modules/events/catalog/session.restored.js +14 -0
- package/dist/server/modules/events/catalog/session.restored.js.map +1 -0
- package/dist/server/modules/hooks/dtos/hook-event.dto.d.ts +2 -2
- package/dist/server/modules/providers/adapters/claude.adapter.d.ts +4 -1
- package/dist/server/modules/providers/adapters/claude.adapter.js +6 -0
- package/dist/server/modules/providers/adapters/claude.adapter.js.map +1 -1
- package/dist/server/modules/providers/adapters/codex.adapter.d.ts +4 -1
- package/dist/server/modules/providers/adapters/codex.adapter.js +6 -0
- package/dist/server/modules/providers/adapters/codex.adapter.js.map +1 -1
- package/dist/server/modules/providers/adapters/gemini.adapter.d.ts +4 -1
- package/dist/server/modules/providers/adapters/gemini.adapter.js +6 -0
- package/dist/server/modules/providers/adapters/gemini.adapter.js.map +1 -1
- package/dist/server/modules/providers/adapters/opencode.adapter.d.ts +4 -1
- package/dist/server/modules/providers/adapters/opencode.adapter.js +6 -0
- package/dist/server/modules/providers/adapters/opencode.adapter.js.map +1 -1
- package/dist/server/modules/providers/adapters/provider-adapter.interface.d.ts +8 -0
- package/dist/server/modules/reviews/dtos/review.dto.d.ts +2 -2
- package/dist/server/modules/session-reader/data/pricing.json +20 -0
- package/dist/server/modules/session-reader/services/transcript-persistence.listener.js +3 -3
- package/dist/server/modules/session-reader/services/transcript-persistence.listener.js.map +1 -1
- package/dist/server/modules/sessions/controllers/sessions.controller.d.ts +3 -1
- package/dist/server/modules/sessions/controllers/sessions.controller.js +57 -0
- package/dist/server/modules/sessions/controllers/sessions.controller.js.map +1 -1
- package/dist/server/modules/sessions/dtos/sessions.dto.d.ts +25 -1
- package/dist/server/modules/sessions/dtos/sessions.dto.js +4 -1
- package/dist/server/modules/sessions/dtos/sessions.dto.js.map +1 -1
- package/dist/server/modules/sessions/services/sessions.service.d.ts +35 -1
- package/dist/server/modules/sessions/services/sessions.service.js +471 -118
- package/dist/server/modules/sessions/services/sessions.service.js.map +1 -1
- package/dist/server/modules/sessions/utils/env-builder.d.ts +1 -1
- package/dist/server/modules/sessions/utils/env-builder.js +3 -3
- package/dist/server/modules/sessions/utils/env-builder.js.map +1 -1
- package/dist/server/modules/storage/db/schema.d.ts +38 -2
- package/dist/server/modules/storage/db/schema.js +4 -1
- package/dist/server/modules/storage/db/schema.js.map +1 -1
- package/dist/server/modules/subscribers/events/event-fields-catalog.js +12 -0
- package/dist/server/modules/subscribers/events/event-fields-catalog.js.map +1 -1
- package/dist/server/modules/terminal/gateways/terminal.gateway.d.ts +6 -0
- package/dist/server/modules/terminal/gateways/terminal.gateway.js +17 -0
- package/dist/server/modules/terminal/gateways/terminal.gateway.js.map +1 -1
- package/dist/server/templates/5-agents-dev.json +4 -4
- package/dist/server/templates/teams-dev.json +17 -20
- package/dist/server/tsconfig.tsbuildinfo +1 -1
- package/dist/server/ui/assets/{ReviewDetailPage-CEMxN6RQ.js → ReviewDetailPage-D0vzq3rI.js} +1 -1
- package/dist/server/ui/assets/{ReviewsPage-BzMksnGd.js → ReviewsPage-aX6m8vW_.js} +1 -1
- package/dist/server/ui/assets/index-BzphIngp.css +32 -0
- package/dist/server/ui/assets/{index-Csagg3g2.js → index-Cut7Gl_8.js} +218 -218
- package/dist/server/ui/assets/{useReviewSubscription-B2ejeWE4.js → useReviewSubscription-CrgRqMXe.js} +1 -1
- package/dist/server/ui/index.html +2 -2
- package/dist/templates/5-agents-dev.json +4 -4
- package/dist/templates/teams-dev.json +17 -20
- package/package.json +5 -1
- package/dist/server/ui/assets/index-ChJ1IUMI.css +0 -32
|
@@ -14,6 +14,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.SessionsService = void 0;
|
|
16
16
|
const common_1 = require("@nestjs/common");
|
|
17
|
+
const promises_1 = require("fs/promises");
|
|
17
18
|
const error_types_1 = require("../../../common/errors/error-types");
|
|
18
19
|
const confirmed_delivery_helper_1 = require("../../terminal/services/confirmed-delivery.helper");
|
|
19
20
|
const core_1 = require("@nestjs/core");
|
|
@@ -67,6 +68,127 @@ let SessionsService = class SessionsService {
|
|
|
67
68
|
this.sqlite = this.db.session?.client ?? this.db;
|
|
68
69
|
logger.info('SessionsService initialized');
|
|
69
70
|
}
|
|
71
|
+
async resolveLaunchTarget(params) {
|
|
72
|
+
const { agentId, projectId, epicId } = params;
|
|
73
|
+
const agent = await this.storage.getAgent(agentId);
|
|
74
|
+
const project = await this.storage.getProject(projectId);
|
|
75
|
+
if (agent.projectId !== projectId) {
|
|
76
|
+
throw new error_types_1.ValidationError(`Agent ${agentId} does not belong to project ${projectId}.`, {
|
|
77
|
+
agentId,
|
|
78
|
+
agentProjectId: agent.projectId,
|
|
79
|
+
requestedProjectId: projectId,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const epic = epicId ? await this.storage.getEpic(epicId) : null;
|
|
83
|
+
const profile = await this.storage.getAgentProfile(agent.profileId);
|
|
84
|
+
let provider;
|
|
85
|
+
let options;
|
|
86
|
+
let configEnv = null;
|
|
87
|
+
if (agent.providerConfigId) {
|
|
88
|
+
const config = await this.storage.getProfileProviderConfig(agent.providerConfigId);
|
|
89
|
+
provider = await this.storage.getProvider(config.providerId);
|
|
90
|
+
options = config.options;
|
|
91
|
+
configEnv = config.env;
|
|
92
|
+
logger.info({ agentId, configId: config.id, providerId: provider.id }, 'Resolved provider via config');
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const configs = await this.storage.listProfileProviderConfigsByProfile(profile.id);
|
|
96
|
+
if (configs.length > 0) {
|
|
97
|
+
const firstConfig = configs[0];
|
|
98
|
+
provider = await this.storage.getProvider(firstConfig.providerId);
|
|
99
|
+
options = firstConfig.options;
|
|
100
|
+
configEnv = firstConfig.env;
|
|
101
|
+
logger.info({ agentId, profileId: profile.id, configId: firstConfig.id, providerId: provider.id }, 'Resolved provider via first profile config (no providerConfigId set on agent)');
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
throw new error_types_1.ValidationError(`Profile ${profile.id} has no provider configs - cannot launch session`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { agent, project, epic, profile, provider, options, configEnv };
|
|
108
|
+
}
|
|
109
|
+
verifyProviderBinary(provider) {
|
|
110
|
+
if (!provider.binPath) {
|
|
111
|
+
throw new error_types_1.ValidationError(`Provider ${provider.name} is missing a binary path. Set the path before launching sessions.`, {
|
|
112
|
+
code: 'PROVIDER_BINARY_NOT_FOUND',
|
|
113
|
+
providerId: provider.id,
|
|
114
|
+
providerName: provider.name,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
composeLaunchEnv(params) {
|
|
119
|
+
const { sessionId, tmuxSessionName, projectId, agentId, provider, configEnv, optionArgs } = params;
|
|
120
|
+
const providerEnv = provider.env ?? {};
|
|
121
|
+
const mergedBaseEnv = { ...providerEnv, ...(configEnv ?? {}) };
|
|
122
|
+
let envVars = Object.keys(mergedBaseEnv).length > 0 ? mergedBaseEnv : null;
|
|
123
|
+
let processedOptionArgs = optionArgs;
|
|
124
|
+
if (provider.name.toLowerCase() === 'claude') {
|
|
125
|
+
const env = (0, env_config_1.getEnvConfig)();
|
|
126
|
+
const devchainEnv = {
|
|
127
|
+
DEVCHAIN_API_URL: `http://127.0.0.1:${env.PORT}`,
|
|
128
|
+
DEVCHAIN_PROJECT_ID: projectId,
|
|
129
|
+
DEVCHAIN_AGENT_ID: agentId,
|
|
130
|
+
DEVCHAIN_SESSION_ID: sessionId,
|
|
131
|
+
DEVCHAIN_TMUX_SESSION_NAME: tmuxSessionName,
|
|
132
|
+
};
|
|
133
|
+
if (provider.oneMillionContextEnabled) {
|
|
134
|
+
processedOptionArgs = (0, profile_options_1.rewriteModelTo1m)(processedOptionArgs);
|
|
135
|
+
}
|
|
136
|
+
const modelStr = (0, profile_options_1.extractModelFromArgs)(processedOptionArgs);
|
|
137
|
+
const family = modelStr ? (0, profile_options_1.detectClaudeModelFamily)(modelStr) : null;
|
|
138
|
+
if (provider.oneMillionContextEnabled &&
|
|
139
|
+
family === 'opus' &&
|
|
140
|
+
provider.autoCompactThreshold1m != null) {
|
|
141
|
+
devchainEnv.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE = String(provider.autoCompactThreshold1m);
|
|
142
|
+
}
|
|
143
|
+
else if (provider.autoCompactThreshold != null) {
|
|
144
|
+
devchainEnv.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE = String(provider.autoCompactThreshold);
|
|
145
|
+
}
|
|
146
|
+
envVars = { ...devchainEnv, ...providerEnv, ...(configEnv ?? {}) };
|
|
147
|
+
delete envVars.CLAUDE_CODE_DISABLE_1M_CONTEXT;
|
|
148
|
+
}
|
|
149
|
+
return { envVars, processedOptionArgs };
|
|
150
|
+
}
|
|
151
|
+
async ensureMcpConfig(provider, projectRootPath) {
|
|
152
|
+
let preflightResult = await this.preflightService.runChecks(projectRootPath);
|
|
153
|
+
let providerCheck = preflightResult.providers?.find((p) => p.id === provider.id);
|
|
154
|
+
if (providerCheck?.mcpStatus && providerCheck.mcpStatus !== 'pass') {
|
|
155
|
+
logger.info({
|
|
156
|
+
providerId: provider.id,
|
|
157
|
+
providerName: provider.name,
|
|
158
|
+
mcpStatus: providerCheck.mcpStatus,
|
|
159
|
+
}, 'MCP not configured, attempting auto-ensure');
|
|
160
|
+
const ensureResult = await this.mcpEnsureService.ensureMcp(provider, projectRootPath);
|
|
161
|
+
if (ensureResult.success) {
|
|
162
|
+
logger.info({ providerId: provider.id, action: ensureResult.action }, 'MCP auto-configured successfully');
|
|
163
|
+
preflightResult = await this.preflightService.runChecks(projectRootPath);
|
|
164
|
+
providerCheck = preflightResult.providers?.find((p) => p.id === provider.id);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
logger.warn({ providerId: provider.id, message: ensureResult.message }, 'MCP auto-ensure failed');
|
|
168
|
+
}
|
|
169
|
+
if (providerCheck?.mcpStatus && providerCheck.mcpStatus !== 'pass') {
|
|
170
|
+
throw new error_types_1.ValidationError('Provider MCP is not configured', {
|
|
171
|
+
code: 'MCP_NOT_CONFIGURED',
|
|
172
|
+
providerId: provider.id,
|
|
173
|
+
providerName: provider.name,
|
|
174
|
+
mcpStatus: providerCheck.mcpStatus,
|
|
175
|
+
mcpMessage: providerCheck.mcpMessage,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return preflightResult;
|
|
180
|
+
}
|
|
181
|
+
async setupHooksConfig(provider, projectRootPath) {
|
|
182
|
+
if (provider.name.toLowerCase() !== 'claude')
|
|
183
|
+
return;
|
|
184
|
+
try {
|
|
185
|
+
await this.hooksConfigService.ensureHooksConfig(projectRootPath);
|
|
186
|
+
logger.info({ projectRootPath }, 'Hooks config ensured for Claude provider');
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
logger.warn({ error }, 'Failed to ensure hooks config (non-fatal)');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
70
192
|
async launchSession(data) {
|
|
71
193
|
const { epicId, agentId, projectId, options: launchOptions } = data;
|
|
72
194
|
const silent = launchOptions?.silent === true;
|
|
@@ -119,40 +241,7 @@ let SessionsService = class SessionsService {
|
|
|
119
241
|
.run(new Date().toISOString(), new Date().toISOString(), existingSession.id);
|
|
120
242
|
logger.info({ agentId }, 'Orphaned session cleaned up, proceeding to create new session');
|
|
121
243
|
}
|
|
122
|
-
const agent = await this.
|
|
123
|
-
const project = await this.storage.getProject(projectId);
|
|
124
|
-
if (agent.projectId !== projectId) {
|
|
125
|
-
throw new error_types_1.ValidationError(`Agent ${agentId} does not belong to project ${projectId}.`, {
|
|
126
|
-
agentId,
|
|
127
|
-
agentProjectId: agent.projectId,
|
|
128
|
-
requestedProjectId: projectId,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
const epic = epicId ? await this.storage.getEpic(epicId) : null;
|
|
132
|
-
const profile = await this.storage.getAgentProfile(agent.profileId);
|
|
133
|
-
let provider;
|
|
134
|
-
let options;
|
|
135
|
-
let configEnv = null;
|
|
136
|
-
if (agent.providerConfigId) {
|
|
137
|
-
const config = await this.storage.getProfileProviderConfig(agent.providerConfigId);
|
|
138
|
-
provider = await this.storage.getProvider(config.providerId);
|
|
139
|
-
options = config.options;
|
|
140
|
-
configEnv = config.env;
|
|
141
|
-
logger.info({ agentId, configId: config.id, providerId: provider.id }, 'Resolved provider via config');
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
const configs = await this.storage.listProfileProviderConfigsByProfile(profile.id);
|
|
145
|
-
if (configs.length > 0) {
|
|
146
|
-
const firstConfig = configs[0];
|
|
147
|
-
provider = await this.storage.getProvider(firstConfig.providerId);
|
|
148
|
-
options = firstConfig.options;
|
|
149
|
-
configEnv = firstConfig.env;
|
|
150
|
-
logger.info({ agentId, profileId: profile.id, configId: firstConfig.id, providerId: provider.id }, 'Resolved provider via first profile config (no providerConfigId set on agent)');
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
throw new error_types_1.ValidationError(`Profile ${profile.id} has no provider configs - cannot launch session`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
244
|
+
const { agent, project, epic, profile, provider, options, configEnv } = await this.resolveLaunchTarget({ agentId, projectId, epicId });
|
|
156
245
|
if (provider.name.toLowerCase() === 'claude') {
|
|
157
246
|
const { autoCompactEnabled, configState } = await (0, claude_config_1.checkClaudeAutoCompact)();
|
|
158
247
|
if (!autoCompactEnabled && configState !== 'malformed') {
|
|
@@ -167,42 +256,8 @@ let SessionsService = class SessionsService {
|
|
|
167
256
|
});
|
|
168
257
|
}
|
|
169
258
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (providerCheck?.mcpStatus && providerCheck.mcpStatus !== 'pass') {
|
|
173
|
-
logger.info({
|
|
174
|
-
providerId: provider.id,
|
|
175
|
-
providerName: provider.name,
|
|
176
|
-
mcpStatus: providerCheck.mcpStatus,
|
|
177
|
-
}, 'MCP not configured, attempting auto-ensure');
|
|
178
|
-
const ensureResult = await this.mcpEnsureService.ensureMcp(provider, project.rootPath);
|
|
179
|
-
if (ensureResult.success) {
|
|
180
|
-
logger.info({ providerId: provider.id, action: ensureResult.action }, 'MCP auto-configured successfully');
|
|
181
|
-
preflightResult = await this.preflightService.runChecks(project.rootPath);
|
|
182
|
-
providerCheck = preflightResult.providers?.find((p) => p.id === provider.id);
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
logger.warn({ providerId: provider.id, message: ensureResult.message }, 'MCP auto-ensure failed');
|
|
186
|
-
}
|
|
187
|
-
if (providerCheck?.mcpStatus && providerCheck.mcpStatus !== 'pass') {
|
|
188
|
-
throw new error_types_1.ValidationError('Provider MCP is not configured', {
|
|
189
|
-
code: 'MCP_NOT_CONFIGURED',
|
|
190
|
-
providerId: provider.id,
|
|
191
|
-
providerName: provider.name,
|
|
192
|
-
mcpStatus: providerCheck.mcpStatus,
|
|
193
|
-
mcpMessage: providerCheck.mcpMessage,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
if (provider.name.toLowerCase() === 'claude') {
|
|
198
|
-
try {
|
|
199
|
-
await this.hooksConfigService.ensureHooksConfig(project.rootPath);
|
|
200
|
-
logger.info({ projectId, projectRootPath: project.rootPath }, 'Hooks config ensured for Claude provider');
|
|
201
|
-
}
|
|
202
|
-
catch (error) {
|
|
203
|
-
logger.warn({ error, projectId }, 'Failed to ensure hooks config (non-fatal)');
|
|
204
|
-
}
|
|
205
|
-
}
|
|
259
|
+
const preflightResult = await this.ensureMcpConfig(provider, project.rootPath);
|
|
260
|
+
await this.setupHooksConfig(provider, project.rootPath);
|
|
206
261
|
if (preflightResult.overall === 'fail') {
|
|
207
262
|
const failedChecks = preflightResult.checks
|
|
208
263
|
.filter((c) => c.status === 'fail')
|
|
@@ -224,12 +279,7 @@ let SessionsService = class SessionsService {
|
|
|
224
279
|
await this.tmuxService.createSession(tmuxSessionName, project.rootPath);
|
|
225
280
|
await this.tmuxService.setAlternateScreenOff(tmuxSessionName);
|
|
226
281
|
this.tmuxService.startHealthCheck(tmuxSessionName, sessionId);
|
|
227
|
-
|
|
228
|
-
throw new error_types_1.ValidationError(`Provider ${provider.name} is missing a binary path. Set the path before launching sessions.`, {
|
|
229
|
-
providerId: provider.id,
|
|
230
|
-
providerName: provider.name,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
282
|
+
this.verifyProviderBinary(provider);
|
|
233
283
|
let optionArgs = [];
|
|
234
284
|
try {
|
|
235
285
|
optionArgs = (0, profile_options_1.parseProfileOptions)(options);
|
|
@@ -246,37 +296,29 @@ let SessionsService = class SessionsService {
|
|
|
246
296
|
if (agent.modelOverride) {
|
|
247
297
|
optionArgs = (0, profile_options_1.injectModelOverride)(optionArgs, agent.modelOverride);
|
|
248
298
|
}
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
provider.autoCompactThreshold1m != null) {
|
|
269
|
-
devchainEnv.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE = String(provider.autoCompactThreshold1m);
|
|
270
|
-
}
|
|
271
|
-
else if (provider.autoCompactThreshold != null) {
|
|
272
|
-
devchainEnv.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE = String(provider.autoCompactThreshold);
|
|
273
|
-
}
|
|
274
|
-
envVars = { ...devchainEnv, ...providerEnv, ...(configEnv ?? {}) };
|
|
275
|
-
delete envVars.CLAUDE_CODE_DISABLE_1M_CONTEXT;
|
|
299
|
+
const { envVars, processedOptionArgs } = this.composeLaunchEnv({
|
|
300
|
+
sessionId,
|
|
301
|
+
tmuxSessionName,
|
|
302
|
+
projectId,
|
|
303
|
+
agentId,
|
|
304
|
+
provider,
|
|
305
|
+
configEnv,
|
|
306
|
+
optionArgs,
|
|
307
|
+
});
|
|
308
|
+
optionArgs = processedOptionArgs;
|
|
309
|
+
let launchArgv = optionArgs;
|
|
310
|
+
try {
|
|
311
|
+
const launchAdapter = this.providerAdapterFactory.getAdapter(provider.name);
|
|
312
|
+
launchArgv = launchAdapter.buildLaunchArgs({
|
|
313
|
+
mode: 'new',
|
|
314
|
+
profileOptionArgs: optionArgs,
|
|
315
|
+
}).argv;
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
276
318
|
}
|
|
277
319
|
let commandArgs;
|
|
278
320
|
try {
|
|
279
|
-
commandArgs = (0, env_builder_1.buildSessionCommand)(envVars, provider.binPath,
|
|
321
|
+
commandArgs = (0, env_builder_1.buildSessionCommand)(envVars, provider.binPath, launchArgv);
|
|
280
322
|
}
|
|
281
323
|
catch (error) {
|
|
282
324
|
if (error instanceof env_builder_1.EnvBuilderError) {
|
|
@@ -290,10 +332,10 @@ let SessionsService = class SessionsService {
|
|
|
290
332
|
try {
|
|
291
333
|
this.sqlite
|
|
292
334
|
.prepare(`
|
|
293
|
-
INSERT INTO sessions (id, epic_id, agent_id, tmux_session_id, status, started_at, created_at, updated_at)
|
|
294
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
335
|
+
INSERT INTO sessions (id, epic_id, agent_id, tmux_session_id, status, started_at, provider_name_at_launch, created_at, updated_at)
|
|
336
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
295
337
|
`)
|
|
296
|
-
.run(sessionId, epicId ?? null, agentId, tmuxSessionName, 'running', now, now, now);
|
|
338
|
+
.run(sessionId, epicId ?? null, agentId, tmuxSessionName, 'running', now, provider.name.toLowerCase(), now, now);
|
|
297
339
|
logger.info({ sessionId, tmuxSessionName }, 'Session created in database');
|
|
298
340
|
}
|
|
299
341
|
catch (error) {
|
|
@@ -427,7 +469,6 @@ let SessionsService = class SessionsService {
|
|
|
427
469
|
startedAt: now,
|
|
428
470
|
endedAt: null,
|
|
429
471
|
transcriptPath: null,
|
|
430
|
-
claudeSessionId: null,
|
|
431
472
|
createdAt: now,
|
|
432
473
|
updatedAt: now,
|
|
433
474
|
epic: epic
|
|
@@ -450,6 +491,236 @@ let SessionsService = class SessionsService {
|
|
|
450
491
|
};
|
|
451
492
|
});
|
|
452
493
|
}
|
|
494
|
+
async restoreSession(sessionId, projectId) {
|
|
495
|
+
const source = this.sqlite
|
|
496
|
+
.prepare(`SELECT id, epic_id, agent_id, tmux_session_id, status, started_at, ended_at,
|
|
497
|
+
transcript_path, provider_session_id, provider_name_at_launch, created_at, updated_at
|
|
498
|
+
FROM sessions WHERE id = ?`)
|
|
499
|
+
.get(sessionId);
|
|
500
|
+
if (!source) {
|
|
501
|
+
throw new common_1.NotFoundException('Session not found');
|
|
502
|
+
}
|
|
503
|
+
const sourceAgent = await this.storage.getAgent(source.agent_id);
|
|
504
|
+
if (sourceAgent.projectId !== projectId) {
|
|
505
|
+
throw new common_1.ForbiddenException('PROJECT_MISMATCH');
|
|
506
|
+
}
|
|
507
|
+
if (source.status !== 'stopped' && source.status !== 'failed') {
|
|
508
|
+
throw new common_1.ConflictException({
|
|
509
|
+
message: 'Session is not in a restorable state',
|
|
510
|
+
code: 'INVALID_SESSION_STATE',
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
if (!source.provider_session_id) {
|
|
514
|
+
throw new common_1.ConflictException({
|
|
515
|
+
message: 'Session has no provider session ID',
|
|
516
|
+
code: 'NO_PROVIDER_SESSION_ID',
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
const { provider: currentProvider } = await this.resolveLaunchTarget({
|
|
520
|
+
agentId: source.agent_id,
|
|
521
|
+
projectId,
|
|
522
|
+
epicId: source.epic_id,
|
|
523
|
+
});
|
|
524
|
+
if (source.provider_name_at_launch &&
|
|
525
|
+
currentProvider.name.toLowerCase() !== source.provider_name_at_launch.toLowerCase()) {
|
|
526
|
+
throw new common_1.ConflictException({
|
|
527
|
+
message: 'Current provider differs from launch-time provider',
|
|
528
|
+
code: 'PROVIDER_MISMATCH',
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
return this.sessionCoordinator.withAgentLock(source.agent_id, async () => {
|
|
532
|
+
const lockedSource = this.sqlite
|
|
533
|
+
.prepare(`SELECT id, epic_id, agent_id, tmux_session_id, status, started_at, ended_at,
|
|
534
|
+
transcript_path, provider_session_id, provider_name_at_launch, created_at, updated_at
|
|
535
|
+
FROM sessions WHERE id = ?`)
|
|
536
|
+
.get(sessionId);
|
|
537
|
+
if (!lockedSource) {
|
|
538
|
+
throw new common_1.NotFoundException('Session not found');
|
|
539
|
+
}
|
|
540
|
+
if (lockedSource.status !== 'stopped' && lockedSource.status !== 'failed') {
|
|
541
|
+
throw new common_1.ConflictException({
|
|
542
|
+
message: 'Session is not in a restorable state',
|
|
543
|
+
code: 'INVALID_SESSION_STATE',
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
if (!lockedSource.provider_session_id) {
|
|
547
|
+
throw new common_1.ConflictException({
|
|
548
|
+
message: 'Session has no provider session ID',
|
|
549
|
+
code: 'NO_PROVIDER_SESSION_ID',
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
if (lockedSource.provider_name_at_launch &&
|
|
553
|
+
currentProvider.name.toLowerCase() !== lockedSource.provider_name_at_launch.toLowerCase()) {
|
|
554
|
+
throw new common_1.ConflictException({
|
|
555
|
+
message: 'Current provider differs from launch-time provider',
|
|
556
|
+
code: 'PROVIDER_MISMATCH',
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
const existingRunning = this.getActiveSessionForAgent(lockedSource.agent_id);
|
|
560
|
+
if (existingRunning) {
|
|
561
|
+
throw new common_1.ConflictException({
|
|
562
|
+
message: 'Agent already has a running session',
|
|
563
|
+
code: 'INVALID_SESSION_STATE',
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
const prior = {
|
|
567
|
+
status: lockedSource.status,
|
|
568
|
+
ended_at: lockedSource.ended_at,
|
|
569
|
+
tmux_session_id: lockedSource.tmux_session_id,
|
|
570
|
+
};
|
|
571
|
+
const { agent, project, epic, profile, provider, options, configEnv } = await this.resolveLaunchTarget({
|
|
572
|
+
agentId: lockedSource.agent_id,
|
|
573
|
+
projectId,
|
|
574
|
+
epicId: lockedSource.epic_id,
|
|
575
|
+
});
|
|
576
|
+
this.verifyProviderBinary(provider);
|
|
577
|
+
if (lockedSource.provider_name_at_launch &&
|
|
578
|
+
provider.name.toLowerCase() !== lockedSource.provider_name_at_launch.toLowerCase()) {
|
|
579
|
+
throw new common_1.ConflictException({
|
|
580
|
+
message: 'Current provider differs from launch-time provider',
|
|
581
|
+
code: 'PROVIDER_MISMATCH',
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
let optionArgs = [];
|
|
585
|
+
try {
|
|
586
|
+
optionArgs = (0, profile_options_1.parseProfileOptions)(options);
|
|
587
|
+
}
|
|
588
|
+
catch (error) {
|
|
589
|
+
if (error instanceof profile_options_1.ProfileOptionsError) {
|
|
590
|
+
throw new error_types_1.ValidationError(error.message, {
|
|
591
|
+
profileId: profile.id,
|
|
592
|
+
profileName: profile.name,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
throw error;
|
|
596
|
+
}
|
|
597
|
+
if (agent.modelOverride) {
|
|
598
|
+
optionArgs = (0, profile_options_1.injectModelOverride)(optionArgs, agent.modelOverride);
|
|
599
|
+
}
|
|
600
|
+
const projectSlug = project.name
|
|
601
|
+
.toLowerCase()
|
|
602
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
603
|
+
.replace(/(^-|-$)/g, '');
|
|
604
|
+
const epicSegment = lockedSource.epic_id ?? 'independent';
|
|
605
|
+
const tmuxSessionName = this.tmuxService.createSessionName(projectSlug, epicSegment, agent.id, lockedSource.id);
|
|
606
|
+
const { envVars, processedOptionArgs } = this.composeLaunchEnv({
|
|
607
|
+
sessionId: lockedSource.id,
|
|
608
|
+
tmuxSessionName,
|
|
609
|
+
projectId,
|
|
610
|
+
agentId: agent.id,
|
|
611
|
+
provider,
|
|
612
|
+
configEnv,
|
|
613
|
+
optionArgs,
|
|
614
|
+
});
|
|
615
|
+
optionArgs = processedOptionArgs;
|
|
616
|
+
const adapter = this.providerAdapterFactory.getAdapter(provider.name);
|
|
617
|
+
const launchArgv = adapter.buildLaunchArgs({
|
|
618
|
+
mode: 'restore',
|
|
619
|
+
providerSessionId: lockedSource.provider_session_id,
|
|
620
|
+
profileOptionArgs: optionArgs,
|
|
621
|
+
}).argv;
|
|
622
|
+
if (!launchArgv.includes(lockedSource.provider_session_id)) {
|
|
623
|
+
throw new error_types_1.ValidationError('Restore argv does not include provider session ID — adapter contract violation', {
|
|
624
|
+
code: 'RESTORE_ARGS_UNAVAILABLE',
|
|
625
|
+
providerName: provider.name,
|
|
626
|
+
providerSessionId: lockedSource.provider_session_id,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
let commandArgs;
|
|
630
|
+
try {
|
|
631
|
+
commandArgs = (0, env_builder_1.buildSessionCommand)(envVars, provider.binPath, launchArgv);
|
|
632
|
+
}
|
|
633
|
+
catch (error) {
|
|
634
|
+
if (error instanceof env_builder_1.EnvBuilderError) {
|
|
635
|
+
throw new error_types_1.ValidationError(error.message, {
|
|
636
|
+
agentId: agent.id,
|
|
637
|
+
providerConfigId: agent.providerConfigId,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
throw error;
|
|
641
|
+
}
|
|
642
|
+
const now = new Date().toISOString();
|
|
643
|
+
this.sqlite
|
|
644
|
+
.prepare(`UPDATE sessions
|
|
645
|
+
SET status = 'running', tmux_session_id = ?, ended_at = NULL,
|
|
646
|
+
last_activity_at = ?, updated_at = ?
|
|
647
|
+
WHERE id = ?`)
|
|
648
|
+
.run(tmuxSessionName, now, now, lockedSource.id);
|
|
649
|
+
try {
|
|
650
|
+
await this.tmuxService.createSession(tmuxSessionName, project.rootPath);
|
|
651
|
+
}
|
|
652
|
+
catch (tmuxError) {
|
|
653
|
+
this.sqlite
|
|
654
|
+
.prepare(`UPDATE sessions
|
|
655
|
+
SET status = ?, ended_at = ?, tmux_session_id = ?, updated_at = ?
|
|
656
|
+
WHERE id = ?`)
|
|
657
|
+
.run(prior.status, prior.ended_at, prior.tmux_session_id, now, lockedSource.id);
|
|
658
|
+
logger.error({ sessionId: lockedSource.id, error: String(tmuxError) }, 'Restore failed: tmux creation error — rolled back');
|
|
659
|
+
throw new common_1.InternalServerErrorException('RESTORE_FAILED');
|
|
660
|
+
}
|
|
661
|
+
await this.tmuxService.setAlternateScreenOff(tmuxSessionName);
|
|
662
|
+
this.tmuxService.startHealthCheck(tmuxSessionName, lockedSource.id);
|
|
663
|
+
try {
|
|
664
|
+
await this.tmuxService.sendCommandArgs(tmuxSessionName, commandArgs);
|
|
665
|
+
}
|
|
666
|
+
catch (sendError) {
|
|
667
|
+
this.sqlite
|
|
668
|
+
.prepare(`UPDATE sessions
|
|
669
|
+
SET status = ?, ended_at = ?, tmux_session_id = ?, updated_at = ?
|
|
670
|
+
WHERE id = ?`)
|
|
671
|
+
.run(prior.status, prior.ended_at, prior.tmux_session_id, now, lockedSource.id);
|
|
672
|
+
try {
|
|
673
|
+
await this.tmuxService.destroySession(tmuxSessionName);
|
|
674
|
+
}
|
|
675
|
+
catch (destroyErr) {
|
|
676
|
+
logger.warn({ tmuxSessionName, error: String(destroyErr) }, 'Failed to destroy tmux session during restore rollback');
|
|
677
|
+
}
|
|
678
|
+
logger.error({ sessionId: lockedSource.id, error: String(sendError) }, 'Restore failed: CLI launch error — rolled back');
|
|
679
|
+
throw new common_1.InternalServerErrorException('RESTORE_FAILED');
|
|
680
|
+
}
|
|
681
|
+
await this.ptyService.startStreaming(lockedSource.id, tmuxSessionName);
|
|
682
|
+
await this.getEventsService().publish('session.restored', {
|
|
683
|
+
sessionId: lockedSource.id,
|
|
684
|
+
epicId: lockedSource.epic_id,
|
|
685
|
+
agentId: agent.id,
|
|
686
|
+
tmuxSessionName,
|
|
687
|
+
});
|
|
688
|
+
if (lockedSource.transcript_path) {
|
|
689
|
+
await this.getEventsService().publish('session.transcript.discovered', {
|
|
690
|
+
sessionId: lockedSource.id,
|
|
691
|
+
agentId: agent.id,
|
|
692
|
+
projectId,
|
|
693
|
+
transcriptPath: lockedSource.transcript_path,
|
|
694
|
+
providerName: provider.name.toLowerCase(),
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
try {
|
|
698
|
+
this.getTerminalGateway().broadcastEvent(`agent/${agent.id}`, 'presence', {
|
|
699
|
+
online: true,
|
|
700
|
+
sessionId: lockedSource.id,
|
|
701
|
+
agentId: agent.id,
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
catch (error) {
|
|
705
|
+
logger.warn({ error, agentId: agent.id }, 'Failed to broadcast presence after restore');
|
|
706
|
+
}
|
|
707
|
+
return {
|
|
708
|
+
id: lockedSource.id,
|
|
709
|
+
epicId: lockedSource.epic_id,
|
|
710
|
+
agentId: agent.id,
|
|
711
|
+
tmuxSessionId: tmuxSessionName,
|
|
712
|
+
status: 'running',
|
|
713
|
+
startedAt: lockedSource.started_at,
|
|
714
|
+
endedAt: null,
|
|
715
|
+
transcriptPath: lockedSource.transcript_path,
|
|
716
|
+
createdAt: lockedSource.created_at,
|
|
717
|
+
updatedAt: now,
|
|
718
|
+
epic: epic ? { id: epic.id, title: epic.title, projectId: epic.projectId } : null,
|
|
719
|
+
agent: { id: agent.id, name: agent.name, profileId: agent.profileId },
|
|
720
|
+
project: { id: project.id, name: project.name, rootPath: project.rootPath },
|
|
721
|
+
};
|
|
722
|
+
});
|
|
723
|
+
}
|
|
453
724
|
async terminateSession(sessionId) {
|
|
454
725
|
logger.info({ sessionId }, 'Terminating session');
|
|
455
726
|
const session = this.getSession(sessionId);
|
|
@@ -471,14 +742,24 @@ let SessionsService = class SessionsService {
|
|
|
471
742
|
logger.warn({ sessionId, tmuxSessionId: session.tmuxSessionId }, 'Tmux session already gone, cleaning up database record');
|
|
472
743
|
}
|
|
473
744
|
}
|
|
745
|
+
let sizeBytes = null;
|
|
746
|
+
if (session.transcriptPath) {
|
|
747
|
+
try {
|
|
748
|
+
const fileStat = await (0, promises_1.stat)(session.transcriptPath);
|
|
749
|
+
sizeBytes = fileStat.size;
|
|
750
|
+
}
|
|
751
|
+
catch (error) {
|
|
752
|
+
logger.warn({ error, sessionId, transcriptPath: session.transcriptPath }, 'Could not stat transcript file for size_bytes — leaving NULL (best-effort)');
|
|
753
|
+
}
|
|
754
|
+
}
|
|
474
755
|
const now = new Date().toISOString();
|
|
475
756
|
this.sqlite
|
|
476
757
|
.prepare(`
|
|
477
758
|
UPDATE sessions
|
|
478
|
-
SET status = ?, ended_at = ?, updated_at = ?
|
|
759
|
+
SET status = ?, ended_at = ?, size_bytes = ?, updated_at = ?
|
|
479
760
|
WHERE id = ?
|
|
480
761
|
`)
|
|
481
|
-
.run('stopped', now, now, sessionId);
|
|
762
|
+
.run('stopped', now, sizeBytes, now, sessionId);
|
|
482
763
|
logger.info({ sessionId }, 'Session terminated');
|
|
483
764
|
await this.getEventsService().publish('session.stopped', { sessionId });
|
|
484
765
|
if (session.agentId) {
|
|
@@ -494,10 +775,86 @@ let SessionsService = class SessionsService {
|
|
|
494
775
|
}
|
|
495
776
|
}
|
|
496
777
|
}
|
|
778
|
+
async getAgentSessionHistory(agentId, projectId, cursor, limit) {
|
|
779
|
+
const agent = await this.storage.getAgent(agentId);
|
|
780
|
+
if (agent.projectId !== projectId) {
|
|
781
|
+
throw new common_1.ForbiddenException('PROJECT_MISMATCH');
|
|
782
|
+
}
|
|
783
|
+
const clampedLimit = Math.min(Math.max(1, limit), 100);
|
|
784
|
+
const sortExpr = `COALESCE(last_activity_at, ended_at, started_at)`;
|
|
785
|
+
const selectCols = `id, provider_session_id, provider_name_at_launch, status, started_at, ended_at, last_activity_at, size_bytes, transcript_path`;
|
|
786
|
+
const baseStatus = `status IN ('stopped','failed')`;
|
|
787
|
+
const { cnt: total } = this.sqlite
|
|
788
|
+
.prepare(`SELECT COUNT(*) as cnt FROM sessions WHERE agent_id = ? AND ${baseStatus}`)
|
|
789
|
+
.get(agentId);
|
|
790
|
+
let cursorSortKey = null;
|
|
791
|
+
let cursorId = null;
|
|
792
|
+
if (cursor) {
|
|
793
|
+
try {
|
|
794
|
+
const decoded = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf-8'));
|
|
795
|
+
cursorSortKey = decoded.s;
|
|
796
|
+
cursorId = decoded.i;
|
|
797
|
+
}
|
|
798
|
+
catch {
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
let rows;
|
|
802
|
+
if (cursorSortKey && cursorId) {
|
|
803
|
+
rows = this.sqlite
|
|
804
|
+
.prepare(`SELECT ${selectCols} FROM sessions
|
|
805
|
+
WHERE agent_id = ? AND ${baseStatus}
|
|
806
|
+
AND (${sortExpr} < ? OR (${sortExpr} = ? AND id < ?))
|
|
807
|
+
ORDER BY ${sortExpr} DESC, id DESC
|
|
808
|
+
LIMIT ?`)
|
|
809
|
+
.all(agentId, cursorSortKey, cursorSortKey, cursorId, clampedLimit + 1);
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
rows = this.sqlite
|
|
813
|
+
.prepare(`SELECT ${selectCols} FROM sessions
|
|
814
|
+
WHERE agent_id = ? AND ${baseStatus}
|
|
815
|
+
ORDER BY ${sortExpr} DESC, id DESC
|
|
816
|
+
LIMIT ?`)
|
|
817
|
+
.all(agentId, clampedLimit + 1);
|
|
818
|
+
}
|
|
819
|
+
const hasMore = rows.length > clampedLimit;
|
|
820
|
+
const pageRows = hasMore ? rows.slice(0, clampedLimit) : rows;
|
|
821
|
+
const now = new Date().toISOString();
|
|
822
|
+
for (const row of pageRows) {
|
|
823
|
+
if (row.size_bytes === null && row.transcript_path !== null) {
|
|
824
|
+
try {
|
|
825
|
+
const fileStat = await (0, promises_1.stat)(row.transcript_path);
|
|
826
|
+
this.sqlite
|
|
827
|
+
.prepare(`UPDATE sessions SET size_bytes = ?, updated_at = ? WHERE id = ?`)
|
|
828
|
+
.run(fileStat.size, now, row.id);
|
|
829
|
+
row.size_bytes = fileStat.size;
|
|
830
|
+
}
|
|
831
|
+
catch {
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
let nextCursor = null;
|
|
836
|
+
if (hasMore && pageRows.length > 0) {
|
|
837
|
+
const last = pageRows[pageRows.length - 1];
|
|
838
|
+
const sortKey = last.last_activity_at ?? last.ended_at ?? last.started_at;
|
|
839
|
+
nextCursor = Buffer.from(JSON.stringify({ s: sortKey, i: last.id })).toString('base64url');
|
|
840
|
+
}
|
|
841
|
+
const items = pageRows.map((row) => ({
|
|
842
|
+
id: row.id,
|
|
843
|
+
providerSessionId: row.provider_session_id ?? null,
|
|
844
|
+
providerNameAtLaunch: row.provider_name_at_launch,
|
|
845
|
+
status: row.status,
|
|
846
|
+
startedAt: row.started_at,
|
|
847
|
+
endedAt: row.ended_at,
|
|
848
|
+
lastActivityAt: row.last_activity_at,
|
|
849
|
+
sizeBytes: row.size_bytes,
|
|
850
|
+
transcriptAvailable: row.transcript_path !== null,
|
|
851
|
+
}));
|
|
852
|
+
return { items, nextCursor, hasMore, total };
|
|
853
|
+
}
|
|
497
854
|
getSession(sessionId) {
|
|
498
855
|
const row = this.sqlite
|
|
499
856
|
.prepare(`
|
|
500
|
-
SELECT id, epic_id, agent_id, tmux_session_id, status, started_at, ended_at, last_activity_at, activity_state, busy_since, transcript_path,
|
|
857
|
+
SELECT id, epic_id, agent_id, tmux_session_id, status, started_at, ended_at, last_activity_at, activity_state, busy_since, transcript_path, created_at, updated_at
|
|
501
858
|
FROM sessions
|
|
502
859
|
WHERE id = ?
|
|
503
860
|
`)
|
|
@@ -518,7 +875,6 @@ let SessionsService = class SessionsService {
|
|
|
518
875
|
activityState: row.activity_state ?? null,
|
|
519
876
|
busySince: row.busy_since ?? null,
|
|
520
877
|
transcriptPath: row.transcript_path ?? null,
|
|
521
|
-
claudeSessionId: row.claude_session_id ?? null,
|
|
522
878
|
createdAt: row.created_at,
|
|
523
879
|
updatedAt: row.updated_at,
|
|
524
880
|
};
|
|
@@ -526,7 +882,7 @@ let SessionsService = class SessionsService {
|
|
|
526
882
|
async listActiveSessions(projectId, allowedAgentIds) {
|
|
527
883
|
const rows = this.sqlite
|
|
528
884
|
.prepare(`
|
|
529
|
-
SELECT id, epic_id, agent_id, tmux_session_id, status, started_at, ended_at, last_activity_at, activity_state, busy_since, transcript_path,
|
|
885
|
+
SELECT id, epic_id, agent_id, tmux_session_id, status, started_at, ended_at, last_activity_at, activity_state, busy_since, transcript_path, created_at, updated_at
|
|
530
886
|
FROM sessions
|
|
531
887
|
WHERE status = 'running'
|
|
532
888
|
ORDER BY started_at DESC
|
|
@@ -564,7 +920,6 @@ let SessionsService = class SessionsService {
|
|
|
564
920
|
activityState: row.activity_state ?? null,
|
|
565
921
|
busySince: row.busy_since ?? null,
|
|
566
922
|
transcriptPath: row.transcript_path ?? null,
|
|
567
|
-
claudeSessionId: row.claude_session_id ?? null,
|
|
568
923
|
createdAt: row.created_at,
|
|
569
924
|
updatedAt: row.updated_at,
|
|
570
925
|
}));
|
|
@@ -583,7 +938,7 @@ let SessionsService = class SessionsService {
|
|
|
583
938
|
.prepare(`
|
|
584
939
|
SELECT id, epic_id, agent_id, tmux_session_id, status,
|
|
585
940
|
started_at, ended_at, last_activity_at, activity_state,
|
|
586
|
-
busy_since, transcript_path,
|
|
941
|
+
busy_since, transcript_path,
|
|
587
942
|
created_at, updated_at
|
|
588
943
|
FROM sessions
|
|
589
944
|
WHERE status = 'running' AND agent_id = ?
|
|
@@ -606,7 +961,6 @@ let SessionsService = class SessionsService {
|
|
|
606
961
|
activityState: row.activity_state ?? null,
|
|
607
962
|
busySince: row.busy_since ?? null,
|
|
608
963
|
transcriptPath: row.transcript_path ?? null,
|
|
609
|
-
claudeSessionId: row.claude_session_id ?? null,
|
|
610
964
|
createdAt: row.created_at,
|
|
611
965
|
updatedAt: row.updated_at,
|
|
612
966
|
};
|
|
@@ -616,7 +970,7 @@ let SessionsService = class SessionsService {
|
|
|
616
970
|
.prepare(`
|
|
617
971
|
SELECT s.id, s.epic_id, s.agent_id, s.tmux_session_id, s.status,
|
|
618
972
|
s.started_at, s.ended_at, s.last_activity_at, s.activity_state,
|
|
619
|
-
s.busy_since, s.transcript_path,
|
|
973
|
+
s.busy_since, s.transcript_path,
|
|
620
974
|
s.created_at, s.updated_at
|
|
621
975
|
FROM sessions s
|
|
622
976
|
JOIN agents a ON s.agent_id = a.id
|
|
@@ -636,7 +990,6 @@ let SessionsService = class SessionsService {
|
|
|
636
990
|
activityState: row.activity_state ?? null,
|
|
637
991
|
busySince: row.busy_since ?? null,
|
|
638
992
|
transcriptPath: row.transcript_path ?? null,
|
|
639
|
-
claudeSessionId: row.claude_session_id ?? null,
|
|
640
993
|
createdAt: row.created_at,
|
|
641
994
|
updatedAt: row.updated_at,
|
|
642
995
|
}));
|