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