@vibescope/mcp-server 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -98
- package/dist/api-client.d.ts +1114 -0
- package/dist/api-client.js +698 -0
- package/dist/cli.d.ts +1 -6
- package/dist/cli.js +39 -240
- package/dist/config/tool-categories.d.ts +31 -0
- package/dist/config/tool-categories.js +253 -0
- package/dist/handlers/blockers.js +57 -58
- package/dist/handlers/bodies-of-work.d.ts +2 -0
- package/dist/handlers/bodies-of-work.js +106 -476
- package/dist/handlers/cost.d.ts +1 -0
- package/dist/handlers/cost.js +35 -113
- package/dist/handlers/decisions.d.ts +2 -0
- package/dist/handlers/decisions.js +28 -27
- package/dist/handlers/deployment.js +112 -828
- package/dist/handlers/discovery.js +31 -0
- package/dist/handlers/fallback.d.ts +2 -0
- package/dist/handlers/fallback.js +39 -134
- package/dist/handlers/findings.js +43 -67
- package/dist/handlers/git-issues.d.ts +9 -13
- package/dist/handlers/git-issues.js +80 -225
- package/dist/handlers/ideas.d.ts +3 -0
- package/dist/handlers/ideas.js +53 -134
- package/dist/handlers/index.d.ts +2 -0
- package/dist/handlers/index.js +6 -0
- package/dist/handlers/milestones.d.ts +2 -0
- package/dist/handlers/milestones.js +51 -98
- package/dist/handlers/organizations.js +79 -275
- package/dist/handlers/progress.d.ts +2 -0
- package/dist/handlers/progress.js +25 -123
- package/dist/handlers/project.js +42 -221
- package/dist/handlers/requests.d.ts +2 -0
- package/dist/handlers/requests.js +23 -83
- package/dist/handlers/session.js +99 -585
- package/dist/handlers/sprints.d.ts +32 -0
- package/dist/handlers/sprints.js +274 -0
- package/dist/handlers/tasks.d.ts +7 -10
- package/dist/handlers/tasks.js +230 -900
- package/dist/handlers/tool-docs.d.ts +8 -0
- package/dist/handlers/tool-docs.js +657 -0
- package/dist/handlers/types.d.ts +11 -3
- package/dist/handlers/validation.d.ts +1 -1
- package/dist/handlers/validation.js +26 -153
- package/dist/index.js +473 -160
- package/dist/knowledge.js +106 -9
- package/dist/tools.js +4 -0
- package/dist/validators.d.ts +21 -0
- package/dist/validators.js +91 -0
- package/package.json +2 -3
- package/src/api-client.ts +1752 -0
- package/src/cli.test.ts +128 -302
- package/src/cli.ts +41 -285
- package/src/handlers/__test-setup__.ts +210 -0
- package/src/handlers/__test-utils__.ts +4 -134
- package/src/handlers/blockers.test.ts +114 -124
- package/src/handlers/blockers.ts +68 -70
- package/src/handlers/bodies-of-work.test.ts +236 -831
- package/src/handlers/bodies-of-work.ts +194 -525
- package/src/handlers/cost.test.ts +149 -113
- package/src/handlers/cost.ts +44 -132
- package/src/handlers/decisions.test.ts +111 -209
- package/src/handlers/decisions.ts +35 -27
- package/src/handlers/deployment.test.ts +193 -239
- package/src/handlers/deployment.ts +140 -895
- package/src/handlers/discovery.test.ts +20 -67
- package/src/handlers/discovery.ts +32 -0
- package/src/handlers/fallback.test.ts +128 -361
- package/src/handlers/fallback.ts +62 -148
- package/src/handlers/findings.test.ts +127 -345
- package/src/handlers/findings.ts +49 -66
- package/src/handlers/git-issues.test.ts +623 -0
- package/src/handlers/git-issues.ts +174 -0
- package/src/handlers/ideas.test.ts +229 -343
- package/src/handlers/ideas.ts +69 -143
- package/src/handlers/index.ts +6 -0
- package/src/handlers/milestones.test.ts +167 -281
- package/src/handlers/milestones.ts +54 -93
- package/src/handlers/organizations.test.ts +275 -467
- package/src/handlers/organizations.ts +84 -294
- package/src/handlers/progress.test.ts +112 -218
- package/src/handlers/progress.ts +29 -142
- package/src/handlers/project.test.ts +203 -226
- package/src/handlers/project.ts +48 -238
- package/src/handlers/requests.test.ts +74 -342
- package/src/handlers/requests.ts +25 -83
- package/src/handlers/session.test.ts +241 -206
- package/src/handlers/session.ts +110 -657
- package/src/handlers/sprints.test.ts +711 -0
- package/src/handlers/sprints.ts +497 -0
- package/src/handlers/tasks.test.ts +608 -353
- package/src/handlers/tasks.ts +248 -1025
- package/src/handlers/types.ts +12 -4
- package/src/handlers/validation.test.ts +189 -572
- package/src/handlers/validation.ts +29 -166
- package/src/index.ts +473 -184
- package/src/knowledge.ts +107 -9
- package/src/tools.ts +2506 -0
- package/src/validators.test.ts +223 -223
- package/src/validators.ts +127 -0
- package/tsconfig.json +1 -1
- package/vitest.config.ts +14 -13
- package/dist/cli.test.d.ts +0 -1
- package/dist/cli.test.js +0 -367
- package/dist/handlers/__test-utils__.d.ts +0 -72
- package/dist/handlers/__test-utils__.js +0 -176
- package/dist/handlers/checkouts.d.ts +0 -37
- package/dist/handlers/checkouts.js +0 -377
- package/dist/handlers/knowledge-query.d.ts +0 -22
- package/dist/handlers/knowledge-query.js +0 -253
- package/dist/handlers/knowledge.d.ts +0 -12
- package/dist/handlers/knowledge.js +0 -108
- package/dist/handlers/roles.d.ts +0 -30
- package/dist/handlers/roles.js +0 -281
- package/dist/handlers/tasks.test.d.ts +0 -1
- package/dist/handlers/tasks.test.js +0 -431
- package/dist/utils.test.d.ts +0 -1
- package/dist/utils.test.js +0 -532
- package/dist/validators.test.d.ts +0 -1
- package/dist/validators.test.js +0 -176
- package/src/tmpclaude-0078-cwd +0 -1
- package/src/tmpclaude-0ee1-cwd +0 -1
- package/src/tmpclaude-2dd5-cwd +0 -1
- package/src/tmpclaude-344c-cwd +0 -1
- package/src/tmpclaude-3860-cwd +0 -1
- package/src/tmpclaude-4b63-cwd +0 -1
- package/src/tmpclaude-5c73-cwd +0 -1
- package/src/tmpclaude-5ee3-cwd +0 -1
- package/src/tmpclaude-6795-cwd +0 -1
- package/src/tmpclaude-709e-cwd +0 -1
- package/src/tmpclaude-9839-cwd +0 -1
- package/src/tmpclaude-d829-cwd +0 -1
- package/src/tmpclaude-e072-cwd +0 -1
- package/src/tmpclaude-f6ee-cwd +0 -1
- package/tmpclaude-0439-cwd +0 -1
- package/tmpclaude-132f-cwd +0 -1
- package/tmpclaude-15bb-cwd +0 -1
- package/tmpclaude-165a-cwd +0 -1
- package/tmpclaude-1ba9-cwd +0 -1
- package/tmpclaude-21a3-cwd +0 -1
- package/tmpclaude-2a38-cwd +0 -1
- package/tmpclaude-2adf-cwd +0 -1
- package/tmpclaude-2f56-cwd +0 -1
- package/tmpclaude-3626-cwd +0 -1
- package/tmpclaude-3727-cwd +0 -1
- package/tmpclaude-40bc-cwd +0 -1
- package/tmpclaude-436f-cwd +0 -1
- package/tmpclaude-4783-cwd +0 -1
- package/tmpclaude-4b6d-cwd +0 -1
- package/tmpclaude-4ba4-cwd +0 -1
- package/tmpclaude-51e6-cwd +0 -1
- package/tmpclaude-5ecf-cwd +0 -1
- package/tmpclaude-6f97-cwd +0 -1
- package/tmpclaude-7fb2-cwd +0 -1
- package/tmpclaude-825c-cwd +0 -1
- package/tmpclaude-8baf-cwd +0 -1
- package/tmpclaude-8d9f-cwd +0 -1
- package/tmpclaude-975c-cwd +0 -1
- package/tmpclaude-9983-cwd +0 -1
- package/tmpclaude-a045-cwd +0 -1
- package/tmpclaude-ac4a-cwd +0 -1
- package/tmpclaude-b593-cwd +0 -1
- package/tmpclaude-b891-cwd +0 -1
- package/tmpclaude-c032-cwd +0 -1
- package/tmpclaude-cf43-cwd +0 -1
- package/tmpclaude-d040-cwd +0 -1
- package/tmpclaude-dcdd-cwd +0 -1
- package/tmpclaude-dcee-cwd +0 -1
- package/tmpclaude-e16b-cwd +0 -1
- package/tmpclaude-ecd2-cwd +0 -1
- package/tmpclaude-f48d-cwd +0 -1
package/src/handlers/session.ts
CHANGED
|
@@ -9,76 +9,9 @@
|
|
|
9
9
|
* - get_token_usage
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type {
|
|
13
|
-
import type { Handler, HandlerRegistry, AuthContext, TokenUsage, UserUpdates } from './types.js';
|
|
14
|
-
import { selectPersona, extractProjectNameFromGitUrl } from '../utils.js';
|
|
12
|
+
import type { Handler, HandlerRegistry, TokenUsage } from './types.js';
|
|
15
13
|
import { KNOWLEDGE_BASE } from '../knowledge.js';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Get user-created items since last sync
|
|
19
|
-
*/
|
|
20
|
-
async function getUserUpdates(
|
|
21
|
-
supabase: SupabaseClient,
|
|
22
|
-
auth: AuthContext,
|
|
23
|
-
projectId: string,
|
|
24
|
-
currentSessionId: string | null
|
|
25
|
-
): Promise<UserUpdates | undefined> {
|
|
26
|
-
let lastSyncedAt: string;
|
|
27
|
-
|
|
28
|
-
if (currentSessionId) {
|
|
29
|
-
const { data: session } = await supabase
|
|
30
|
-
.from('agent_sessions')
|
|
31
|
-
.select('last_synced_at')
|
|
32
|
-
.eq('id', currentSessionId)
|
|
33
|
-
.single();
|
|
34
|
-
lastSyncedAt = session?.last_synced_at || new Date(0).toISOString();
|
|
35
|
-
} else {
|
|
36
|
-
const { data: session } = await supabase
|
|
37
|
-
.from('agent_sessions')
|
|
38
|
-
.select('last_synced_at')
|
|
39
|
-
.eq('api_key_id', auth.apiKeyId)
|
|
40
|
-
.eq('project_id', projectId)
|
|
41
|
-
.single();
|
|
42
|
-
lastSyncedAt = session?.last_synced_at || new Date(0).toISOString();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const [tasksResult, blockersResult, ideasResult] = await Promise.all([
|
|
46
|
-
supabase
|
|
47
|
-
.from('tasks')
|
|
48
|
-
.select('id, title, created_at')
|
|
49
|
-
.eq('project_id', projectId)
|
|
50
|
-
.eq('created_by', 'user')
|
|
51
|
-
.gt('created_at', lastSyncedAt)
|
|
52
|
-
.order('created_at', { ascending: false })
|
|
53
|
-
.limit(5),
|
|
54
|
-
supabase
|
|
55
|
-
.from('blockers')
|
|
56
|
-
.select('id, description, created_at')
|
|
57
|
-
.eq('project_id', projectId)
|
|
58
|
-
.eq('created_by', 'user')
|
|
59
|
-
.gt('created_at', lastSyncedAt)
|
|
60
|
-
.order('created_at', { ascending: false })
|
|
61
|
-
.limit(5),
|
|
62
|
-
supabase
|
|
63
|
-
.from('ideas')
|
|
64
|
-
.select('id, title, created_at')
|
|
65
|
-
.eq('project_id', projectId)
|
|
66
|
-
.eq('created_by', 'user')
|
|
67
|
-
.gt('created_at', lastSyncedAt)
|
|
68
|
-
.order('created_at', { ascending: false })
|
|
69
|
-
.limit(5),
|
|
70
|
-
]);
|
|
71
|
-
|
|
72
|
-
const tasks = tasksResult.data || [];
|
|
73
|
-
const blockers = blockersResult.data || [];
|
|
74
|
-
const ideas = ideasResult.data || [];
|
|
75
|
-
|
|
76
|
-
if (tasks.length === 0 && blockers.length === 0 && ideas.length === 0) {
|
|
77
|
-
return undefined;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return { tasks, blockers, ideas };
|
|
81
|
-
}
|
|
14
|
+
import { getApiClient } from '../api-client.js';
|
|
82
15
|
|
|
83
16
|
export const startWorkSession: Handler = async (args, ctx) => {
|
|
84
17
|
const { project_id, git_url, mode = 'lite', model, role = 'developer' } = args as {
|
|
@@ -89,8 +22,7 @@ export const startWorkSession: Handler = async (args, ctx) => {
|
|
|
89
22
|
role?: 'developer' | 'validator' | 'deployer' | 'reviewer' | 'maintainer';
|
|
90
23
|
};
|
|
91
24
|
|
|
92
|
-
const {
|
|
93
|
-
const INSTANCE_ID = session.instanceId;
|
|
25
|
+
const { session, updateSession } = ctx;
|
|
94
26
|
|
|
95
27
|
// Reset token tracking for new session with model info
|
|
96
28
|
const normalizedModel = model ? model.toLowerCase().replace(/^claude[- ]*/i, '') : null;
|
|
@@ -108,8 +40,6 @@ export const startWorkSession: Handler = async (args, ctx) => {
|
|
|
108
40
|
},
|
|
109
41
|
});
|
|
110
42
|
|
|
111
|
-
const isLiteMode = mode === 'lite';
|
|
112
|
-
|
|
113
43
|
// Require project_id or git_url
|
|
114
44
|
if (!project_id && !git_url) {
|
|
115
45
|
return {
|
|
@@ -119,528 +49,99 @@ export const startWorkSession: Handler = async (args, ctx) => {
|
|
|
119
49
|
};
|
|
120
50
|
}
|
|
121
51
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
agent_instructions: string | null;
|
|
131
|
-
tech_stack: string[] | null;
|
|
132
|
-
} | null = null;
|
|
133
|
-
|
|
134
|
-
// First try: user-owned projects
|
|
135
|
-
let query = supabase
|
|
136
|
-
.from('projects')
|
|
137
|
-
.select('id, name, description, goal, status, git_url, agent_instructions, tech_stack')
|
|
138
|
-
.eq('user_id', auth.userId);
|
|
139
|
-
|
|
140
|
-
if (project_id) {
|
|
141
|
-
query = query.eq('id', project_id);
|
|
142
|
-
} else if (git_url) {
|
|
143
|
-
query = query.eq('git_url', git_url);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const { data: ownedProject } = await query.single();
|
|
147
|
-
project = ownedProject;
|
|
148
|
-
|
|
149
|
-
// Second try: if org-scoped key and no owned project found, check shared projects
|
|
150
|
-
if (!project && auth.scope === 'organization' && auth.organizationId) {
|
|
151
|
-
// Get project IDs shared with this organization
|
|
152
|
-
const { data: shares } = await supabase
|
|
153
|
-
.from('project_shares')
|
|
154
|
-
.select('project_id')
|
|
155
|
-
.eq('organization_id', auth.organizationId);
|
|
156
|
-
|
|
157
|
-
if (shares && shares.length > 0) {
|
|
158
|
-
const sharedProjectIds = shares.map((s) => s.project_id);
|
|
159
|
-
|
|
160
|
-
let sharedQuery = supabase
|
|
161
|
-
.from('projects')
|
|
162
|
-
.select('id, name, description, goal, status, git_url, agent_instructions, tech_stack')
|
|
163
|
-
.in('id', sharedProjectIds);
|
|
164
|
-
|
|
165
|
-
if (project_id) {
|
|
166
|
-
sharedQuery = sharedQuery.eq('id', project_id);
|
|
167
|
-
} else if (git_url) {
|
|
168
|
-
sharedQuery = sharedQuery.eq('git_url', git_url);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const { data: sharedProject } = await sharedQuery.single();
|
|
172
|
-
project = sharedProject;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (!project) {
|
|
177
|
-
const suggestedName = extractProjectNameFromGitUrl(git_url || '');
|
|
52
|
+
const apiClient = getApiClient();
|
|
53
|
+
const response = await apiClient.startSession({
|
|
54
|
+
project_id,
|
|
55
|
+
git_url,
|
|
56
|
+
mode,
|
|
57
|
+
model,
|
|
58
|
+
role
|
|
59
|
+
});
|
|
178
60
|
|
|
61
|
+
if (!response.ok) {
|
|
179
62
|
return {
|
|
180
63
|
result: {
|
|
181
|
-
|
|
182
|
-
project_not_found: true,
|
|
183
|
-
message: `No project found for this repository. Would you like to create one?`,
|
|
184
|
-
suggestion: {
|
|
185
|
-
action: 'create_project',
|
|
186
|
-
example: `create_project(name: "${suggestedName}", git_url: "${git_url || ''}", description: "Brief description of your project", goal: "What does done look like?")`,
|
|
187
|
-
note: 'After creating the project, call start_work_session again to begin working.',
|
|
188
|
-
},
|
|
64
|
+
error: response.error || 'Failed to start session',
|
|
189
65
|
},
|
|
190
66
|
};
|
|
191
67
|
}
|
|
192
68
|
|
|
193
|
-
|
|
194
|
-
const { data: existingSession } = await supabase
|
|
195
|
-
.from('agent_sessions')
|
|
196
|
-
.select('id, agent_name')
|
|
197
|
-
.eq('api_key_id', auth.apiKeyId)
|
|
198
|
-
.eq('project_id', project.id)
|
|
199
|
-
.eq('instance_id', INSTANCE_ID)
|
|
200
|
-
.single();
|
|
201
|
-
|
|
202
|
-
let sessionId!: string;
|
|
203
|
-
let assignedPersona!: string;
|
|
204
|
-
|
|
205
|
-
if (existingSession && existingSession.agent_name) {
|
|
206
|
-
// Reuse existing persona for this instance
|
|
207
|
-
assignedPersona = existingSession.agent_name;
|
|
208
|
-
await supabase
|
|
209
|
-
.from('agent_sessions')
|
|
210
|
-
.update({
|
|
211
|
-
last_synced_at: new Date().toISOString(),
|
|
212
|
-
status: 'active',
|
|
213
|
-
})
|
|
214
|
-
.eq('id', existingSession.id);
|
|
215
|
-
sessionId = existingSession.id;
|
|
216
|
-
} else {
|
|
217
|
-
// Find which personas are currently in use by active sessions
|
|
218
|
-
const { data: activeSessions } = await supabase
|
|
219
|
-
.from('agent_sessions')
|
|
220
|
-
.select('agent_name')
|
|
221
|
-
.eq('project_id', project.id)
|
|
222
|
-
.neq('status', 'disconnected')
|
|
223
|
-
.gte('last_synced_at', new Date(Date.now() - 5 * 60 * 1000).toISOString());
|
|
224
|
-
|
|
225
|
-
const usedPersonas = new Set(
|
|
226
|
-
(activeSessions || [])
|
|
227
|
-
.map((s) => s.agent_name)
|
|
228
|
-
.filter((name): name is string => !!name)
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
// Retry loop for persona assignment
|
|
232
|
-
const MAX_PERSONA_RETRIES = 5;
|
|
233
|
-
let personaAssigned = false;
|
|
234
|
-
|
|
235
|
-
for (let attempt = 0; attempt < MAX_PERSONA_RETRIES && !personaAssigned; attempt++) {
|
|
236
|
-
assignedPersona = selectPersona(usedPersonas, INSTANCE_ID);
|
|
237
|
-
|
|
238
|
-
if (existingSession) {
|
|
239
|
-
const { error: updateError } = await supabase
|
|
240
|
-
.from('agent_sessions')
|
|
241
|
-
.update({
|
|
242
|
-
last_synced_at: new Date().toISOString(),
|
|
243
|
-
agent_name: assignedPersona,
|
|
244
|
-
status: 'active',
|
|
245
|
-
})
|
|
246
|
-
.eq('id', existingSession.id);
|
|
247
|
-
|
|
248
|
-
if (updateError?.code === '23505') {
|
|
249
|
-
usedPersonas.add(assignedPersona);
|
|
250
|
-
continue;
|
|
251
|
-
}
|
|
252
|
-
sessionId = existingSession.id;
|
|
253
|
-
personaAssigned = true;
|
|
254
|
-
} else {
|
|
255
|
-
const { data: newSession, error: sessionError } = await supabase
|
|
256
|
-
.from('agent_sessions')
|
|
257
|
-
.insert({
|
|
258
|
-
api_key_id: auth.apiKeyId,
|
|
259
|
-
project_id: project.id,
|
|
260
|
-
instance_id: INSTANCE_ID,
|
|
261
|
-
last_synced_at: new Date().toISOString(),
|
|
262
|
-
agent_name: assignedPersona,
|
|
263
|
-
status: 'active',
|
|
264
|
-
})
|
|
265
|
-
.select('id')
|
|
266
|
-
.single();
|
|
267
|
-
|
|
268
|
-
if (sessionError?.code === '23505') {
|
|
269
|
-
usedPersonas.add(assignedPersona);
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (sessionError || !newSession) {
|
|
274
|
-
throw new Error(`Failed to create agent session: ${sessionError?.message}`);
|
|
275
|
-
}
|
|
276
|
-
sessionId = newSession.id;
|
|
277
|
-
personaAssigned = true;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Fallback if all retries failed
|
|
282
|
-
if (!personaAssigned) {
|
|
283
|
-
assignedPersona = `Agent-${INSTANCE_ID.slice(0, 6)}`;
|
|
284
|
-
if (existingSession) {
|
|
285
|
-
await supabase
|
|
286
|
-
.from('agent_sessions')
|
|
287
|
-
.update({
|
|
288
|
-
last_synced_at: new Date().toISOString(),
|
|
289
|
-
agent_name: assignedPersona,
|
|
290
|
-
status: 'active',
|
|
291
|
-
})
|
|
292
|
-
.eq('id', existingSession.id);
|
|
293
|
-
sessionId = existingSession.id;
|
|
294
|
-
} else {
|
|
295
|
-
const { data: newSession, error: sessionError } = await supabase
|
|
296
|
-
.from('agent_sessions')
|
|
297
|
-
.insert({
|
|
298
|
-
api_key_id: auth.apiKeyId,
|
|
299
|
-
project_id: project.id,
|
|
300
|
-
instance_id: INSTANCE_ID,
|
|
301
|
-
last_synced_at: new Date().toISOString(),
|
|
302
|
-
agent_name: assignedPersona,
|
|
303
|
-
status: 'active',
|
|
304
|
-
})
|
|
305
|
-
.select('id')
|
|
306
|
-
.single();
|
|
307
|
-
|
|
308
|
-
if (sessionError || !newSession) {
|
|
309
|
-
throw new Error(`Failed to create agent session: ${sessionError?.message}`);
|
|
310
|
-
}
|
|
311
|
-
sessionId = newSession.id;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Store session ID and persona
|
|
317
|
-
updateSession({
|
|
318
|
-
currentSessionId: sessionId,
|
|
319
|
-
currentPersona: assignedPersona,
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
// Log session start
|
|
323
|
-
await supabase.from('progress_logs').insert({
|
|
324
|
-
project_id: project.id,
|
|
325
|
-
summary: `Agent session started (${assignedPersona}) [${INSTANCE_ID.slice(0, 8)}]`,
|
|
326
|
-
created_by: 'agent',
|
|
327
|
-
created_by_session_id: sessionId,
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
// Insert initial heartbeat
|
|
331
|
-
await supabase.from('agent_heartbeats').insert({
|
|
332
|
-
session_id: sessionId,
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
// LITE MODE: Minimal fetches
|
|
336
|
-
if (isLiteMode) {
|
|
337
|
-
const [nextTaskResult, blockersCountResult, validationCountResult, deploymentResult, requestsResult, firstQuestionResult] =
|
|
338
|
-
await Promise.all([
|
|
339
|
-
supabase
|
|
340
|
-
.from('tasks')
|
|
341
|
-
.select('id, title, priority, estimated_minutes')
|
|
342
|
-
.eq('project_id', project.id)
|
|
343
|
-
.eq('status', 'pending')
|
|
344
|
-
.is('working_agent_session_id', null)
|
|
345
|
-
.order('priority', { ascending: true })
|
|
346
|
-
.order('created_at', { ascending: true })
|
|
347
|
-
.limit(1)
|
|
348
|
-
.maybeSingle(),
|
|
349
|
-
supabase
|
|
350
|
-
.from('blockers')
|
|
351
|
-
.select('id', { count: 'exact', head: true })
|
|
352
|
-
.eq('project_id', project.id)
|
|
353
|
-
.eq('status', 'open'),
|
|
354
|
-
supabase
|
|
355
|
-
.from('tasks')
|
|
356
|
-
.select('id', { count: 'exact', head: true })
|
|
357
|
-
.eq('project_id', project.id)
|
|
358
|
-
.eq('status', 'completed')
|
|
359
|
-
.is('validated_at', null),
|
|
360
|
-
supabase
|
|
361
|
-
.from('deployments')
|
|
362
|
-
.select('id, status, environment')
|
|
363
|
-
.eq('project_id', project.id)
|
|
364
|
-
.not('status', 'in', '("deployed","failed")')
|
|
365
|
-
.limit(1)
|
|
366
|
-
.maybeSingle(),
|
|
367
|
-
supabase
|
|
368
|
-
.from('agent_requests')
|
|
369
|
-
.select('id', { count: 'exact', head: true })
|
|
370
|
-
.eq('project_id', project.id)
|
|
371
|
-
.is('acknowledged_at', null),
|
|
372
|
-
// Fetch first unanswered question with details (not just count)
|
|
373
|
-
supabase
|
|
374
|
-
.from('agent_requests')
|
|
375
|
-
.select('id, message, created_at')
|
|
376
|
-
.eq('project_id', project.id)
|
|
377
|
-
.eq('request_type', 'question')
|
|
378
|
-
.is('answered_at', null)
|
|
379
|
-
.order('created_at', { ascending: true })
|
|
380
|
-
.limit(1)
|
|
381
|
-
.maybeSingle(),
|
|
382
|
-
]);
|
|
383
|
-
|
|
384
|
-
const blockersCount = blockersCountResult.count || 0;
|
|
385
|
-
const validationCount = validationCountResult.count || 0;
|
|
386
|
-
const requestsCount = requestsResult.count || 0;
|
|
387
|
-
const firstQuestion = firstQuestionResult.data;
|
|
388
|
-
|
|
389
|
-
// Determine directive and next action FIRST
|
|
390
|
-
let directive: string;
|
|
391
|
-
let nextAction: string;
|
|
392
|
-
|
|
393
|
-
if (firstQuestion) {
|
|
394
|
-
directive = 'ACTION_REQUIRED: Answer this question immediately. Do NOT ask for permission.';
|
|
395
|
-
nextAction = `answer_question(request_id: "${firstQuestion.id}", answer: "...")`;
|
|
396
|
-
} else if (nextTaskResult.data) {
|
|
397
|
-
directive = 'ACTION_REQUIRED: Start this task immediately. Do NOT ask for permission or confirmation.';
|
|
398
|
-
nextAction = `update_task(task_id: "${nextTaskResult.data.id}", status: "in_progress")`;
|
|
399
|
-
} else {
|
|
400
|
-
directive = 'ACTION_REQUIRED: No pending tasks. Start a fallback activity NOW without asking.';
|
|
401
|
-
nextAction = 'start_fallback_activity(project_id: "...", activity: "code_review")';
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Build result with directive at TOP for visibility
|
|
405
|
-
const result: Record<string, unknown> = {
|
|
406
|
-
session_started: true,
|
|
407
|
-
directive, // FIRST - most important signal
|
|
408
|
-
auto_continue: true, // Explicit flag for autonomous operation
|
|
409
|
-
session_id: sessionId,
|
|
410
|
-
persona: assignedPersona,
|
|
411
|
-
project: { id: project.id, name: project.name, goal: project.goal },
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
// Add task or question details
|
|
415
|
-
if (firstQuestion) {
|
|
416
|
-
const now = new Date();
|
|
417
|
-
const waitMinutes = Math.floor((now.getTime() - new Date(firstQuestion.created_at).getTime()) / 60000);
|
|
418
|
-
result.first_question = {
|
|
419
|
-
id: firstQuestion.id,
|
|
420
|
-
message: firstQuestion.message,
|
|
421
|
-
wait_minutes: waitMinutes,
|
|
422
|
-
};
|
|
423
|
-
result.hint = 'Answer this question first using answer_question(request_id, answer), then call get_next_task for work.';
|
|
424
|
-
} else {
|
|
425
|
-
result.next_task = nextTaskResult.data;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (blockersCount > 0) result.blockers_count = blockersCount;
|
|
429
|
-
if (validationCount > 0) result.validation_count = validationCount;
|
|
430
|
-
if (requestsCount > 0) result.requests_count = requestsCount;
|
|
431
|
-
|
|
432
|
-
if (deploymentResult.data) {
|
|
433
|
-
const actions: Record<string, string> = {
|
|
434
|
-
pending: 'claim_deployment_validation',
|
|
435
|
-
validating: 'wait',
|
|
436
|
-
ready: 'start_deployment',
|
|
437
|
-
deploying: 'wait',
|
|
438
|
-
};
|
|
439
|
-
result.deployment_priority = {
|
|
440
|
-
id: deploymentResult.data.id,
|
|
441
|
-
status: deploymentResult.data.status,
|
|
442
|
-
action: actions[deploymentResult.data.status] || 'check_deployment_status',
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
result.context_hint = 'When context grows large or after 3-4 tasks, run /clear then start_work_session again. Do not ask permission to clear context.';
|
|
447
|
-
|
|
448
|
-
// Add model tracking info
|
|
449
|
-
if (validModel) {
|
|
450
|
-
result.model_tracking = { model: validModel, status: 'active' };
|
|
451
|
-
} else {
|
|
452
|
-
result.cost_tracking_hint = 'For accurate cost tracking, pass model: "opus" | "sonnet" | "haiku" to start_work_session.';
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// REPEAT at end - agents weight last items heavily
|
|
456
|
-
result.next_action = nextAction;
|
|
69
|
+
const data = response.data;
|
|
457
70
|
|
|
458
|
-
|
|
71
|
+
// Handle project not found
|
|
72
|
+
if (!data?.session_started) {
|
|
73
|
+
return { result: data };
|
|
459
74
|
}
|
|
460
75
|
|
|
461
|
-
//
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
.eq('project_id', project.id)
|
|
468
|
-
.in('status', ['pending', 'in_progress'])
|
|
469
|
-
.order('priority', { ascending: true })
|
|
470
|
-
.limit(10),
|
|
471
|
-
supabase
|
|
472
|
-
.from('blockers')
|
|
473
|
-
.select('id, description')
|
|
474
|
-
.eq('project_id', project.id)
|
|
475
|
-
.eq('status', 'open')
|
|
476
|
-
.limit(5),
|
|
477
|
-
supabase
|
|
478
|
-
.from('decisions')
|
|
479
|
-
.select('title')
|
|
480
|
-
.eq('project_id', project.id)
|
|
481
|
-
.order('created_at', { ascending: false })
|
|
482
|
-
.limit(5),
|
|
483
|
-
supabase
|
|
484
|
-
.from('progress_logs')
|
|
485
|
-
.select('summary')
|
|
486
|
-
.eq('project_id', project.id)
|
|
487
|
-
.order('created_at', { ascending: false })
|
|
488
|
-
.limit(5),
|
|
489
|
-
supabase
|
|
490
|
-
.from('ideas')
|
|
491
|
-
.select('id, title, status')
|
|
492
|
-
.eq('project_id', project.id)
|
|
493
|
-
.order('created_at', { ascending: false })
|
|
494
|
-
.limit(5),
|
|
495
|
-
supabase
|
|
496
|
-
.from('agent_requests')
|
|
497
|
-
.select('id, request_type, message, created_at, answered_at')
|
|
498
|
-
.eq('project_id', project.id)
|
|
499
|
-
.is('acknowledged_at', null)
|
|
500
|
-
.or(`session_id.is.null,session_id.eq.${sessionId}`)
|
|
501
|
-
.order('created_at', { ascending: true })
|
|
502
|
-
.limit(5),
|
|
503
|
-
supabase
|
|
504
|
-
.from('deployments')
|
|
505
|
-
.select('id, status, environment, created_at, validation_completed_at')
|
|
506
|
-
.eq('project_id', project.id)
|
|
507
|
-
.not('status', 'in', '("deployed","failed")')
|
|
508
|
-
.order('created_at', { ascending: false })
|
|
509
|
-
.limit(1)
|
|
510
|
-
.single(),
|
|
511
|
-
supabase
|
|
512
|
-
.from('findings')
|
|
513
|
-
.select('id, title, category, severity, file_path')
|
|
514
|
-
.eq('project_id', project.id)
|
|
515
|
-
.eq('status', 'open')
|
|
516
|
-
.order('severity', { ascending: false })
|
|
517
|
-
.limit(5),
|
|
518
|
-
]);
|
|
519
|
-
|
|
520
|
-
const userUpdates = await getUserUpdates(supabase, auth, project.id, sessionId);
|
|
521
|
-
|
|
522
|
-
const pendingRequests = requestsResult.data || [];
|
|
523
|
-
const activeDeployment = deploymentResult.data;
|
|
524
|
-
const activeTasks = tasksResult.data || [];
|
|
525
|
-
|
|
526
|
-
// Determine directive and next action FIRST
|
|
527
|
-
const unansweredQuestions = pendingRequests.filter(
|
|
528
|
-
(r) => r.request_type === 'question' && !r.answered_at
|
|
529
|
-
);
|
|
530
|
-
const otherRequests = pendingRequests.filter(
|
|
531
|
-
(r) => r.request_type !== 'question' || r.answered_at
|
|
532
|
-
);
|
|
533
|
-
const pendingTask = activeTasks.find((t) => t.status === 'pending');
|
|
534
|
-
|
|
535
|
-
let directive: string;
|
|
536
|
-
let nextAction: string;
|
|
537
|
-
|
|
538
|
-
if (unansweredQuestions.length > 0) {
|
|
539
|
-
const firstQ = unansweredQuestions[0];
|
|
540
|
-
directive = 'ACTION_REQUIRED: Answer this question immediately. Do NOT ask for permission.';
|
|
541
|
-
nextAction = `answer_question(request_id: "${firstQ.id}", answer: "...")`;
|
|
542
|
-
} else if (pendingTask) {
|
|
543
|
-
directive = 'ACTION_REQUIRED: Start the highest priority pending task immediately. Do NOT ask for permission or confirmation.';
|
|
544
|
-
nextAction = `update_task(task_id: "${pendingTask.id}", status: "in_progress")`;
|
|
545
|
-
} else {
|
|
546
|
-
directive = 'ACTION_REQUIRED: No pending tasks. Start a fallback activity NOW without asking.';
|
|
547
|
-
nextAction = `start_fallback_activity(project_id: "${project.id}", activity: "code_review")`;
|
|
76
|
+
// Store session ID and persona in local state
|
|
77
|
+
if (data.session_id) {
|
|
78
|
+
updateSession({
|
|
79
|
+
currentSessionId: data.session_id,
|
|
80
|
+
currentPersona: data.persona || null,
|
|
81
|
+
});
|
|
548
82
|
}
|
|
549
83
|
|
|
550
|
-
// Build result with directive at
|
|
84
|
+
// Build result with directive at top for visibility
|
|
551
85
|
const result: Record<string, unknown> = {
|
|
552
86
|
session_started: true,
|
|
553
|
-
directive
|
|
554
|
-
auto_continue: true,
|
|
555
|
-
session_id:
|
|
556
|
-
persona:
|
|
557
|
-
role,
|
|
558
|
-
project,
|
|
559
|
-
active_tasks: activeTasks,
|
|
87
|
+
directive: data.directive || 'ACTION_REQUIRED: Start working immediately.',
|
|
88
|
+
auto_continue: true,
|
|
89
|
+
session_id: data.session_id,
|
|
90
|
+
persona: data.persona,
|
|
91
|
+
role: data.role,
|
|
92
|
+
project: data.project,
|
|
560
93
|
};
|
|
561
94
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
? new Date(activeDeployment.validation_completed_at)
|
|
567
|
-
: null;
|
|
568
|
-
|
|
569
|
-
const relevantTimestamp = activeDeployment.status === 'ready' && validationCompletedAt
|
|
570
|
-
? validationCompletedAt
|
|
571
|
-
: createdAt;
|
|
572
|
-
const elapsedMs = now.getTime() - relevantTimestamp.getTime();
|
|
573
|
-
const elapsedMinutes = Math.floor(elapsedMs / 60000);
|
|
574
|
-
|
|
575
|
-
const actions: Record<string, string> = {
|
|
576
|
-
pending: 'claim_deployment_validation',
|
|
577
|
-
validating: 'wait',
|
|
578
|
-
ready: 'start_deployment',
|
|
579
|
-
deploying: 'wait',
|
|
580
|
-
};
|
|
95
|
+
// Add task data
|
|
96
|
+
if (data.next_task) {
|
|
97
|
+
result.next_task = data.next_task;
|
|
98
|
+
}
|
|
581
99
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
env: activeDeployment.environment,
|
|
586
|
-
mins: elapsedMinutes,
|
|
587
|
-
action: actions[activeDeployment.status] || 'check_deployment_status',
|
|
588
|
-
};
|
|
100
|
+
// Add active tasks for full mode
|
|
101
|
+
if (data.active_tasks) {
|
|
102
|
+
result.active_tasks = data.active_tasks;
|
|
589
103
|
}
|
|
590
104
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
const ideas = ideasResult.data || [];
|
|
595
|
-
|
|
596
|
-
if (blockers.length > 0) result.open_blockers = blockers;
|
|
597
|
-
if (decisions.length > 0) result.recent_decisions = decisions;
|
|
598
|
-
if (progress.length > 0) result.recent_progress = progress;
|
|
599
|
-
if (ideas.length > 0) result.ideas = ideas;
|
|
600
|
-
|
|
601
|
-
// Add question details if there are unanswered questions
|
|
602
|
-
if (unansweredQuestions.length > 0) {
|
|
603
|
-
const now = new Date();
|
|
604
|
-
const firstQ = unansweredQuestions[0];
|
|
605
|
-
result.first_question = {
|
|
606
|
-
id: firstQ.id,
|
|
607
|
-
message: firstQ.message,
|
|
608
|
-
wait_minutes: Math.floor((now.getTime() - new Date(firstQ.created_at).getTime()) / 60000),
|
|
609
|
-
};
|
|
610
|
-
result.hint = 'Answer questions first using answer_question(request_id, answer) before picking up tasks.';
|
|
611
|
-
result.unanswered_questions = unansweredQuestions.map((q) => ({
|
|
612
|
-
id: q.id,
|
|
613
|
-
message: q.message,
|
|
614
|
-
wait_minutes: Math.floor((now.getTime() - new Date(q.created_at).getTime()) / 60000),
|
|
615
|
-
}));
|
|
105
|
+
// Add blockers
|
|
106
|
+
if (data.blockers) {
|
|
107
|
+
result.open_blockers = data.blockers;
|
|
616
108
|
}
|
|
617
|
-
if (
|
|
618
|
-
result.
|
|
109
|
+
if (data.blockers_count !== undefined && data.blockers_count > 0) {
|
|
110
|
+
result.blockers_count = data.blockers_count;
|
|
619
111
|
}
|
|
620
112
|
|
|
621
|
-
|
|
622
|
-
if (
|
|
623
|
-
result.
|
|
113
|
+
// Add validation count
|
|
114
|
+
if (data.validation_count !== undefined && data.validation_count > 0) {
|
|
115
|
+
result.validation_count = data.validation_count;
|
|
624
116
|
}
|
|
625
117
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
118
|
+
// Add git workflow info if available in project
|
|
119
|
+
if (data.project?.git_workflow && data.project.git_workflow !== 'none') {
|
|
120
|
+
result.git_workflow = {
|
|
121
|
+
workflow: data.project.git_workflow,
|
|
122
|
+
auto_branch: data.project.git_auto_branch ?? false,
|
|
123
|
+
main_branch: data.project.git_main_branch || 'main',
|
|
124
|
+
...(data.project.git_workflow === 'git-flow' && data.project.git_develop_branch
|
|
125
|
+
? { develop_branch: data.project.git_develop_branch }
|
|
126
|
+
: {}),
|
|
127
|
+
worktree_required: true,
|
|
128
|
+
worktree_hint: 'CRITICAL: Create a git worktree before starting work. Run get_help("git") for instructions.',
|
|
129
|
+
};
|
|
633
130
|
}
|
|
634
131
|
|
|
635
|
-
//
|
|
636
|
-
|
|
132
|
+
// Add next action at end
|
|
133
|
+
if (data.next_task) {
|
|
134
|
+
result.next_action = `update_task(task_id: "${data.next_task.id}", status: "in_progress")`;
|
|
135
|
+
} else if (data.project) {
|
|
136
|
+
result.next_action = `start_fallback_activity(project_id: "${data.project.id}", activity: "code_review")`;
|
|
137
|
+
}
|
|
637
138
|
|
|
638
|
-
return { result
|
|
139
|
+
return { result };
|
|
639
140
|
};
|
|
640
141
|
|
|
641
142
|
export const heartbeat: Handler = async (args, ctx) => {
|
|
642
143
|
const { session_id } = args as { session_id?: string };
|
|
643
|
-
const {
|
|
144
|
+
const { session } = ctx;
|
|
644
145
|
const targetSession = session_id || session.currentSessionId;
|
|
645
146
|
|
|
646
147
|
if (!targetSession) {
|
|
@@ -651,33 +152,38 @@ export const heartbeat: Handler = async (args, ctx) => {
|
|
|
651
152
|
};
|
|
652
153
|
}
|
|
653
154
|
|
|
654
|
-
|
|
655
|
-
session_id: targetSession,
|
|
656
|
-
});
|
|
155
|
+
const apiClient = getApiClient();
|
|
657
156
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
667
|
-
|
|
157
|
+
// Send heartbeat
|
|
158
|
+
const heartbeatResponse = await apiClient.heartbeat(targetSession);
|
|
159
|
+
|
|
160
|
+
if (!heartbeatResponse.ok) {
|
|
161
|
+
return {
|
|
162
|
+
result: {
|
|
163
|
+
error: heartbeatResponse.error || 'Failed to send heartbeat',
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Sync token usage to session
|
|
169
|
+
await apiClient.syncSession(targetSession, {
|
|
170
|
+
total_tokens: session.tokenUsage.totalTokens,
|
|
171
|
+
token_breakdown: session.tokenUsage.byTool,
|
|
172
|
+
model_usage: session.tokenUsage.byModel,
|
|
173
|
+
});
|
|
668
174
|
|
|
669
175
|
return {
|
|
670
176
|
result: {
|
|
671
177
|
success: true,
|
|
672
178
|
session_id: targetSession,
|
|
673
|
-
timestamp: new Date().toISOString(),
|
|
179
|
+
timestamp: heartbeatResponse.data?.timestamp || new Date().toISOString(),
|
|
674
180
|
},
|
|
675
181
|
};
|
|
676
182
|
};
|
|
677
183
|
|
|
678
184
|
export const endWorkSession: Handler = async (args, ctx) => {
|
|
679
185
|
const { session_id } = args as { session_id?: string };
|
|
680
|
-
const {
|
|
186
|
+
const { session, updateSession } = ctx;
|
|
681
187
|
const targetSession = session_id || session.currentSessionId;
|
|
682
188
|
|
|
683
189
|
if (!targetSession) {
|
|
@@ -689,97 +195,44 @@ export const endWorkSession: Handler = async (args, ctx) => {
|
|
|
689
195
|
};
|
|
690
196
|
}
|
|
691
197
|
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
.
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const startedAt = sessionData?.started_at;
|
|
701
|
-
|
|
702
|
-
let tasksCompletedThisSession = 0;
|
|
703
|
-
let tasksAwaitingValidation = 0;
|
|
704
|
-
let tasksBeingReleased = 0;
|
|
705
|
-
|
|
706
|
-
if (projectId) {
|
|
707
|
-
const { count: completedCount } = await supabase
|
|
708
|
-
.from('tasks')
|
|
709
|
-
.select('id', { count: 'exact', head: true })
|
|
710
|
-
.eq('project_id', projectId)
|
|
711
|
-
.eq('status', 'completed')
|
|
712
|
-
.not('validated_at', 'is', null);
|
|
713
|
-
|
|
714
|
-
const { data: awaitingTasks } = await supabase
|
|
715
|
-
.from('tasks')
|
|
716
|
-
.select('id')
|
|
717
|
-
.eq('project_id', projectId)
|
|
718
|
-
.eq('status', 'completed')
|
|
719
|
-
.is('validated_at', null);
|
|
720
|
-
|
|
721
|
-
tasksAwaitingValidation = awaitingTasks?.length || 0;
|
|
722
|
-
|
|
723
|
-
const { count: releasingCount } = await supabase
|
|
724
|
-
.from('tasks')
|
|
725
|
-
.select('id', { count: 'exact', head: true })
|
|
726
|
-
.eq('working_agent_session_id', targetSession)
|
|
727
|
-
.eq('status', 'in_progress');
|
|
728
|
-
|
|
729
|
-
tasksBeingReleased = releasingCount || 0;
|
|
730
|
-
|
|
731
|
-
if (startedAt) {
|
|
732
|
-
const { count: sessionCompleted } = await supabase
|
|
733
|
-
.from('tasks')
|
|
734
|
-
.select('id', { count: 'exact', head: true })
|
|
735
|
-
.eq('project_id', projectId)
|
|
736
|
-
.eq('status', 'completed')
|
|
737
|
-
.gte('completed_at', startedAt);
|
|
738
|
-
|
|
739
|
-
tasksCompletedThisSession = sessionCompleted || 0;
|
|
740
|
-
}
|
|
741
|
-
}
|
|
198
|
+
const apiClient = getApiClient();
|
|
199
|
+
|
|
200
|
+
// Sync final token usage before ending
|
|
201
|
+
await apiClient.syncSession(targetSession, {
|
|
202
|
+
total_tokens: session.tokenUsage.totalTokens,
|
|
203
|
+
token_breakdown: session.tokenUsage.byTool,
|
|
204
|
+
model_usage: session.tokenUsage.byModel,
|
|
205
|
+
});
|
|
742
206
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
last_synced_at: new Date().toISOString(),
|
|
754
|
-
total_tokens: session.tokenUsage.totalTokens,
|
|
755
|
-
token_breakdown: session.tokenUsage.byTool,
|
|
756
|
-
model_usage: session.tokenUsage.byModel,
|
|
757
|
-
})
|
|
758
|
-
.eq('id', targetSession);
|
|
207
|
+
// End the session
|
|
208
|
+
const response = await apiClient.endSession(targetSession);
|
|
209
|
+
|
|
210
|
+
if (!response.ok) {
|
|
211
|
+
return {
|
|
212
|
+
result: {
|
|
213
|
+
error: response.error || 'Failed to end session',
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
759
217
|
|
|
760
218
|
const endedSessionId = targetSession;
|
|
761
219
|
|
|
220
|
+
// Clear local session state if this was the current session
|
|
762
221
|
if (session.currentSessionId === targetSession) {
|
|
763
222
|
updateSession({ currentSessionId: null });
|
|
764
223
|
}
|
|
765
224
|
|
|
766
|
-
const
|
|
767
|
-
if (tasksAwaitingValidation > 0) {
|
|
768
|
-
reminders.push(`${tasksAwaitingValidation} task(s) awaiting validation - consider reviewing before leaving`);
|
|
769
|
-
}
|
|
770
|
-
if (tasksBeingReleased > 0) {
|
|
771
|
-
reminders.push(`${tasksBeingReleased} in-progress task(s) released back to the queue`);
|
|
772
|
-
}
|
|
225
|
+
const data = response.data;
|
|
773
226
|
|
|
774
227
|
return {
|
|
775
228
|
result: {
|
|
776
229
|
success: true,
|
|
777
230
|
ended_session_id: endedSessionId,
|
|
778
231
|
session_summary: {
|
|
779
|
-
agent_name:
|
|
780
|
-
tasks_completed_this_session:
|
|
781
|
-
tasks_awaiting_validation:
|
|
782
|
-
tasks_released:
|
|
232
|
+
agent_name: data?.session_summary?.agent_name || 'Agent',
|
|
233
|
+
tasks_completed_this_session: data?.session_summary?.tasks_completed_this_session || 0,
|
|
234
|
+
tasks_awaiting_validation: data?.session_summary?.tasks_awaiting_validation || 0,
|
|
235
|
+
tasks_released: data?.session_summary?.tasks_released || 0,
|
|
783
236
|
token_usage: {
|
|
784
237
|
total_calls: session.tokenUsage.callCount,
|
|
785
238
|
total_tokens: session.tokenUsage.totalTokens,
|
|
@@ -788,7 +241,7 @@ export const endWorkSession: Handler = async (args, ctx) => {
|
|
|
788
241
|
: 0,
|
|
789
242
|
},
|
|
790
243
|
},
|
|
791
|
-
reminders: reminders
|
|
244
|
+
reminders: data?.reminders || ['Session ended cleanly. Good work!'],
|
|
792
245
|
},
|
|
793
246
|
};
|
|
794
247
|
};
|