@runtypelabs/sdk 1.7.2 → 1.7.3
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/index.cjs +7026 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5147 -0
- package/dist/index.d.ts +5146 -27
- package/dist/index.js +6955 -77
- package/dist/index.js.map +1 -1
- package/package.json +14 -7
- package/dist/batch-builder.d.ts +0 -106
- package/dist/batch-builder.d.ts.map +0 -1
- package/dist/batch-builder.js +0 -124
- package/dist/batch-builder.js.map +0 -1
- package/dist/batches-namespace.d.ts +0 -132
- package/dist/batches-namespace.d.ts.map +0 -1
- package/dist/batches-namespace.js +0 -128
- package/dist/batches-namespace.js.map +0 -1
- package/dist/case-types.d.ts +0 -42
- package/dist/case-types.d.ts.map +0 -1
- package/dist/case-types.js +0 -16
- package/dist/case-types.js.map +0 -1
- package/dist/client-token-types.d.ts +0 -143
- package/dist/client-token-types.d.ts.map +0 -1
- package/dist/client-token-types.js +0 -11
- package/dist/client-token-types.js.map +0 -1
- package/dist/client.d.ts +0 -135
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -522
- package/dist/client.js.map +0 -1
- package/dist/endpoints.d.ts +0 -1353
- package/dist/endpoints.d.ts.map +0 -1
- package/dist/endpoints.js +0 -2936
- package/dist/endpoints.js.map +0 -1
- package/dist/error-handling-types.d.ts +0 -71
- package/dist/error-handling-types.d.ts.map +0 -1
- package/dist/error-handling-types.js +0 -12
- package/dist/error-handling-types.js.map +0 -1
- package/dist/eval-builder.d.ts +0 -216
- package/dist/eval-builder.d.ts.map +0 -1
- package/dist/eval-builder.js +0 -225
- package/dist/eval-builder.js.map +0 -1
- package/dist/evals-namespace.d.ts +0 -205
- package/dist/evals-namespace.d.ts.map +0 -1
- package/dist/evals-namespace.js +0 -208
- package/dist/evals-namespace.js.map +0 -1
- package/dist/flow-builder.d.ts +0 -717
- package/dist/flow-builder.d.ts.map +0 -1
- package/dist/flow-builder.js +0 -592
- package/dist/flow-builder.js.map +0 -1
- package/dist/flow-result.d.ts +0 -117
- package/dist/flow-result.d.ts.map +0 -1
- package/dist/flow-result.js +0 -175
- package/dist/flow-result.js.map +0 -1
- package/dist/flows-namespace.d.ts +0 -442
- package/dist/flows-namespace.d.ts.map +0 -1
- package/dist/flows-namespace.js +0 -686
- package/dist/flows-namespace.js.map +0 -1
- package/dist/generated-tool-gate.d.ts +0 -75
- package/dist/generated-tool-gate.d.ts.map +0 -1
- package/dist/generated-tool-gate.js +0 -314
- package/dist/generated-tool-gate.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/prompts-namespace.d.ts +0 -237
- package/dist/prompts-namespace.d.ts.map +0 -1
- package/dist/prompts-namespace.js +0 -222
- package/dist/prompts-namespace.js.map +0 -1
- package/dist/runtype.d.ts +0 -232
- package/dist/runtype.d.ts.map +0 -1
- package/dist/runtype.js +0 -367
- package/dist/runtype.js.map +0 -1
- package/dist/stream-utils.d.ts +0 -58
- package/dist/stream-utils.d.ts.map +0 -1
- package/dist/stream-utils.js +0 -373
- package/dist/stream-utils.js.map +0 -1
- package/dist/transform.d.ts +0 -30
- package/dist/transform.d.ts.map +0 -1
- package/dist/transform.js +0 -196
- package/dist/transform.js.map +0 -1
- package/dist/types.d.ts +0 -717
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -7
- package/dist/types.js.map +0 -1
package/dist/endpoints.js
DELETED
|
@@ -1,2936 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* API endpoint handlers with automatic camelCase/snake_case transformation
|
|
4
|
-
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.AgentsEndpoint = exports.ClientTokensEndpoint = exports.EvalEndpoint = exports.ToolsEndpoint = exports.ContextTemplatesEndpoint = exports.FlowStepsEndpoint = exports.AnalyticsEndpoint = exports.UsersEndpoint = exports.ChatEndpoint = exports.DispatchEndpoint = exports.ModelConfigsEndpoint = exports.ApiKeysEndpoint = exports.RecordsEndpoint = exports.PromptsEndpoint = exports.FlowsEndpoint = void 0;
|
|
7
|
-
const generated_tool_gate_1 = require("./generated-tool-gate");
|
|
8
|
-
/**
|
|
9
|
-
* Flows endpoint handlers
|
|
10
|
-
*/
|
|
11
|
-
class FlowsEndpoint {
|
|
12
|
-
constructor(client) {
|
|
13
|
-
this.client = client;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* List all flows for the authenticated user
|
|
17
|
-
*/
|
|
18
|
-
async list(params) {
|
|
19
|
-
return this.client.get('/flows', params);
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Get a specific flow by ID
|
|
23
|
-
*/
|
|
24
|
-
async get(id) {
|
|
25
|
-
const response = await this.client.get(`/flows/${id}`);
|
|
26
|
-
return response;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Create a new flow
|
|
30
|
-
*/
|
|
31
|
-
async create(data) {
|
|
32
|
-
return this.client.post('/flows', data);
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Update an existing flow
|
|
36
|
-
*/
|
|
37
|
-
async update(id, data) {
|
|
38
|
-
return this.client.put(`/flows/${id}`, data);
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Delete a flow
|
|
42
|
-
*/
|
|
43
|
-
async delete(id) {
|
|
44
|
-
return this.client.delete(`/flows/${id}`);
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Run a flow on all records of a specific type
|
|
48
|
-
*/
|
|
49
|
-
async runOnRecordType(id, recordType) {
|
|
50
|
-
return this.client.post(`/flows/${id}/run-on-record-type`, {
|
|
51
|
-
recordType,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Publish flow (promote draft to published)
|
|
56
|
-
*/
|
|
57
|
-
async publish(id) {
|
|
58
|
-
return this.client.post(`/flows/${id}/publish`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
exports.FlowsEndpoint = FlowsEndpoint;
|
|
62
|
-
/**
|
|
63
|
-
* Prompts endpoint handlers
|
|
64
|
-
*/
|
|
65
|
-
class PromptsEndpoint {
|
|
66
|
-
constructor(client) {
|
|
67
|
-
this.client = client;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* List all prompts for the authenticated user
|
|
71
|
-
*/
|
|
72
|
-
async list(params) {
|
|
73
|
-
return this.client.get('/prompts', params);
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Get a specific prompt by ID
|
|
77
|
-
*/
|
|
78
|
-
async get(id) {
|
|
79
|
-
return this.client.get(`/prompts/${id}`);
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Create a new prompt
|
|
83
|
-
*/
|
|
84
|
-
async create(data) {
|
|
85
|
-
return this.client.post('/prompts', data);
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Update an existing prompt
|
|
89
|
-
*/
|
|
90
|
-
async update(id, data) {
|
|
91
|
-
return this.client.put(`/prompts/${id}`, data);
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Delete a prompt
|
|
95
|
-
*/
|
|
96
|
-
async delete(id) {
|
|
97
|
-
return this.client.delete(`/prompts/${id}`);
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Run a prompt on a specific record
|
|
101
|
-
*/
|
|
102
|
-
async runOnRecord(id, recordId) {
|
|
103
|
-
return this.client.post(`/prompts/${id}/run-on-record`, { recordId });
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Get flows using this prompt
|
|
107
|
-
*/
|
|
108
|
-
async getFlows(id) {
|
|
109
|
-
return this.client.get(`/prompts/${id}/flows`);
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Update flow attachments for a prompt
|
|
113
|
-
*/
|
|
114
|
-
async updateFlows(id, flowIds) {
|
|
115
|
-
return this.client.put(`/prompts/${id}/flows`, { flowIds });
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
exports.PromptsEndpoint = PromptsEndpoint;
|
|
119
|
-
/**
|
|
120
|
-
* Records endpoint handlers
|
|
121
|
-
*/
|
|
122
|
-
class RecordsEndpoint {
|
|
123
|
-
constructor(client) {
|
|
124
|
-
this.client = client;
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* List all records for the authenticated user
|
|
128
|
-
*/
|
|
129
|
-
async list(params) {
|
|
130
|
-
return this.client.get('/records', params);
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Get a specific record by ID
|
|
134
|
-
*/
|
|
135
|
-
async get(id) {
|
|
136
|
-
return this.client.get(`/records/${id}`);
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Create a new record
|
|
140
|
-
*/
|
|
141
|
-
async create(data) {
|
|
142
|
-
return this.client.post('/records', data);
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Update an existing record
|
|
146
|
-
*/
|
|
147
|
-
async update(id, data) {
|
|
148
|
-
return this.client.put(`/records/${id}`, data);
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Delete a record
|
|
152
|
-
*/
|
|
153
|
-
async delete(id) {
|
|
154
|
-
return this.client.delete(`/records/${id}`);
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Bulk delete multiple records
|
|
158
|
-
*/
|
|
159
|
-
async bulkDelete(ids) {
|
|
160
|
-
return this.client.post('/records/bulk-delete', { ids });
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Bulk edit multiple records
|
|
164
|
-
*/
|
|
165
|
-
async bulkEdit(data) {
|
|
166
|
-
return this.client.post('/records/bulk-edit', data);
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Get results for a record
|
|
170
|
-
*/
|
|
171
|
-
async getResults(id, params) {
|
|
172
|
-
return this.client.get(`/records/${id}/results`, params);
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Delete a specific result for a record
|
|
176
|
-
*/
|
|
177
|
-
async deleteResult(id, resultId) {
|
|
178
|
-
return this.client.delete(`/records/${id}/results`, { resultId });
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Upload CSV file to create multiple records
|
|
182
|
-
*/
|
|
183
|
-
async uploadCsv(file, params) {
|
|
184
|
-
const formData = new FormData();
|
|
185
|
-
formData.append('file', file);
|
|
186
|
-
if (params?.typeColumn) {
|
|
187
|
-
formData.append('typeColumn', params.typeColumn);
|
|
188
|
-
}
|
|
189
|
-
if (params?.nameColumn) {
|
|
190
|
-
formData.append('nameColumn', params.nameColumn);
|
|
191
|
-
}
|
|
192
|
-
return this.client.postFormData('/records/upload-csv', formData);
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Get record types (distinct values)
|
|
196
|
-
*/
|
|
197
|
-
async getTypes() {
|
|
198
|
-
return this.client.get('/records?distinct=types');
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Get example record by type and/or name
|
|
202
|
-
*/
|
|
203
|
-
async getExample(params) {
|
|
204
|
-
return this.client.get('/records', {
|
|
205
|
-
...params,
|
|
206
|
-
includeFields: true,
|
|
207
|
-
limit: 1,
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
exports.RecordsEndpoint = RecordsEndpoint;
|
|
212
|
-
/**
|
|
213
|
-
* API Keys endpoint handlers
|
|
214
|
-
*/
|
|
215
|
-
class ApiKeysEndpoint {
|
|
216
|
-
constructor(client) {
|
|
217
|
-
this.client = client;
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* List all API keys for the authenticated user
|
|
221
|
-
*/
|
|
222
|
-
async list() {
|
|
223
|
-
const response = await this.client.get('/api-keys');
|
|
224
|
-
return response.apiKeys;
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Get a specific API key by ID
|
|
228
|
-
*/
|
|
229
|
-
async get(id) {
|
|
230
|
-
return this.client.get(`/api-keys/${id}`);
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Create a new API key
|
|
234
|
-
*/
|
|
235
|
-
async create(data) {
|
|
236
|
-
return this.client.post('/api-keys', data);
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Update an existing API key
|
|
240
|
-
*/
|
|
241
|
-
async update(id, data) {
|
|
242
|
-
return this.client.put(`/api-keys/${id}`, data);
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Delete an API key
|
|
246
|
-
*/
|
|
247
|
-
async delete(id) {
|
|
248
|
-
return this.client.delete(`/api-keys/${id}`);
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Regenerate an API key
|
|
252
|
-
*/
|
|
253
|
-
async regenerate(id) {
|
|
254
|
-
return this.client.post(`/api-keys/${id}/regenerate`);
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Get API key analytics
|
|
258
|
-
*/
|
|
259
|
-
async getAnalytics(id) {
|
|
260
|
-
return this.client.get(`/api-keys/${id}/analytics`);
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Get usage logs for an API key
|
|
264
|
-
*/
|
|
265
|
-
async getUsage(id, params) {
|
|
266
|
-
return this.client.get(`/api-keys/${id}/usage`, params);
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Get API key analytics (all keys or specific key)
|
|
270
|
-
*/
|
|
271
|
-
async getAnalyticsEnhanced(apiKeyId, params) {
|
|
272
|
-
const endpoint = apiKeyId === 'all' ? '/api-keys/analytics' : `/api-keys/${apiKeyId}/analytics`;
|
|
273
|
-
return this.client.get(endpoint, params);
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Get API key usage logs (all keys or specific key)
|
|
277
|
-
*/
|
|
278
|
-
async getLogs(apiKeyId, params) {
|
|
279
|
-
const endpoint = apiKeyId === 'all' ? '/api-keys/logs' : `/api-keys/${apiKeyId}/logs`;
|
|
280
|
-
return this.client.get(endpoint, params);
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Get API key permission options
|
|
284
|
-
*/
|
|
285
|
-
async getPermissionOptions() {
|
|
286
|
-
return this.client.get('/api-keys/options');
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Get API key security logs
|
|
290
|
-
*/
|
|
291
|
-
async getSecurityLogs(id) {
|
|
292
|
-
return this.client.get(`/api-keys/${id}/security-logs`);
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Update API key security settings
|
|
296
|
-
*/
|
|
297
|
-
async updateSecurity(id, data) {
|
|
298
|
-
return this.client.put(`/api-keys/${id}/security`, data);
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Clear suspicious activity for API key
|
|
302
|
-
*/
|
|
303
|
-
async clearSuspiciousActivity(id) {
|
|
304
|
-
return this.client.post(`/api-keys/${id}/clear-suspicious`);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
exports.ApiKeysEndpoint = ApiKeysEndpoint;
|
|
308
|
-
/**
|
|
309
|
-
* Model Configurations endpoint handlers
|
|
310
|
-
*/
|
|
311
|
-
class ModelConfigsEndpoint {
|
|
312
|
-
constructor(client) {
|
|
313
|
-
this.client = client;
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Get available models catalog
|
|
317
|
-
*/
|
|
318
|
-
async getAvailable() {
|
|
319
|
-
const response = await this.client.get('/model-configs/available');
|
|
320
|
-
// Handle both wrapped and direct array responses
|
|
321
|
-
return Array.isArray(response) ? response : response.data || [];
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* Get search-capable models (all available, regardless of user configuration)
|
|
325
|
-
*/
|
|
326
|
-
async getSearchCapable() {
|
|
327
|
-
const response = await this.client.get('/model-configs/search-capable');
|
|
328
|
-
return response.data || [];
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* Get configured search-capable models (only user's configured and enabled models)
|
|
332
|
-
*/
|
|
333
|
-
async getSearchConfigured() {
|
|
334
|
-
const response = await this.client.get('/model-configs/search-configured');
|
|
335
|
-
return response.data || [];
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* List user's model configurations
|
|
339
|
-
*/
|
|
340
|
-
async list() {
|
|
341
|
-
const response = await this.client.get('/model-configs');
|
|
342
|
-
// Handle both wrapped and direct array responses
|
|
343
|
-
return Array.isArray(response) ? response : response.data || [];
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* Create a new model configuration
|
|
347
|
-
*/
|
|
348
|
-
async create(data) {
|
|
349
|
-
return this.client.post('/model-configs', data);
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* Update an existing model configuration
|
|
353
|
-
*/
|
|
354
|
-
async update(id, data) {
|
|
355
|
-
return this.client.put(`/model-configs/${id}`, data);
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* Delete a model configuration
|
|
359
|
-
*/
|
|
360
|
-
async delete(id) {
|
|
361
|
-
return this.client.delete(`/model-configs/${id}`);
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Enable/disable a model configuration
|
|
365
|
-
*/
|
|
366
|
-
async updateStatus(id, isEnabled) {
|
|
367
|
-
return this.client.patch(`/model-configs/${id}/status`, { isEnabled });
|
|
368
|
-
}
|
|
369
|
-
/**
|
|
370
|
-
* Set a model configuration as default
|
|
371
|
-
*/
|
|
372
|
-
async setDefault(id) {
|
|
373
|
-
return this.client.patch(`/model-configs/${id}/default`);
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* Get usage statistics
|
|
377
|
-
*/
|
|
378
|
-
async getUsage() {
|
|
379
|
-
return this.client.get('/model-configs/usage');
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
exports.ModelConfigsEndpoint = ModelConfigsEndpoint;
|
|
383
|
-
/**
|
|
384
|
-
* Dispatch endpoint handler for atomic record/flow creation and execution
|
|
385
|
-
*/
|
|
386
|
-
class DispatchEndpoint {
|
|
387
|
-
constructor(client) {
|
|
388
|
-
this.client = client;
|
|
389
|
-
}
|
|
390
|
-
/**
|
|
391
|
-
* Dispatch: create and/or execute flows on records atomically
|
|
392
|
-
*/
|
|
393
|
-
async execute(data) {
|
|
394
|
-
return this.client.post('/dispatch', data);
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* Dispatch with streaming response
|
|
398
|
-
*/
|
|
399
|
-
async executeStream(data) {
|
|
400
|
-
return this.client.requestStream('/dispatch', {
|
|
401
|
-
method: 'POST',
|
|
402
|
-
body: JSON.stringify(data),
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
/**
|
|
406
|
-
* Resume paused flow execution
|
|
407
|
-
* API expects snake_case field names
|
|
408
|
-
*
|
|
409
|
-
* @param data.execution_id - The execution ID to resume
|
|
410
|
-
* @param data.tool_outputs - Tool outputs to inject into the flow
|
|
411
|
-
* @param data.stream_response - Whether to stream the response
|
|
412
|
-
* @param data.messages - Optional messages to override the original dispatch messages
|
|
413
|
-
*/
|
|
414
|
-
async resume(data) {
|
|
415
|
-
if (data.streamResponse) {
|
|
416
|
-
return this.client.requestStream('/dispatch/resume', {
|
|
417
|
-
method: 'POST',
|
|
418
|
-
body: JSON.stringify(data),
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
return this.client.post('/dispatch/resume', data);
|
|
422
|
-
}
|
|
423
|
-
/**
|
|
424
|
-
* Evaluate a model-proposed runtime tool against a configurable allowlist policy.
|
|
425
|
-
* Useful for local `propose_runtime_tool` handlers before redispatch.
|
|
426
|
-
*/
|
|
427
|
-
gateGeneratedRuntimeToolProposal(proposal, options) {
|
|
428
|
-
return (0, generated_tool_gate_1.evaluateGeneratedRuntimeToolProposal)(proposal, options);
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* Build standardized local-tool output for a generated tool proposal.
|
|
432
|
-
* Returns `{ approved, reason, violations, tool? }`.
|
|
433
|
-
*/
|
|
434
|
-
buildGeneratedRuntimeToolGateOutput(proposal, options) {
|
|
435
|
-
return (0, generated_tool_gate_1.buildGeneratedRuntimeToolGateOutput)(proposal, options);
|
|
436
|
-
}
|
|
437
|
-
/**
|
|
438
|
-
* Attach approved runtime tools to a prompt step in a redispatch request.
|
|
439
|
-
* Returns a new request object and does not mutate the original.
|
|
440
|
-
*/
|
|
441
|
-
attachApprovedRuntimeTools(request, runtimeTools, options) {
|
|
442
|
-
return (0, generated_tool_gate_1.attachRuntimeToolsToDispatchRequest)(request, runtimeTools, options);
|
|
443
|
-
}
|
|
444
|
-
/**
|
|
445
|
-
* Validate a generated runtime tool proposal and attach it to the redispatch
|
|
446
|
-
* request if approved, in one call.
|
|
447
|
-
*/
|
|
448
|
-
applyGeneratedRuntimeToolProposal(request, proposal, options) {
|
|
449
|
-
return (0, generated_tool_gate_1.applyGeneratedRuntimeToolProposalToDispatchRequest)(request, proposal, options);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
exports.DispatchEndpoint = DispatchEndpoint;
|
|
453
|
-
/**
|
|
454
|
-
* Chat endpoint handler
|
|
455
|
-
*/
|
|
456
|
-
class ChatEndpoint {
|
|
457
|
-
constructor(client) {
|
|
458
|
-
this.client = client;
|
|
459
|
-
}
|
|
460
|
-
/**
|
|
461
|
-
* Send a chat message
|
|
462
|
-
*/
|
|
463
|
-
async send(message) {
|
|
464
|
-
return this.client.post('/chat', { message });
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Send a chat message with streaming response
|
|
468
|
-
*/
|
|
469
|
-
async sendStream(message, options) {
|
|
470
|
-
return this.client.requestStream('/chat', {
|
|
471
|
-
method: 'POST',
|
|
472
|
-
body: JSON.stringify({ message, ...options }),
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
exports.ChatEndpoint = ChatEndpoint;
|
|
477
|
-
/**
|
|
478
|
-
* Users endpoint handlers
|
|
479
|
-
*/
|
|
480
|
-
class UsersEndpoint {
|
|
481
|
-
constructor(client) {
|
|
482
|
-
this.client = client;
|
|
483
|
-
}
|
|
484
|
-
/**
|
|
485
|
-
* Get current user profile
|
|
486
|
-
*/
|
|
487
|
-
async getProfile() {
|
|
488
|
-
return this.client.get('/users/profile');
|
|
489
|
-
}
|
|
490
|
-
/**
|
|
491
|
-
* Update user profile
|
|
492
|
-
*/
|
|
493
|
-
async updateProfile(data) {
|
|
494
|
-
return this.client.put('/users/profile', data);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
exports.UsersEndpoint = UsersEndpoint;
|
|
498
|
-
/**
|
|
499
|
-
* Analytics endpoint handlers
|
|
500
|
-
*/
|
|
501
|
-
class AnalyticsEndpoint {
|
|
502
|
-
constructor(client) {
|
|
503
|
-
this.client = client;
|
|
504
|
-
}
|
|
505
|
-
/**
|
|
506
|
-
* Get analytics stats
|
|
507
|
-
*/
|
|
508
|
-
async getStats(params) {
|
|
509
|
-
return this.client.get('/analytics/stats', params);
|
|
510
|
-
}
|
|
511
|
-
/**
|
|
512
|
-
* Get analytics executions
|
|
513
|
-
*/
|
|
514
|
-
async getExecutions(params) {
|
|
515
|
-
return this.client.get('/analytics/record-results', params);
|
|
516
|
-
}
|
|
517
|
-
/**
|
|
518
|
-
* Get all record results (same as executions)
|
|
519
|
-
*/
|
|
520
|
-
async getAllRecordResults(params) {
|
|
521
|
-
return this.client.get('/analytics/record-results', params);
|
|
522
|
-
}
|
|
523
|
-
/**
|
|
524
|
-
* Get model usage analytics
|
|
525
|
-
*/
|
|
526
|
-
async getModelUsage(params) {
|
|
527
|
-
return this.client.get('/model-usage', params);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
exports.AnalyticsEndpoint = AnalyticsEndpoint;
|
|
531
|
-
/**
|
|
532
|
-
* Flow Steps endpoint handlers
|
|
533
|
-
*
|
|
534
|
-
* @deprecated Flow steps are now consolidated into the flows endpoint.
|
|
535
|
-
* Use `flows.get(id)` to get a flow with embedded `flowSteps`.
|
|
536
|
-
* Use `flows.update(id, { flow_steps: [...] })` to update flow steps.
|
|
537
|
-
*/
|
|
538
|
-
class FlowStepsEndpoint {
|
|
539
|
-
constructor(client) {
|
|
540
|
-
this.client = client;
|
|
541
|
-
}
|
|
542
|
-
/**
|
|
543
|
-
* Get flow steps for a flow
|
|
544
|
-
* @deprecated Use `flows.get(flowId)` instead - flow steps are now embedded in the flow response as `flowSteps`
|
|
545
|
-
*/
|
|
546
|
-
async getByFlow(flowId) {
|
|
547
|
-
console.warn('[Runtype SDK] flowSteps.getByFlow() is deprecated. Use flows.get(flowId) instead - flow steps are now embedded in the flow response as flowSteps.');
|
|
548
|
-
return this.client.get(`/flow-steps/flow/${flowId}`);
|
|
549
|
-
}
|
|
550
|
-
/**
|
|
551
|
-
* Get a specific flow step
|
|
552
|
-
* @deprecated Use `flows.get(flowId)` and find the step in `flowSteps` array
|
|
553
|
-
*/
|
|
554
|
-
async get(id) {
|
|
555
|
-
console.warn('[Runtype SDK] flowSteps.get() is deprecated. Use flows.get(flowId) and find the step in flowSteps array.');
|
|
556
|
-
return this.client.get(`/flow-steps/${id}`);
|
|
557
|
-
}
|
|
558
|
-
/**
|
|
559
|
-
* Create a flow step
|
|
560
|
-
* @deprecated Use `flows.update(flowId, { flow_steps: [...] })` to add steps
|
|
561
|
-
*/
|
|
562
|
-
async create(data) {
|
|
563
|
-
console.warn('[Runtype SDK] flowSteps.create() is deprecated. Use flows.update(flowId, { flow_steps: [...] }) to add steps.');
|
|
564
|
-
return this.client.post('/flow-steps', data);
|
|
565
|
-
}
|
|
566
|
-
/**
|
|
567
|
-
* Update a flow step
|
|
568
|
-
* @deprecated Use `flows.update(flowId, { flow_steps: [...] })` to update steps
|
|
569
|
-
*/
|
|
570
|
-
async update(id, data) {
|
|
571
|
-
console.warn('[Runtype SDK] flowSteps.update() is deprecated. Use flows.update(flowId, { flow_steps: [...] }) to update steps.');
|
|
572
|
-
return this.client.put(`/flow-steps/${id}`, data);
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* Delete a flow step
|
|
576
|
-
* @deprecated Use `flows.update(flowId, { flow_steps: [...] })` with the step removed
|
|
577
|
-
*/
|
|
578
|
-
async delete(id) {
|
|
579
|
-
console.warn('[Runtype SDK] flowSteps.delete() is deprecated. Use flows.update(flowId, { flow_steps: [...] }) with the step removed.');
|
|
580
|
-
return this.client.delete(`/flow-steps/${id}`);
|
|
581
|
-
}
|
|
582
|
-
/**
|
|
583
|
-
* Reorder flow steps
|
|
584
|
-
* @deprecated Use `flows.update(flowId, { flow_steps: [...] })` with updated order values
|
|
585
|
-
*/
|
|
586
|
-
async reorder(flowId, stepOrders) {
|
|
587
|
-
console.warn('[Runtype SDK] flowSteps.reorder() is deprecated. Use flows.update(flowId, { flow_steps: [...] }) with updated order values.');
|
|
588
|
-
return this.client.post('/flow-steps/reorder', {
|
|
589
|
-
flowId,
|
|
590
|
-
stepOrders,
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
exports.FlowStepsEndpoint = FlowStepsEndpoint;
|
|
595
|
-
/**
|
|
596
|
-
* Context Templates endpoint handlers
|
|
597
|
-
*/
|
|
598
|
-
class ContextTemplatesEndpoint {
|
|
599
|
-
constructor(client) {
|
|
600
|
-
this.client = client;
|
|
601
|
-
}
|
|
602
|
-
/**
|
|
603
|
-
* List context templates
|
|
604
|
-
*/
|
|
605
|
-
async list(params) {
|
|
606
|
-
return this.client.get('/context-templates', params);
|
|
607
|
-
}
|
|
608
|
-
/**
|
|
609
|
-
* Get a specific context template
|
|
610
|
-
*/
|
|
611
|
-
async get(id) {
|
|
612
|
-
return this.client.get(`/context-templates/${id}`);
|
|
613
|
-
}
|
|
614
|
-
/**
|
|
615
|
-
* Create a context template
|
|
616
|
-
*/
|
|
617
|
-
async create(data) {
|
|
618
|
-
return this.client.post('/context-templates', data);
|
|
619
|
-
}
|
|
620
|
-
/**
|
|
621
|
-
* Update a context template
|
|
622
|
-
*/
|
|
623
|
-
async update(id, data) {
|
|
624
|
-
return this.client.put(`/context-templates/${id}`, data);
|
|
625
|
-
}
|
|
626
|
-
/**
|
|
627
|
-
* Delete a context template
|
|
628
|
-
*/
|
|
629
|
-
async delete(id) {
|
|
630
|
-
return this.client.delete(`/context-templates/${id}`);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
exports.ContextTemplatesEndpoint = ContextTemplatesEndpoint;
|
|
634
|
-
/**
|
|
635
|
-
* Tools endpoint handlers
|
|
636
|
-
*/
|
|
637
|
-
class ToolsEndpoint {
|
|
638
|
-
constructor(client) {
|
|
639
|
-
this.client = client;
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* List all tools for the authenticated user
|
|
643
|
-
*/
|
|
644
|
-
async list(params) {
|
|
645
|
-
return this.client.get('/tools', params);
|
|
646
|
-
}
|
|
647
|
-
/**
|
|
648
|
-
* Get a specific tool by ID
|
|
649
|
-
*/
|
|
650
|
-
async get(id) {
|
|
651
|
-
return this.client.get(`/tools/${id}`);
|
|
652
|
-
}
|
|
653
|
-
/**
|
|
654
|
-
* Create a new tool
|
|
655
|
-
*/
|
|
656
|
-
async create(data) {
|
|
657
|
-
return this.client.post('/tools', data);
|
|
658
|
-
}
|
|
659
|
-
/**
|
|
660
|
-
* Update an existing tool
|
|
661
|
-
*/
|
|
662
|
-
async update(id, data) {
|
|
663
|
-
return this.client.put(`/tools/${id}`, data);
|
|
664
|
-
}
|
|
665
|
-
/**
|
|
666
|
-
* Delete a tool
|
|
667
|
-
*/
|
|
668
|
-
async delete(id) {
|
|
669
|
-
return this.client.delete(`/tools/${id}`);
|
|
670
|
-
}
|
|
671
|
-
/**
|
|
672
|
-
* Execute a tool
|
|
673
|
-
*/
|
|
674
|
-
async execute(id, data) {
|
|
675
|
-
return this.client.post(`/tools/${id}/execute`, data);
|
|
676
|
-
}
|
|
677
|
-
/**
|
|
678
|
-
* Execute a tool with streaming response
|
|
679
|
-
*/
|
|
680
|
-
async executeStream(id, data) {
|
|
681
|
-
return this.client.requestStream(`/tools/${id}/execute`, {
|
|
682
|
-
method: 'POST',
|
|
683
|
-
body: JSON.stringify(data),
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
/**
|
|
687
|
-
* Test a tool (same as execute but for testing purposes)
|
|
688
|
-
*/
|
|
689
|
-
async test(id, data) {
|
|
690
|
-
return this.client.post(`/tools/${id}/test`, data);
|
|
691
|
-
}
|
|
692
|
-
/**
|
|
693
|
-
* Get tool executions for a specific tool
|
|
694
|
-
*/
|
|
695
|
-
async getExecutions(toolId, params) {
|
|
696
|
-
return this.client.get(`/tools/${toolId}/executions`, params);
|
|
697
|
-
}
|
|
698
|
-
/**
|
|
699
|
-
* Get AI SDK compatible tool schemas
|
|
700
|
-
*/
|
|
701
|
-
async getSchemas(params) {
|
|
702
|
-
return this.client.get('/tools/schema', params);
|
|
703
|
-
}
|
|
704
|
-
/**
|
|
705
|
-
* Get available tools for prompts
|
|
706
|
-
*/
|
|
707
|
-
async getAvailable() {
|
|
708
|
-
const response = await this.client.get('/tools', {
|
|
709
|
-
isActive: true,
|
|
710
|
-
limit: 200,
|
|
711
|
-
});
|
|
712
|
-
const payload = response;
|
|
713
|
-
if (Array.isArray(payload?.data)) {
|
|
714
|
-
return payload.data;
|
|
715
|
-
}
|
|
716
|
-
if (Array.isArray(payload)) {
|
|
717
|
-
return payload;
|
|
718
|
-
}
|
|
719
|
-
return [];
|
|
720
|
-
}
|
|
721
|
-
/**
|
|
722
|
-
* Convert a flow to a tool
|
|
723
|
-
*/
|
|
724
|
-
async convertFromFlow(flowId, data) {
|
|
725
|
-
return this.client.post(`/flows/${flowId}/convert-to-tool`, data);
|
|
726
|
-
}
|
|
727
|
-
/**
|
|
728
|
-
* Get all built-in tools
|
|
729
|
-
*/
|
|
730
|
-
async getBuiltInTools() {
|
|
731
|
-
return this.client.get('/tools/builtin');
|
|
732
|
-
}
|
|
733
|
-
/**
|
|
734
|
-
* Get built-in tools compatible with a specific model and provider
|
|
735
|
-
*/
|
|
736
|
-
async getCompatibleBuiltInTools(model, provider) {
|
|
737
|
-
return this.client.get('/tools/builtin/compatible', {
|
|
738
|
-
model,
|
|
739
|
-
provider,
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
/**
|
|
743
|
-
* Get parameter schema for a specific built-in tool
|
|
744
|
-
*/
|
|
745
|
-
async getBuiltInToolSchema(toolId) {
|
|
746
|
-
return this.client.get(`/tools/builtin/${toolId}/schema`);
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
exports.ToolsEndpoint = ToolsEndpoint;
|
|
750
|
-
/**
|
|
751
|
-
* Eval endpoint handlers
|
|
752
|
-
*/
|
|
753
|
-
class EvalEndpoint {
|
|
754
|
-
constructor(client) {
|
|
755
|
-
this.client = client;
|
|
756
|
-
}
|
|
757
|
-
/**
|
|
758
|
-
* Run virtual eval with streaming response
|
|
759
|
-
* Executes multiple eval configs simultaneously and streams results via multiplexed SSE
|
|
760
|
-
*/
|
|
761
|
-
async runVirtualEval(data) {
|
|
762
|
-
return this.client.requestStream('/eval/stream', {
|
|
763
|
-
method: 'POST',
|
|
764
|
-
body: JSON.stringify(data),
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
exports.EvalEndpoint = EvalEndpoint;
|
|
769
|
-
/**
|
|
770
|
-
* Client Tokens endpoint handlers
|
|
771
|
-
*
|
|
772
|
-
* Manages client tokens for secure browser-to-API communication.
|
|
773
|
-
* Client tokens enable embedding chat widgets in external applications.
|
|
774
|
-
*/
|
|
775
|
-
class ClientTokensEndpoint {
|
|
776
|
-
constructor(client) {
|
|
777
|
-
this.client = client;
|
|
778
|
-
}
|
|
779
|
-
/**
|
|
780
|
-
* List all client tokens for the authenticated user
|
|
781
|
-
*/
|
|
782
|
-
async list() {
|
|
783
|
-
const response = await this.client.get('/client-tokens');
|
|
784
|
-
return response.clientTokens;
|
|
785
|
-
}
|
|
786
|
-
/**
|
|
787
|
-
* Get a specific client token by ID
|
|
788
|
-
*/
|
|
789
|
-
async get(id) {
|
|
790
|
-
return this.client.get(`/client-tokens/${id}`);
|
|
791
|
-
}
|
|
792
|
-
/**
|
|
793
|
-
* Create a new client token
|
|
794
|
-
*
|
|
795
|
-
* @returns The created token with the plain token value (shown only once)
|
|
796
|
-
*/
|
|
797
|
-
async create(data) {
|
|
798
|
-
return this.client.post('/client-tokens', data);
|
|
799
|
-
}
|
|
800
|
-
/**
|
|
801
|
-
* Update an existing client token
|
|
802
|
-
*/
|
|
803
|
-
async update(id, data) {
|
|
804
|
-
return this.client.put(`/client-tokens/${id}`, data);
|
|
805
|
-
}
|
|
806
|
-
/**
|
|
807
|
-
* Delete a client token
|
|
808
|
-
*/
|
|
809
|
-
async delete(id) {
|
|
810
|
-
return this.client.delete(`/client-tokens/${id}`);
|
|
811
|
-
}
|
|
812
|
-
/**
|
|
813
|
-
* Regenerate a client token's value
|
|
814
|
-
*
|
|
815
|
-
* @returns The updated token with the new plain token value (shown only once)
|
|
816
|
-
*/
|
|
817
|
-
async regenerate(id) {
|
|
818
|
-
return this.client.post(`/client-tokens/${id}/regenerate`);
|
|
819
|
-
}
|
|
820
|
-
/**
|
|
821
|
-
* Get the plain token value for a client token
|
|
822
|
-
*
|
|
823
|
-
* Note: Only works if the token was just created or regenerated in the same session
|
|
824
|
-
*/
|
|
825
|
-
async getToken(id) {
|
|
826
|
-
return this.client.get(`/client-tokens/${id}/token`);
|
|
827
|
-
}
|
|
828
|
-
/**
|
|
829
|
-
* List conversations for a client token
|
|
830
|
-
*/
|
|
831
|
-
async listConversations(id, params) {
|
|
832
|
-
return this.client.get(`/client-tokens/${id}/conversations`, params);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
exports.ClientTokensEndpoint = ClientTokensEndpoint;
|
|
836
|
-
/**
|
|
837
|
-
* Parse SSE stream chunks into individual events with event type support
|
|
838
|
-
*/
|
|
839
|
-
function parseSSEChunkWithEventType(chunk, buffer) {
|
|
840
|
-
buffer += chunk;
|
|
841
|
-
// Split on double newlines to get complete events
|
|
842
|
-
const parts = buffer.split('\n\n');
|
|
843
|
-
const remainingBuffer = parts.pop() || '';
|
|
844
|
-
const events = [];
|
|
845
|
-
for (const part of parts) {
|
|
846
|
-
if (!part.trim())
|
|
847
|
-
continue;
|
|
848
|
-
const lines = part.split('\n');
|
|
849
|
-
let eventType = null;
|
|
850
|
-
let dataStr = null;
|
|
851
|
-
for (const line of lines) {
|
|
852
|
-
if (line.startsWith('event: ')) {
|
|
853
|
-
eventType = line.slice(7).trim();
|
|
854
|
-
}
|
|
855
|
-
else if (line.startsWith('data: ')) {
|
|
856
|
-
dataStr = line.slice(6).trim();
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
if (dataStr && dataStr !== '[DONE]') {
|
|
860
|
-
try {
|
|
861
|
-
const data = JSON.parse(dataStr);
|
|
862
|
-
events.push({ eventType, data });
|
|
863
|
-
}
|
|
864
|
-
catch {
|
|
865
|
-
// Invalid JSON, skip
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
return { events, remainingBuffer };
|
|
870
|
-
}
|
|
871
|
-
/**
|
|
872
|
-
* Dispatch a parsed agent event to the appropriate callback
|
|
873
|
-
*/
|
|
874
|
-
function dispatchAgentEvent(event, callbacks) {
|
|
875
|
-
const { eventType, data } = event;
|
|
876
|
-
const typedData = data;
|
|
877
|
-
// If no event type, try to get it from the data
|
|
878
|
-
const type = eventType || typedData?.type;
|
|
879
|
-
// @snake-case-ok-start: Agent event type identifiers use snake_case to match flow events
|
|
880
|
-
switch (type) {
|
|
881
|
-
case 'agent_start':
|
|
882
|
-
callbacks.onAgentStart?.(typedData);
|
|
883
|
-
break;
|
|
884
|
-
case 'agent_iteration_start':
|
|
885
|
-
callbacks.onIterationStart?.(typedData);
|
|
886
|
-
break;
|
|
887
|
-
case 'agent_turn_start':
|
|
888
|
-
callbacks.onTurnStart?.(typedData);
|
|
889
|
-
break;
|
|
890
|
-
case 'agent_turn_delta':
|
|
891
|
-
callbacks.onTurnDelta?.(typedData);
|
|
892
|
-
break;
|
|
893
|
-
case 'agent_turn_complete':
|
|
894
|
-
callbacks.onTurnComplete?.(typedData);
|
|
895
|
-
break;
|
|
896
|
-
case 'agent_tool_start':
|
|
897
|
-
callbacks.onToolStart?.(typedData);
|
|
898
|
-
break;
|
|
899
|
-
case 'agent_tool_delta':
|
|
900
|
-
callbacks.onToolDelta?.(typedData);
|
|
901
|
-
break;
|
|
902
|
-
case 'agent_tool_complete':
|
|
903
|
-
callbacks.onToolComplete?.(typedData);
|
|
904
|
-
break;
|
|
905
|
-
case 'agent_iteration_complete':
|
|
906
|
-
callbacks.onIterationComplete?.(typedData);
|
|
907
|
-
break;
|
|
908
|
-
case 'agent_reflection':
|
|
909
|
-
callbacks.onReflection?.(typedData);
|
|
910
|
-
break;
|
|
911
|
-
case 'agent_complete':
|
|
912
|
-
callbacks.onAgentComplete?.(typedData);
|
|
913
|
-
break;
|
|
914
|
-
case 'agent_error':
|
|
915
|
-
callbacks.onError?.(typedData);
|
|
916
|
-
break;
|
|
917
|
-
case 'agent_await':
|
|
918
|
-
callbacks.onAgentPaused?.(typedData);
|
|
919
|
-
break;
|
|
920
|
-
case 'agent_ping':
|
|
921
|
-
callbacks.onPing?.(typedData);
|
|
922
|
-
break;
|
|
923
|
-
default:
|
|
924
|
-
callbacks.onUnknownEvent?.(event);
|
|
925
|
-
}
|
|
926
|
-
// @snake-case-ok-end
|
|
927
|
-
}
|
|
928
|
-
/**
|
|
929
|
-
* Process agent stream with callbacks
|
|
930
|
-
*/
|
|
931
|
-
async function processAgentStream(body, callbacks) {
|
|
932
|
-
if (!body)
|
|
933
|
-
return;
|
|
934
|
-
const reader = body.getReader();
|
|
935
|
-
const decoder = new TextDecoder();
|
|
936
|
-
let buffer = '';
|
|
937
|
-
try {
|
|
938
|
-
while (true) {
|
|
939
|
-
const { done, value } = await reader.read();
|
|
940
|
-
if (done) {
|
|
941
|
-
// Process any remaining buffer
|
|
942
|
-
if (buffer.trim()) {
|
|
943
|
-
const { events } = parseSSEChunkWithEventType(buffer + '\n\n', '');
|
|
944
|
-
for (const event of events) {
|
|
945
|
-
dispatchAgentEvent(event, callbacks);
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
break;
|
|
949
|
-
}
|
|
950
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
951
|
-
const { events, remainingBuffer } = parseSSEChunkWithEventType(chunk, buffer);
|
|
952
|
-
buffer = remainingBuffer;
|
|
953
|
-
for (const event of events) {
|
|
954
|
-
dispatchAgentEvent(event, callbacks);
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
finally {
|
|
959
|
-
reader.releaseLock();
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
const GENERATED_RUNTIME_TOOL_PROPOSAL_SCHEMA = {
|
|
963
|
-
type: 'object',
|
|
964
|
-
properties: {
|
|
965
|
-
name: {
|
|
966
|
-
type: 'string',
|
|
967
|
-
description: 'Tool name. Use letters/numbers/underscore only.',
|
|
968
|
-
},
|
|
969
|
-
description: {
|
|
970
|
-
type: 'string',
|
|
971
|
-
description: 'Clear description of what the generated tool does.',
|
|
972
|
-
},
|
|
973
|
-
toolType: {
|
|
974
|
-
type: 'string',
|
|
975
|
-
enum: ['custom'],
|
|
976
|
-
description: 'Must be "custom" for generated code execution tools.',
|
|
977
|
-
},
|
|
978
|
-
parametersSchema: {
|
|
979
|
-
type: 'object',
|
|
980
|
-
description: 'JSON schema for tool call arguments.',
|
|
981
|
-
},
|
|
982
|
-
config: {
|
|
983
|
-
type: 'object',
|
|
984
|
-
description: 'Runtime tool config including code, sandboxProvider, language, and timeout.',
|
|
985
|
-
},
|
|
986
|
-
reason: {
|
|
987
|
-
type: 'string',
|
|
988
|
-
description: 'Why this tool is needed.',
|
|
989
|
-
},
|
|
990
|
-
},
|
|
991
|
-
required: ['name', 'description', 'toolType', 'parametersSchema', 'config'],
|
|
992
|
-
};
|
|
993
|
-
function appendRuntimeToolsToAgentRequest(request, runtimeTools) {
|
|
994
|
-
const existing = request.tools?.runtimeTools || [];
|
|
995
|
-
const existingNames = new Set(existing.map((tool) => tool.name));
|
|
996
|
-
const converted = runtimeTools
|
|
997
|
-
.filter((tool) => !existingNames.has(tool.name))
|
|
998
|
-
.map((tool) => ({
|
|
999
|
-
name: tool.name,
|
|
1000
|
-
description: tool.description,
|
|
1001
|
-
toolType: tool.toolType,
|
|
1002
|
-
parametersSchema: tool.parametersSchema,
|
|
1003
|
-
...(tool.config ? { config: tool.config } : {}),
|
|
1004
|
-
}));
|
|
1005
|
-
return {
|
|
1006
|
-
...request,
|
|
1007
|
-
tools: {
|
|
1008
|
-
...request.tools,
|
|
1009
|
-
runtimeTools: [...existing, ...converted],
|
|
1010
|
-
},
|
|
1011
|
-
};
|
|
1012
|
-
}
|
|
1013
|
-
/**
|
|
1014
|
-
* Agents endpoint handlers
|
|
1015
|
-
*/
|
|
1016
|
-
class AgentsEndpoint {
|
|
1017
|
-
constructor(client) {
|
|
1018
|
-
this.client = client;
|
|
1019
|
-
}
|
|
1020
|
-
/**
|
|
1021
|
-
* List all agents for the authenticated user
|
|
1022
|
-
*/
|
|
1023
|
-
async list(params) {
|
|
1024
|
-
return this.client.get('/agents', params);
|
|
1025
|
-
}
|
|
1026
|
-
/**
|
|
1027
|
-
* Get a specific agent by ID
|
|
1028
|
-
*/
|
|
1029
|
-
async get(id) {
|
|
1030
|
-
return this.client.get(`/agents/${id}`);
|
|
1031
|
-
}
|
|
1032
|
-
/**
|
|
1033
|
-
* Create a new agent
|
|
1034
|
-
*/
|
|
1035
|
-
async create(data) {
|
|
1036
|
-
return this.client.post('/agents', data);
|
|
1037
|
-
}
|
|
1038
|
-
/**
|
|
1039
|
-
* Update an existing agent
|
|
1040
|
-
*/
|
|
1041
|
-
async update(id, data) {
|
|
1042
|
-
return this.client.put(`/agents/${id}`, data);
|
|
1043
|
-
}
|
|
1044
|
-
/**
|
|
1045
|
-
* Delete an agent
|
|
1046
|
-
*/
|
|
1047
|
-
async delete(id) {
|
|
1048
|
-
return this.client.delete(`/agents/${id}`);
|
|
1049
|
-
}
|
|
1050
|
-
/**
|
|
1051
|
-
* Evaluate a model-proposed runtime tool against a configurable allowlist policy.
|
|
1052
|
-
* Useful for local `propose_runtime_tool` handlers before follow-up execution.
|
|
1053
|
-
*/
|
|
1054
|
-
gateGeneratedRuntimeToolProposal(proposal, options) {
|
|
1055
|
-
return (0, generated_tool_gate_1.evaluateGeneratedRuntimeToolProposal)(proposal, options);
|
|
1056
|
-
}
|
|
1057
|
-
/**
|
|
1058
|
-
* Build standardized local-tool output for a generated tool proposal.
|
|
1059
|
-
* Returns `{ approved, reason, violations, tool? }`.
|
|
1060
|
-
*/
|
|
1061
|
-
buildGeneratedRuntimeToolGateOutput(proposal, options) {
|
|
1062
|
-
return (0, generated_tool_gate_1.buildGeneratedRuntimeToolGateOutput)(proposal, options);
|
|
1063
|
-
}
|
|
1064
|
-
/**
|
|
1065
|
-
* Create a local tool definition that validates model-proposed runtime tools.
|
|
1066
|
-
* Plug this into `executeWithLocalTools()` under a name like `propose_runtime_tool`.
|
|
1067
|
-
*/
|
|
1068
|
-
createGeneratedRuntimeToolGateLocalTool(options) {
|
|
1069
|
-
const { description, ...gateOptions } = options || {};
|
|
1070
|
-
return {
|
|
1071
|
-
description: description ||
|
|
1072
|
-
'Validate a generated runtime custom tool and return { approved, reason, violations, tool? }',
|
|
1073
|
-
parametersSchema: GENERATED_RUNTIME_TOOL_PROPOSAL_SCHEMA,
|
|
1074
|
-
execute: async (args) => (0, generated_tool_gate_1.buildGeneratedRuntimeToolGateOutput)(args, gateOptions),
|
|
1075
|
-
};
|
|
1076
|
-
}
|
|
1077
|
-
/**
|
|
1078
|
-
* Attach approved runtime tools to an agent execute request.
|
|
1079
|
-
* Returns a new request object and does not mutate the original.
|
|
1080
|
-
*/
|
|
1081
|
-
attachApprovedRuntimeTools(request, runtimeTools) {
|
|
1082
|
-
return appendRuntimeToolsToAgentRequest(request, runtimeTools);
|
|
1083
|
-
}
|
|
1084
|
-
/**
|
|
1085
|
-
* Validate a generated runtime tool proposal and append it to an agent execute
|
|
1086
|
-
* request if approved, in one call.
|
|
1087
|
-
*/
|
|
1088
|
-
applyGeneratedRuntimeToolProposal(request, proposal, options) {
|
|
1089
|
-
const decision = (0, generated_tool_gate_1.evaluateGeneratedRuntimeToolProposal)(proposal, options);
|
|
1090
|
-
if (!decision.approved || !decision.tool) {
|
|
1091
|
-
return { decision, request };
|
|
1092
|
-
}
|
|
1093
|
-
return {
|
|
1094
|
-
decision,
|
|
1095
|
-
request: appendRuntimeToolsToAgentRequest(request, [decision.tool]),
|
|
1096
|
-
};
|
|
1097
|
-
}
|
|
1098
|
-
/**
|
|
1099
|
-
* Execute an agent (non-streaming)
|
|
1100
|
-
*/
|
|
1101
|
-
async execute(id, data) {
|
|
1102
|
-
return this.client.post(`/agents/${id}/execute`, {
|
|
1103
|
-
...data,
|
|
1104
|
-
streamResponse: false,
|
|
1105
|
-
});
|
|
1106
|
-
}
|
|
1107
|
-
/**
|
|
1108
|
-
* Execute an agent with streaming response
|
|
1109
|
-
*
|
|
1110
|
-
* Returns a Response object with SSE stream.
|
|
1111
|
-
* Use `executeWithCallbacks` for easier handling.
|
|
1112
|
-
*
|
|
1113
|
-
* @example
|
|
1114
|
-
* ```typescript
|
|
1115
|
-
* const response = await client.agents.executeStream('agt_123', {
|
|
1116
|
-
* messages: [{ role: 'user', content: 'Write me a poem' }],
|
|
1117
|
-
* debugMode: true,
|
|
1118
|
-
* })
|
|
1119
|
-
*
|
|
1120
|
-
* // Process the stream manually
|
|
1121
|
-
* const reader = response.body?.getReader()
|
|
1122
|
-
* // ...
|
|
1123
|
-
* ```
|
|
1124
|
-
*/
|
|
1125
|
-
async executeStream(id, data) {
|
|
1126
|
-
return this.client.requestStream(`/agents/${id}/execute`, {
|
|
1127
|
-
method: 'POST',
|
|
1128
|
-
body: JSON.stringify({
|
|
1129
|
-
...data,
|
|
1130
|
-
streamResponse: true,
|
|
1131
|
-
}),
|
|
1132
|
-
});
|
|
1133
|
-
}
|
|
1134
|
-
/**
|
|
1135
|
-
* Execute an agent with streaming and callbacks
|
|
1136
|
-
*
|
|
1137
|
-
* Processes the SSE stream and calls the appropriate callbacks for each event type.
|
|
1138
|
-
* This is the recommended way to handle agent streaming.
|
|
1139
|
-
*
|
|
1140
|
-
* @example
|
|
1141
|
-
* ```typescript
|
|
1142
|
-
* let content = ''
|
|
1143
|
-
*
|
|
1144
|
-
* const result = await client.agents.executeWithCallbacks('agt_123', {
|
|
1145
|
-
* messages: [{ role: 'user', content: 'Write me a poem' }],
|
|
1146
|
-
* debugMode: true,
|
|
1147
|
-
* }, {
|
|
1148
|
-
* onTurnDelta: (event) => {
|
|
1149
|
-
* content += event.delta
|
|
1150
|
-
* process.stdout.write(event.delta)
|
|
1151
|
-
* },
|
|
1152
|
-
* onIterationComplete: (event) => {
|
|
1153
|
-
* console.log(`\nIteration ${event.iteration} complete`)
|
|
1154
|
-
* },
|
|
1155
|
-
* onAgentComplete: (event) => {
|
|
1156
|
-
* console.log(`\nAgent finished: ${event.stopReason}`)
|
|
1157
|
-
* },
|
|
1158
|
-
* onError: (event) => {
|
|
1159
|
-
* console.error(`Error: ${event.error.message}`)
|
|
1160
|
-
* },
|
|
1161
|
-
* })
|
|
1162
|
-
*
|
|
1163
|
-
* console.log('Final result:', result)
|
|
1164
|
-
* ```
|
|
1165
|
-
*/
|
|
1166
|
-
async executeWithCallbacks(id, data, callbacks) {
|
|
1167
|
-
const response = await this.executeStream(id, data);
|
|
1168
|
-
if (!response.ok) {
|
|
1169
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
1170
|
-
throw new Error(error.error || `HTTP ${response.status}`);
|
|
1171
|
-
}
|
|
1172
|
-
let completeEvent = null;
|
|
1173
|
-
// Accumulate turn delta text so finalOutput is non-empty even when the
|
|
1174
|
-
// agent_complete event carries no output (e.g. model ended after tool calls)
|
|
1175
|
-
let accumulatedOutput = '';
|
|
1176
|
-
await processAgentStream(response.body, {
|
|
1177
|
-
...callbacks,
|
|
1178
|
-
onTurnDelta: (event) => {
|
|
1179
|
-
if (event.contentType === 'text') {
|
|
1180
|
-
accumulatedOutput += event.delta;
|
|
1181
|
-
}
|
|
1182
|
-
callbacks.onTurnDelta?.(event);
|
|
1183
|
-
},
|
|
1184
|
-
onAgentComplete: (event) => {
|
|
1185
|
-
if (!event.finalOutput && accumulatedOutput) {
|
|
1186
|
-
event.finalOutput = accumulatedOutput;
|
|
1187
|
-
}
|
|
1188
|
-
completeEvent = event;
|
|
1189
|
-
callbacks.onAgentComplete?.(event);
|
|
1190
|
-
},
|
|
1191
|
-
});
|
|
1192
|
-
return completeEvent;
|
|
1193
|
-
}
|
|
1194
|
-
/**
|
|
1195
|
-
* Execute an agent with local tool support (pause/resume loop)
|
|
1196
|
-
*
|
|
1197
|
-
* When the agent hits a tool with `toolType: 'local'`, the server emits
|
|
1198
|
-
* `agent_await`. This method automatically executes the local tool and
|
|
1199
|
-
* resumes execution, repeating until the agent completes.
|
|
1200
|
-
*
|
|
1201
|
-
* @example
|
|
1202
|
-
* ```typescript
|
|
1203
|
-
* const result = await client.agents.executeWithLocalTools('agt_123', {
|
|
1204
|
-
* messages: [{ role: 'user', content: 'Create a file called hello.txt' }],
|
|
1205
|
-
* }, {
|
|
1206
|
-
* write_file: async ({ path, content }) => {
|
|
1207
|
-
* fs.writeFileSync(path, content)
|
|
1208
|
-
* return 'ok'
|
|
1209
|
-
* },
|
|
1210
|
-
* })
|
|
1211
|
-
* ```
|
|
1212
|
-
*/
|
|
1213
|
-
async executeWithLocalTools(id, data, localTools, callbacks, options) {
|
|
1214
|
-
// Build runtime tool definitions from local tool schemas and inject into request
|
|
1215
|
-
const runtimeTools = Object.entries(localTools).map(([name, def]) => ({
|
|
1216
|
-
name,
|
|
1217
|
-
description: def.description,
|
|
1218
|
-
toolType: 'local',
|
|
1219
|
-
parametersSchema: def.parametersSchema,
|
|
1220
|
-
}));
|
|
1221
|
-
const requestData = {
|
|
1222
|
-
...data,
|
|
1223
|
-
tools: {
|
|
1224
|
-
...data.tools,
|
|
1225
|
-
runtimeTools: [...(data.tools?.runtimeTools || []), ...runtimeTools],
|
|
1226
|
-
},
|
|
1227
|
-
};
|
|
1228
|
-
const response = await this.executeStream(id, requestData);
|
|
1229
|
-
if (!response.ok) {
|
|
1230
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
1231
|
-
throw new Error(error.error || `HTTP ${response.status}`);
|
|
1232
|
-
}
|
|
1233
|
-
let currentBody = response.body;
|
|
1234
|
-
// Accumulate text output across all streams (initial + resume cycles)
|
|
1235
|
-
// so finalOutput is non-empty even when the last resume stream has no text
|
|
1236
|
-
let accumulatedOutput = '';
|
|
1237
|
-
let pauseCount = 0;
|
|
1238
|
-
let discoveryPauseCount = 0;
|
|
1239
|
-
let consecutiveDiscoveryPauseCount = 0;
|
|
1240
|
-
const toolNameCounts = {};
|
|
1241
|
-
let recentActionKeys = [];
|
|
1242
|
-
while (true) {
|
|
1243
|
-
let pausedEvent = null;
|
|
1244
|
-
let completeEvent = null;
|
|
1245
|
-
await processAgentStream(currentBody, {
|
|
1246
|
-
...callbacks,
|
|
1247
|
-
onTurnDelta: (event) => {
|
|
1248
|
-
if (event.contentType === 'text') {
|
|
1249
|
-
accumulatedOutput += event.delta;
|
|
1250
|
-
}
|
|
1251
|
-
callbacks?.onTurnDelta?.(event);
|
|
1252
|
-
},
|
|
1253
|
-
onAgentPaused: (event) => {
|
|
1254
|
-
pausedEvent = event;
|
|
1255
|
-
callbacks?.onAgentPaused?.(event);
|
|
1256
|
-
},
|
|
1257
|
-
onAgentComplete: (event) => {
|
|
1258
|
-
// Supplement finalOutput with accumulated turn deltas when the
|
|
1259
|
-
// agent_complete event itself carries no output (common when the
|
|
1260
|
-
// model's last action was a tool call rather than text output)
|
|
1261
|
-
if (!event.finalOutput && accumulatedOutput) {
|
|
1262
|
-
event.finalOutput = accumulatedOutput;
|
|
1263
|
-
}
|
|
1264
|
-
completeEvent = event;
|
|
1265
|
-
callbacks?.onAgentComplete?.(event);
|
|
1266
|
-
},
|
|
1267
|
-
});
|
|
1268
|
-
if (completeEvent)
|
|
1269
|
-
return completeEvent;
|
|
1270
|
-
if (pausedEvent) {
|
|
1271
|
-
const { toolName, parameters, executionId } = pausedEvent;
|
|
1272
|
-
const toolDef = localTools[toolName];
|
|
1273
|
-
if (!toolDef) {
|
|
1274
|
-
throw new Error(`Local tool "${toolName}" required but not provided`);
|
|
1275
|
-
}
|
|
1276
|
-
// Recursively unwrap stringified parameters — the server pipeline may
|
|
1277
|
-
// double-serialize: object → JSON string → JSON string
|
|
1278
|
-
let parsedParams = {};
|
|
1279
|
-
let current = parameters;
|
|
1280
|
-
for (let i = 0; i < 3; i++) {
|
|
1281
|
-
if (typeof current === 'string') {
|
|
1282
|
-
try {
|
|
1283
|
-
current = JSON.parse(current);
|
|
1284
|
-
}
|
|
1285
|
-
catch {
|
|
1286
|
-
console.warn(`[local-tools] Failed to parse parameters (attempt ${i + 1}):`, typeof current, String(current).slice(0, 200));
|
|
1287
|
-
break;
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
else {
|
|
1291
|
-
break;
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
if (current && typeof current === 'object' && !Array.isArray(current)) {
|
|
1295
|
-
parsedParams = current;
|
|
1296
|
-
}
|
|
1297
|
-
else {
|
|
1298
|
-
console.warn('[local-tools] Parameters could not be resolved to an object:', typeof current, String(current).slice(0, 200));
|
|
1299
|
-
}
|
|
1300
|
-
let toolResult;
|
|
1301
|
-
try {
|
|
1302
|
-
toolResult = await toolDef.execute(parsedParams);
|
|
1303
|
-
}
|
|
1304
|
-
catch (err) {
|
|
1305
|
-
// Return the error as a tool result so the agent can recover
|
|
1306
|
-
toolResult = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
1307
|
-
}
|
|
1308
|
-
pauseCount += 1;
|
|
1309
|
-
const toolNameCount = (toolNameCounts[toolName] || 0) + 1;
|
|
1310
|
-
toolNameCounts[toolName] = toolNameCount;
|
|
1311
|
-
const discoveryTool = this.isDiscoveryLocalTool(toolName);
|
|
1312
|
-
if (discoveryTool) {
|
|
1313
|
-
discoveryPauseCount += 1;
|
|
1314
|
-
consecutiveDiscoveryPauseCount += 1;
|
|
1315
|
-
}
|
|
1316
|
-
else {
|
|
1317
|
-
consecutiveDiscoveryPauseCount = 0;
|
|
1318
|
-
}
|
|
1319
|
-
const actionKey = this.buildLocalToolActionKey(toolName, parsedParams);
|
|
1320
|
-
recentActionKeys = [...recentActionKeys, actionKey].slice(-12);
|
|
1321
|
-
const actionKeyCount = recentActionKeys.filter((candidateActionKey) => candidateActionKey === actionKey).length;
|
|
1322
|
-
const forcedCompleteEvent = options?.onLocalToolResult?.({
|
|
1323
|
-
executionId,
|
|
1324
|
-
pauseCount,
|
|
1325
|
-
discoveryPauseCount,
|
|
1326
|
-
consecutiveDiscoveryPauseCount,
|
|
1327
|
-
toolName,
|
|
1328
|
-
toolNameCount,
|
|
1329
|
-
parameters: parsedParams,
|
|
1330
|
-
toolResult,
|
|
1331
|
-
accumulatedOutput,
|
|
1332
|
-
actionKey,
|
|
1333
|
-
actionKeyCount,
|
|
1334
|
-
recentActionKeys,
|
|
1335
|
-
});
|
|
1336
|
-
if (forcedCompleteEvent) {
|
|
1337
|
-
if (!forcedCompleteEvent.finalOutput && accumulatedOutput) {
|
|
1338
|
-
forcedCompleteEvent.finalOutput = accumulatedOutput;
|
|
1339
|
-
}
|
|
1340
|
-
callbacks?.onAgentComplete?.(forcedCompleteEvent);
|
|
1341
|
-
return forcedCompleteEvent;
|
|
1342
|
-
}
|
|
1343
|
-
// Resume via agent resume endpoint
|
|
1344
|
-
const resumeResponse = await this.client.requestStream(`/agents/${id}/resume`, {
|
|
1345
|
-
method: 'POST',
|
|
1346
|
-
body: JSON.stringify({
|
|
1347
|
-
executionId,
|
|
1348
|
-
toolOutputs: { [toolName]: toolResult },
|
|
1349
|
-
streamResponse: true,
|
|
1350
|
-
debugMode: data.debugMode,
|
|
1351
|
-
}),
|
|
1352
|
-
});
|
|
1353
|
-
if (!resumeResponse.ok) {
|
|
1354
|
-
const error = await resumeResponse.json().catch(() => ({ error: 'Unknown error' }));
|
|
1355
|
-
throw new Error(error.error || `HTTP ${resumeResponse.status}`);
|
|
1356
|
-
}
|
|
1357
|
-
currentBody = resumeResponse.body;
|
|
1358
|
-
continue;
|
|
1359
|
-
}
|
|
1360
|
-
// Stream ended without complete or paused
|
|
1361
|
-
return null;
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
// ─── Long-Task Agent Execution ───────────────────────────────────────
|
|
1365
|
-
createEmptyToolTrace() {
|
|
1366
|
-
return {
|
|
1367
|
-
entries: [],
|
|
1368
|
-
discoveryPerformed: false,
|
|
1369
|
-
attemptedWrite: false,
|
|
1370
|
-
wroteFiles: false,
|
|
1371
|
-
executionFileWritten: false,
|
|
1372
|
-
readFiles: false,
|
|
1373
|
-
readPaths: [],
|
|
1374
|
-
actionKeys: [],
|
|
1375
|
-
candidatePaths: [],
|
|
1376
|
-
planWritten: false,
|
|
1377
|
-
bestCandidateReadFailed: false,
|
|
1378
|
-
bestCandidateWritten: false,
|
|
1379
|
-
bestCandidateVerified: false,
|
|
1380
|
-
verificationAttempted: false,
|
|
1381
|
-
verificationPassed: false,
|
|
1382
|
-
localToolLoopGuardTriggered: false,
|
|
1383
|
-
};
|
|
1384
|
-
}
|
|
1385
|
-
isDiscoveryLocalTool(toolName) {
|
|
1386
|
-
return ['tree_directory', 'search_repo', 'glob_files', 'list_directory', 'read_file'].includes(toolName);
|
|
1387
|
-
}
|
|
1388
|
-
buildLocalToolActionKey(toolName, parameters) {
|
|
1389
|
-
const pathValue = typeof parameters.path === 'string' ? this.normalizeCandidatePath(parameters.path) : '';
|
|
1390
|
-
const queryValue = typeof parameters.query === 'string'
|
|
1391
|
-
? parameters.query.trim()
|
|
1392
|
-
: typeof parameters.q === 'string'
|
|
1393
|
-
? parameters.q.trim()
|
|
1394
|
-
: '';
|
|
1395
|
-
const patternValue = typeof parameters.pattern === 'string' ? parameters.pattern.trim() : '';
|
|
1396
|
-
const commandValue = typeof parameters.command === 'string' ? parameters.command.trim() : '';
|
|
1397
|
-
const descriptor = pathValue || queryValue || patternValue || commandValue;
|
|
1398
|
-
if (descriptor) {
|
|
1399
|
-
return `${toolName}:${descriptor.slice(0, 160)}`;
|
|
1400
|
-
}
|
|
1401
|
-
const fallback = this.summarizeUnknownForTrace(parameters, 160);
|
|
1402
|
-
return fallback ? `${toolName}:${fallback}` : toolName;
|
|
1403
|
-
}
|
|
1404
|
-
buildProspectiveStateForSessionTrace(state, trace) {
|
|
1405
|
-
const candidatePaths = Array.from(new Set([...(state.candidatePaths || []), ...trace.candidatePaths])).slice(-20);
|
|
1406
|
-
const recentReadPaths = Array.from(new Set([...(state.recentReadPaths || []), ...trace.readPaths])).slice(-20);
|
|
1407
|
-
return {
|
|
1408
|
-
...state,
|
|
1409
|
-
...(trace.bestCandidatePath
|
|
1410
|
-
? {
|
|
1411
|
-
bestCandidatePath: trace.bestCandidatePath,
|
|
1412
|
-
bestCandidateReason: trace.bestCandidateReason,
|
|
1413
|
-
}
|
|
1414
|
-
: {}),
|
|
1415
|
-
candidatePaths,
|
|
1416
|
-
recentReadPaths,
|
|
1417
|
-
planWritten: state.planWritten || trace.planWritten,
|
|
1418
|
-
};
|
|
1419
|
-
}
|
|
1420
|
-
buildForcedLocalToolTurnCompleteEvent(state, snapshot, reason) {
|
|
1421
|
-
const finalOutput = [
|
|
1422
|
-
snapshot.accumulatedOutput.trim(),
|
|
1423
|
-
`Local tool loop guard ended this ${state.workflowPhase || 'research'} turn: ${reason}`,
|
|
1424
|
-
snapshot.recentActionKeys.length > 0
|
|
1425
|
-
? `Recent local tool actions: ${snapshot.recentActionKeys.slice(-5).join(' | ')}`
|
|
1426
|
-
: '',
|
|
1427
|
-
]
|
|
1428
|
-
.filter(Boolean)
|
|
1429
|
-
.join('\n\n');
|
|
1430
|
-
return {
|
|
1431
|
-
type: 'agent_complete',
|
|
1432
|
-
executionId: snapshot.executionId,
|
|
1433
|
-
seq: 0,
|
|
1434
|
-
agentId: state.agentId,
|
|
1435
|
-
success: true,
|
|
1436
|
-
iterations: 1,
|
|
1437
|
-
stopReason: 'end_turn',
|
|
1438
|
-
completedAt: new Date().toISOString(),
|
|
1439
|
-
totalCost: 0,
|
|
1440
|
-
finalOutput,
|
|
1441
|
-
duration: 0,
|
|
1442
|
-
};
|
|
1443
|
-
}
|
|
1444
|
-
createLocalToolLoopGuard(state, trace) {
|
|
1445
|
-
return (snapshot) => {
|
|
1446
|
-
const repeatedAction = snapshot.actionKeyCount >= 4;
|
|
1447
|
-
const heavyDiscoveryLoop = snapshot.discoveryPauseCount >= 24;
|
|
1448
|
-
const prospectiveState = this.buildProspectiveStateForSessionTrace(state, trace);
|
|
1449
|
-
const sufficientResearch = state.workflowPhase === 'research' && this.hasSufficientResearchEvidence(prospectiveState);
|
|
1450
|
-
let reason;
|
|
1451
|
-
if (state.workflowPhase === 'research') {
|
|
1452
|
-
if (sufficientResearch && snapshot.discoveryPauseCount >= 12) {
|
|
1453
|
-
reason =
|
|
1454
|
-
'research evidence is already sufficient, but this execution kept issuing discovery tools instead of ending the turn';
|
|
1455
|
-
}
|
|
1456
|
-
else if (repeatedAction) {
|
|
1457
|
-
reason = `the same discovery action repeated ${snapshot.actionKeyCount} times in one session`;
|
|
1458
|
-
}
|
|
1459
|
-
else if (snapshot.consecutiveDiscoveryPauseCount >= 18 || heavyDiscoveryLoop) {
|
|
1460
|
-
reason =
|
|
1461
|
-
'this session exceeded the discovery-tool budget without ending the turn';
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
else if (state.workflowPhase === 'planning' &&
|
|
1465
|
-
!trace.planWritten &&
|
|
1466
|
-
snapshot.consecutiveDiscoveryPauseCount >= 18) {
|
|
1467
|
-
reason = 'planning is looping on discovery instead of writing the plan and ending the turn';
|
|
1468
|
-
}
|
|
1469
|
-
else if (state.workflowPhase === 'execution' &&
|
|
1470
|
-
!trace.executionFileWritten &&
|
|
1471
|
-
snapshot.consecutiveDiscoveryPauseCount >= 18) {
|
|
1472
|
-
reason = 'execution is looping on discovery instead of editing repo files and ending the turn';
|
|
1473
|
-
}
|
|
1474
|
-
if (!reason) {
|
|
1475
|
-
return undefined;
|
|
1476
|
-
}
|
|
1477
|
-
trace.localToolLoopGuardTriggered = true;
|
|
1478
|
-
trace.forcedTurnEndReason = reason;
|
|
1479
|
-
this.pushToolTraceEntry(trace, `local-tool loop guard forced end_turn -> ${reason}`);
|
|
1480
|
-
return this.buildForcedLocalToolTurnCompleteEvent(state, snapshot, reason);
|
|
1481
|
-
};
|
|
1482
|
-
}
|
|
1483
|
-
pushToolTraceEntry(trace, entry) {
|
|
1484
|
-
const trimmed = entry.trim();
|
|
1485
|
-
if (!trimmed)
|
|
1486
|
-
return;
|
|
1487
|
-
if (trace.entries[trace.entries.length - 1] === trimmed)
|
|
1488
|
-
return;
|
|
1489
|
-
trace.entries.push(trimmed);
|
|
1490
|
-
if (trace.entries.length > 12) {
|
|
1491
|
-
trace.entries = trace.entries.slice(-12);
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
isPreservationSensitiveTask(state) {
|
|
1495
|
-
const bestCandidatePath = state.bestCandidatePath || '';
|
|
1496
|
-
if (/\.(html|tsx|jsx|css|scss|sass)$/i.test(bestCandidatePath)) {
|
|
1497
|
-
return true;
|
|
1498
|
-
}
|
|
1499
|
-
const prompt = (state.originalMessage || '').toLowerCase();
|
|
1500
|
-
return [
|
|
1501
|
-
'ux',
|
|
1502
|
-
'ui',
|
|
1503
|
-
'design',
|
|
1504
|
-
'frontend',
|
|
1505
|
-
'front-end',
|
|
1506
|
-
'theme',
|
|
1507
|
-
'editor',
|
|
1508
|
-
'layout',
|
|
1509
|
-
'style',
|
|
1510
|
-
'accessibility',
|
|
1511
|
-
'visual',
|
|
1512
|
-
].some((keyword) => prompt.includes(keyword));
|
|
1513
|
-
}
|
|
1514
|
-
getLikelySupportingCandidatePaths(bestCandidatePath, candidatePaths) {
|
|
1515
|
-
if (!bestCandidatePath || !candidatePaths || candidatePaths.length === 0)
|
|
1516
|
-
return [];
|
|
1517
|
-
const normalizedBestCandidatePath = this.normalizeCandidatePath(bestCandidatePath);
|
|
1518
|
-
const bestCandidateSegments = normalizedBestCandidatePath.split('/').filter(Boolean);
|
|
1519
|
-
const relatedRoot = bestCandidateSegments.length >= 2
|
|
1520
|
-
? `${bestCandidateSegments[0]}/${bestCandidateSegments[1]}/`
|
|
1521
|
-
: bestCandidateSegments.length === 1
|
|
1522
|
-
? `${bestCandidateSegments[0]}/`
|
|
1523
|
-
: '';
|
|
1524
|
-
const bestCandidateDir = normalizedBestCandidatePath.includes('/')
|
|
1525
|
-
? `${normalizedBestCandidatePath.slice(0, normalizedBestCandidatePath.lastIndexOf('/'))}/`
|
|
1526
|
-
: '';
|
|
1527
|
-
return candidatePaths
|
|
1528
|
-
.map((candidatePath) => this.normalizeCandidatePath(candidatePath))
|
|
1529
|
-
.filter((candidatePath) => candidatePath &&
|
|
1530
|
-
candidatePath !== normalizedBestCandidatePath &&
|
|
1531
|
-
!this.isMarathonArtifactPath(candidatePath) &&
|
|
1532
|
-
((bestCandidateDir && candidatePath.startsWith(bestCandidateDir)) ||
|
|
1533
|
-
(relatedRoot && candidatePath.startsWith(relatedRoot))));
|
|
1534
|
-
}
|
|
1535
|
-
hasSufficientResearchEvidence(state) {
|
|
1536
|
-
if (!state.bestCandidatePath)
|
|
1537
|
-
return false;
|
|
1538
|
-
const normalizedBestCandidatePath = this.normalizeCandidatePath(state.bestCandidatePath);
|
|
1539
|
-
const normalizedRecentReadPaths = (state.recentReadPaths || []).map((readPath) => this.normalizeCandidatePath(readPath));
|
|
1540
|
-
const readBestCandidate = normalizedRecentReadPaths.includes(normalizedBestCandidatePath);
|
|
1541
|
-
if (!readBestCandidate) {
|
|
1542
|
-
return false;
|
|
1543
|
-
}
|
|
1544
|
-
if (!this.isPreservationSensitiveTask(state)) {
|
|
1545
|
-
return true;
|
|
1546
|
-
}
|
|
1547
|
-
const supportingCandidatePaths = this.getLikelySupportingCandidatePaths(state.bestCandidatePath, state.candidatePaths);
|
|
1548
|
-
if (supportingCandidatePaths.length === 0) {
|
|
1549
|
-
return true;
|
|
1550
|
-
}
|
|
1551
|
-
return normalizedRecentReadPaths.some((readPath) => readPath !== normalizedBestCandidatePath && supportingCandidatePaths.includes(readPath));
|
|
1552
|
-
}
|
|
1553
|
-
buildEffectiveSessionOutput(modelOutput, toolTraceSummary) {
|
|
1554
|
-
return [toolTraceSummary.trim(), modelOutput.trim()].filter(Boolean).join('\n\n');
|
|
1555
|
-
}
|
|
1556
|
-
canAcceptTaskCompletion(output, state, sessionTrace) {
|
|
1557
|
-
if (!this.detectTaskCompletion(output)) {
|
|
1558
|
-
return false;
|
|
1559
|
-
}
|
|
1560
|
-
if (state.workflowPhase !== 'execution') {
|
|
1561
|
-
return true;
|
|
1562
|
-
}
|
|
1563
|
-
if (!state.bestCandidatePath) {
|
|
1564
|
-
return true;
|
|
1565
|
-
}
|
|
1566
|
-
const verificationSatisfied = !state.verificationRequired ||
|
|
1567
|
-
Boolean(state.lastVerificationPassed || sessionTrace.verificationPassed);
|
|
1568
|
-
return (Boolean(state.planWritten) &&
|
|
1569
|
-
Boolean(state.bestCandidateVerified || sessionTrace.bestCandidateVerified) &&
|
|
1570
|
-
verificationSatisfied);
|
|
1571
|
-
}
|
|
1572
|
-
summarizeUnknownForTrace(value, maxLength = 180) {
|
|
1573
|
-
const text = typeof value === 'string'
|
|
1574
|
-
? value
|
|
1575
|
-
: value === undefined
|
|
1576
|
-
? ''
|
|
1577
|
-
: JSON.stringify(value);
|
|
1578
|
-
return text.replace(/\s+/g, ' ').trim().slice(0, maxLength);
|
|
1579
|
-
}
|
|
1580
|
-
summarizeTextBlockForTrace(value, maxLines = 4) {
|
|
1581
|
-
const text = typeof value === 'string'
|
|
1582
|
-
? value
|
|
1583
|
-
: value === undefined
|
|
1584
|
-
? ''
|
|
1585
|
-
: JSON.stringify(value);
|
|
1586
|
-
if (!text)
|
|
1587
|
-
return '';
|
|
1588
|
-
return text
|
|
1589
|
-
.split('\n')
|
|
1590
|
-
.map((line) => line.trim())
|
|
1591
|
-
.filter(Boolean)
|
|
1592
|
-
.slice(0, maxLines)
|
|
1593
|
-
.join(' | ')
|
|
1594
|
-
.slice(0, 240);
|
|
1595
|
-
}
|
|
1596
|
-
parseVerificationResult(result) {
|
|
1597
|
-
if (typeof result !== 'string')
|
|
1598
|
-
return undefined;
|
|
1599
|
-
try {
|
|
1600
|
-
const parsed = JSON.parse(result);
|
|
1601
|
-
if (typeof parsed.success !== 'boolean')
|
|
1602
|
-
return undefined;
|
|
1603
|
-
return {
|
|
1604
|
-
success: parsed.success,
|
|
1605
|
-
...(typeof parsed.command === 'string' ? { command: parsed.command } : {}),
|
|
1606
|
-
...(typeof parsed.output === 'string' ? { output: parsed.output } : {}),
|
|
1607
|
-
...(typeof parsed.error === 'string' ? { error: parsed.error } : {}),
|
|
1608
|
-
};
|
|
1609
|
-
}
|
|
1610
|
-
catch {
|
|
1611
|
-
return undefined;
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
normalizeCandidatePath(candidatePath) {
|
|
1615
|
-
return candidatePath.trim().replace(/\\/g, '/').replace(/^\.?\//, '').replace(/\/+/g, '/');
|
|
1616
|
-
}
|
|
1617
|
-
dedupeNormalizedCandidatePaths(paths) {
|
|
1618
|
-
return Array.from(new Set((paths || [])
|
|
1619
|
-
.map((candidatePath) => this.normalizeCandidatePath(candidatePath))
|
|
1620
|
-
.filter((candidatePath) => {
|
|
1621
|
-
if (!candidatePath)
|
|
1622
|
-
return false;
|
|
1623
|
-
return !this.isMarathonArtifactPath(candidatePath);
|
|
1624
|
-
})));
|
|
1625
|
-
}
|
|
1626
|
-
isMarathonArtifactPath(candidatePath) {
|
|
1627
|
-
const normalized = this.normalizeCandidatePath(candidatePath).toLowerCase();
|
|
1628
|
-
return normalized === '.runtype' || normalized.startsWith('.runtype/');
|
|
1629
|
-
}
|
|
1630
|
-
isDiscoveryToolName(toolName) {
|
|
1631
|
-
return (toolName === 'search_repo' ||
|
|
1632
|
-
toolName === 'glob_files' ||
|
|
1633
|
-
toolName === 'tree_directory' ||
|
|
1634
|
-
toolName === 'list_directory');
|
|
1635
|
-
}
|
|
1636
|
-
sanitizeTaskSlug(taskName) {
|
|
1637
|
-
return taskName
|
|
1638
|
-
.toLowerCase()
|
|
1639
|
-
.replace(/[^a-z0-9_-]+/g, '-')
|
|
1640
|
-
.replace(/^-+|-+$/g, '')
|
|
1641
|
-
.slice(0, 80);
|
|
1642
|
-
}
|
|
1643
|
-
getDefaultPlanPath(taskName) {
|
|
1644
|
-
const slug = this.sanitizeTaskSlug(taskName || 'task');
|
|
1645
|
-
return `.runtype/marathons/${slug}/plan.md`;
|
|
1646
|
-
}
|
|
1647
|
-
dirnameOfCandidatePath(candidatePath) {
|
|
1648
|
-
const normalized = this.normalizeCandidatePath(candidatePath);
|
|
1649
|
-
const index = normalized.lastIndexOf('/');
|
|
1650
|
-
return index >= 0 ? normalized.slice(0, index) : '';
|
|
1651
|
-
}
|
|
1652
|
-
joinCandidatePath(baseDir, nextPath) {
|
|
1653
|
-
const normalizedNext = nextPath.replace(/\\/g, '/').trim();
|
|
1654
|
-
if (!normalizedNext)
|
|
1655
|
-
return '';
|
|
1656
|
-
if (normalizedNext.startsWith('/')) {
|
|
1657
|
-
return this.normalizeCandidatePath(`${baseDir}/${normalizedNext.slice(1)}`);
|
|
1658
|
-
}
|
|
1659
|
-
if (normalizedNext.startsWith('./')) {
|
|
1660
|
-
return this.normalizeCandidatePath(`${baseDir}/${normalizedNext.slice(2)}`);
|
|
1661
|
-
}
|
|
1662
|
-
return this.normalizeCandidatePath(baseDir ? `${baseDir}/${normalizedNext}` : normalizedNext);
|
|
1663
|
-
}
|
|
1664
|
-
scoreCandidatePath(candidatePath) {
|
|
1665
|
-
const normalized = this.normalizeCandidatePath(candidatePath).toLowerCase();
|
|
1666
|
-
let score = 0;
|
|
1667
|
-
if (normalized.endsWith('/theme.html') || normalized.endsWith('theme.html'))
|
|
1668
|
-
score += 80;
|
|
1669
|
-
if (normalized.includes('agent'))
|
|
1670
|
-
score += 30;
|
|
1671
|
-
if (normalized.includes('editor'))
|
|
1672
|
-
score += 30;
|
|
1673
|
-
if (normalized.includes('theme'))
|
|
1674
|
-
score += 25;
|
|
1675
|
-
if (normalized.endsWith('.html'))
|
|
1676
|
-
score += 20;
|
|
1677
|
-
if (normalized.includes('/src/'))
|
|
1678
|
-
score += 10;
|
|
1679
|
-
if (normalized.includes('/app/'))
|
|
1680
|
-
score += 10;
|
|
1681
|
-
if (normalized.includes('index.html'))
|
|
1682
|
-
score -= 10;
|
|
1683
|
-
return score;
|
|
1684
|
-
}
|
|
1685
|
-
addCandidateToTrace(trace, candidatePath, reason) {
|
|
1686
|
-
const normalized = this.normalizeCandidatePath(candidatePath);
|
|
1687
|
-
if (!normalized || normalized.length < 3)
|
|
1688
|
-
return;
|
|
1689
|
-
if (this.isMarathonArtifactPath(normalized))
|
|
1690
|
-
return;
|
|
1691
|
-
if (!trace.candidatePaths.includes(normalized)) {
|
|
1692
|
-
trace.candidatePaths.push(normalized);
|
|
1693
|
-
if (trace.candidatePaths.length > 12) {
|
|
1694
|
-
trace.candidatePaths = trace.candidatePaths.slice(-12);
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
const currentScore = trace.bestCandidatePath ? this.scoreCandidatePath(trace.bestCandidatePath) : -1;
|
|
1698
|
-
const nextScore = this.scoreCandidatePath(normalized);
|
|
1699
|
-
if (!trace.bestCandidatePath || nextScore >= currentScore) {
|
|
1700
|
-
trace.bestCandidatePath = normalized;
|
|
1701
|
-
trace.bestCandidateReason = reason.slice(0, 200);
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
extractCandidatePathsFromText(text, sourcePath) {
|
|
1705
|
-
const candidates = [];
|
|
1706
|
-
if (sourcePath && this.isMarathonArtifactPath(sourcePath)) {
|
|
1707
|
-
return candidates;
|
|
1708
|
-
}
|
|
1709
|
-
const add = (candidatePath, reason) => {
|
|
1710
|
-
const normalized = this.normalizeCandidatePath(candidatePath);
|
|
1711
|
-
if (!normalized)
|
|
1712
|
-
return;
|
|
1713
|
-
if (this.isMarathonArtifactPath(normalized))
|
|
1714
|
-
return;
|
|
1715
|
-
if (!candidates.some((candidate) => candidate.path === normalized)) {
|
|
1716
|
-
candidates.push({ path: normalized, reason });
|
|
1717
|
-
}
|
|
1718
|
-
};
|
|
1719
|
-
const baseDir = sourcePath ? this.dirnameOfCandidatePath(sourcePath) : '';
|
|
1720
|
-
for (const match of text.matchAll(/(?:href|src)=["']([^"']+\.(?:html|tsx|ts|jsx|js|md|json))["']/gi)) {
|
|
1721
|
-
const target = match[1] || '';
|
|
1722
|
-
const resolved = baseDir ? this.joinCandidatePath(baseDir, target) : target;
|
|
1723
|
-
add(resolved, `linked from ${sourcePath || 'discovery result'} via ${target}`);
|
|
1724
|
-
}
|
|
1725
|
-
for (const match of text.matchAll(/\b([\w./-]+\.(?:html|tsx|ts|jsx|js|md|json))\b/g)) {
|
|
1726
|
-
const target = match[1] || '';
|
|
1727
|
-
const resolved = sourcePath && !target.includes('/') ? this.joinCandidatePath(baseDir, target) : this.normalizeCandidatePath(target);
|
|
1728
|
-
add(resolved, `mentioned in ${sourcePath || 'discovery result'}`);
|
|
1729
|
-
}
|
|
1730
|
-
return candidates;
|
|
1731
|
-
}
|
|
1732
|
-
parseSearchRepoResultForCandidates(result) {
|
|
1733
|
-
const candidates = [];
|
|
1734
|
-
for (const line of result.split('\n')) {
|
|
1735
|
-
const contentMatch = line.match(/^\[content\]\s+([^:]+):\d+:\s+(.*)$/);
|
|
1736
|
-
if (contentMatch) {
|
|
1737
|
-
const sourcePath = this.normalizeCandidatePath(contentMatch[1] || '');
|
|
1738
|
-
const content = contentMatch[2] || '';
|
|
1739
|
-
for (const candidate of this.extractCandidatePathsFromText(content, sourcePath)) {
|
|
1740
|
-
if (!candidates.some((existing) => existing.path === candidate.path)) {
|
|
1741
|
-
candidates.push(candidate);
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
continue;
|
|
1745
|
-
}
|
|
1746
|
-
const pathMatch = line.match(/^\[path\]\s+(.+)$/);
|
|
1747
|
-
if (pathMatch) {
|
|
1748
|
-
const sourcePath = this.normalizeCandidatePath(pathMatch[1] || '');
|
|
1749
|
-
if (/\.(html|tsx|ts|jsx|js|md|json)$/i.test(sourcePath)) {
|
|
1750
|
-
candidates.push({ path: sourcePath, reason: 'matched repository path search result' });
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
return candidates;
|
|
1755
|
-
}
|
|
1756
|
-
extractBestCandidateFromBootstrapContext(bootstrapContext) {
|
|
1757
|
-
if (!bootstrapContext)
|
|
1758
|
-
return undefined;
|
|
1759
|
-
const candidates = this.parseSearchRepoResultForCandidates(bootstrapContext);
|
|
1760
|
-
if (candidates.length === 0)
|
|
1761
|
-
return undefined;
|
|
1762
|
-
return candidates.sort((a, b) => this.scoreCandidatePath(b.path) - this.scoreCandidatePath(a.path))[0];
|
|
1763
|
-
}
|
|
1764
|
-
sanitizeResumeState(resumeState, taskName) {
|
|
1765
|
-
if (!resumeState)
|
|
1766
|
-
return undefined;
|
|
1767
|
-
const planPath = typeof resumeState.planPath === 'string' && resumeState.planPath.trim()
|
|
1768
|
-
? this.normalizeCandidatePath(resumeState.planPath)
|
|
1769
|
-
: this.getDefaultPlanPath(taskName);
|
|
1770
|
-
const candidatePaths = this.dedupeNormalizedCandidatePaths(resumeState.candidatePaths);
|
|
1771
|
-
const recentReadPaths = this.dedupeNormalizedCandidatePaths(resumeState.recentReadPaths);
|
|
1772
|
-
const normalizedBestCandidatePath = typeof resumeState.bestCandidatePath === 'string' && resumeState.bestCandidatePath.trim()
|
|
1773
|
-
? this.normalizeCandidatePath(resumeState.bestCandidatePath)
|
|
1774
|
-
: undefined;
|
|
1775
|
-
const bestCandidatePath = normalizedBestCandidatePath && !this.isMarathonArtifactPath(normalizedBestCandidatePath)
|
|
1776
|
-
? normalizedBestCandidatePath
|
|
1777
|
-
: [...candidatePaths, ...recentReadPaths].sort((left, right) => this.scoreCandidatePath(right) - this.scoreCandidatePath(left))[0];
|
|
1778
|
-
const workflowPhase = resumeState.planWritten &&
|
|
1779
|
-
(!resumeState.workflowPhase ||
|
|
1780
|
-
resumeState.workflowPhase === 'research' ||
|
|
1781
|
-
resumeState.workflowPhase === 'planning')
|
|
1782
|
-
? 'execution'
|
|
1783
|
-
: resumeState.workflowPhase;
|
|
1784
|
-
return {
|
|
1785
|
-
...resumeState,
|
|
1786
|
-
workflowPhase,
|
|
1787
|
-
planPath,
|
|
1788
|
-
planWritten: Boolean(resumeState.planWritten),
|
|
1789
|
-
bestCandidatePath,
|
|
1790
|
-
bestCandidateReason: bestCandidatePath ? resumeState.bestCandidateReason : undefined,
|
|
1791
|
-
candidatePaths,
|
|
1792
|
-
recentReadPaths,
|
|
1793
|
-
recentActionKeys: Array.from(new Set(resumeState.recentActionKeys || [])).slice(-20),
|
|
1794
|
-
bestCandidateNeedsVerification: Boolean(resumeState.bestCandidateNeedsVerification),
|
|
1795
|
-
bestCandidateVerified: Boolean(resumeState.bestCandidateVerified),
|
|
1796
|
-
...(resumeState.verificationRequired !== undefined
|
|
1797
|
-
? { verificationRequired: resumeState.verificationRequired }
|
|
1798
|
-
: {}),
|
|
1799
|
-
lastVerificationPassed: Boolean(resumeState.lastVerificationPassed),
|
|
1800
|
-
};
|
|
1801
|
-
}
|
|
1802
|
-
buildPhaseInstructions(state) {
|
|
1803
|
-
const phase = state.workflowPhase || 'research';
|
|
1804
|
-
const planPath = state.planPath || this.getDefaultPlanPath(state.taskName);
|
|
1805
|
-
if (phase === 'planning') {
|
|
1806
|
-
return [
|
|
1807
|
-
'--- Workflow Phase: Planning ---',
|
|
1808
|
-
'Research is complete. Your current job is to write the implementation plan before any product-file edits.',
|
|
1809
|
-
`Write the plan markdown to exactly: ${planPath}`,
|
|
1810
|
-
'Do NOT edit the target product file yet.',
|
|
1811
|
-
'The plan should summarize UX findings, explain why the current best candidate is the right file, and list concrete execution steps.',
|
|
1812
|
-
'The plan must include a "Preserve existing functionality" section that lists current behaviors, linked files, integrations, and constraints that must keep working.',
|
|
1813
|
-
'The plan must include a "Verification steps" section listing the concrete checks you will run before TASK_COMPLETE.',
|
|
1814
|
-
'If the plan already exists, update that same plan file instead of creating a different one.',
|
|
1815
|
-
].join('\n');
|
|
1816
|
-
}
|
|
1817
|
-
if (phase === 'execution') {
|
|
1818
|
-
return [
|
|
1819
|
-
'--- Workflow Phase: Execution ---',
|
|
1820
|
-
`The plan should already exist at: ${planPath}`,
|
|
1821
|
-
...(state.bestCandidatePath ? [`Primary target file: ${state.bestCandidatePath}`] : []),
|
|
1822
|
-
'Execute the plan by editing the target files.',
|
|
1823
|
-
'Before ending each turn, update the markdown plan with progress against the steps you completed.',
|
|
1824
|
-
'Modify the existing implementation incrementally. Do not replace the whole file unless the user explicitly asked for a rewrite.',
|
|
1825
|
-
'Preserve existing functionality, handlers, imports, routes, configuration, and data flow unless the plan explicitly calls for changing them.',
|
|
1826
|
-
'Before TASK_COMPLETE, run a verification command that matches the repo, such as lint, tests, build, or typecheck.',
|
|
1827
|
-
'Avoid broad repo discovery unless the current candidate is clearly wrong.',
|
|
1828
|
-
].join('\n');
|
|
1829
|
-
}
|
|
1830
|
-
return [
|
|
1831
|
-
'--- Workflow Phase: Research ---',
|
|
1832
|
-
'Your current job is to inspect the repo, identify the correct existing target file, and gather enough evidence for a plan.',
|
|
1833
|
-
'Identify related supporting files and current behaviors that must be preserved before planning.',
|
|
1834
|
-
'Do NOT edit the target product file yet.',
|
|
1835
|
-
`When research is complete, the system will advance you to planning and require a plan at: ${planPath}`,
|
|
1836
|
-
].join('\n');
|
|
1837
|
-
}
|
|
1838
|
-
updateWorkflowPhase(state, sessionTrace) {
|
|
1839
|
-
if (!state.workflowPhase)
|
|
1840
|
-
state.workflowPhase = 'research';
|
|
1841
|
-
if (!state.planPath)
|
|
1842
|
-
state.planPath = this.getDefaultPlanPath(state.taskName);
|
|
1843
|
-
state.phaseTransitionSummary = undefined;
|
|
1844
|
-
const transitionSummaries = [];
|
|
1845
|
-
let phaseUpdated = true;
|
|
1846
|
-
while (phaseUpdated) {
|
|
1847
|
-
phaseUpdated = false;
|
|
1848
|
-
if (state.workflowPhase === 'research' && this.hasSufficientResearchEvidence(state)) {
|
|
1849
|
-
state.workflowPhase = 'planning';
|
|
1850
|
-
transitionSummaries.push([
|
|
1851
|
-
'Automatic phase transition: research -> planning.',
|
|
1852
|
-
`Best candidate confirmed: ${state.bestCandidatePath}`,
|
|
1853
|
-
`Next step: write the plan markdown to ${state.planPath} before editing the product file.`,
|
|
1854
|
-
].join('\n'));
|
|
1855
|
-
phaseUpdated = true;
|
|
1856
|
-
continue;
|
|
1857
|
-
}
|
|
1858
|
-
if (state.workflowPhase === 'planning' && (sessionTrace.planWritten || state.planWritten)) {
|
|
1859
|
-
state.planWritten = true;
|
|
1860
|
-
state.workflowPhase = 'execution';
|
|
1861
|
-
transitionSummaries.push([
|
|
1862
|
-
'Automatic phase transition: planning -> execution.',
|
|
1863
|
-
`Plan path: ${state.planPath}`,
|
|
1864
|
-
...(state.bestCandidatePath ? [`Execute against: ${state.bestCandidatePath}`] : []),
|
|
1865
|
-
'Next step: edit the target file(s) and update the plan with progress each turn.',
|
|
1866
|
-
].join('\n'));
|
|
1867
|
-
phaseUpdated = true;
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
if (state.status === 'complete') {
|
|
1871
|
-
state.workflowPhase = 'complete';
|
|
1872
|
-
}
|
|
1873
|
-
if (transitionSummaries.length > 0) {
|
|
1874
|
-
state.phaseTransitionSummary = transitionSummaries.join('\n\n');
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
wrapLocalToolsForTrace(localTools, trace, state) {
|
|
1878
|
-
if (!localTools)
|
|
1879
|
-
return undefined;
|
|
1880
|
-
const wrapped = {};
|
|
1881
|
-
for (const [toolName, toolDef] of Object.entries(localTools)) {
|
|
1882
|
-
wrapped[toolName] = {
|
|
1883
|
-
...toolDef,
|
|
1884
|
-
execute: async (args) => {
|
|
1885
|
-
const actionKey = `${toolName}:${String(args.path || args.query || args.pattern || '.').slice(0, 120)}`;
|
|
1886
|
-
trace.actionKeys.push(actionKey);
|
|
1887
|
-
if (trace.actionKeys.length > 10) {
|
|
1888
|
-
trace.actionKeys = trace.actionKeys.slice(-10);
|
|
1889
|
-
}
|
|
1890
|
-
const normalizedPathArg = typeof args.path === 'string' && args.path.trim()
|
|
1891
|
-
? this.normalizeCandidatePath(String(args.path))
|
|
1892
|
-
: undefined;
|
|
1893
|
-
const normalizedPlanPath = state.planPath
|
|
1894
|
-
? this.normalizeCandidatePath(state.planPath)
|
|
1895
|
-
: undefined;
|
|
1896
|
-
const normalizedBestCandidatePath = state.bestCandidatePath
|
|
1897
|
-
? this.normalizeCandidatePath(state.bestCandidatePath)
|
|
1898
|
-
: undefined;
|
|
1899
|
-
const allowedWriteTargets = new Set([
|
|
1900
|
-
normalizedPlanPath,
|
|
1901
|
-
normalizedBestCandidatePath,
|
|
1902
|
-
...(state.recentReadPaths || []).map((readPath) => this.normalizeCandidatePath(readPath)),
|
|
1903
|
-
...trace.readPaths.map((readPath) => this.normalizeCandidatePath(readPath)),
|
|
1904
|
-
].filter((value) => Boolean(value)));
|
|
1905
|
-
const pathArg = typeof args.path === 'string' && args.path.trim() ? ` path=${String(args.path)}` : '';
|
|
1906
|
-
const queryArg = typeof args.query === 'string' && args.query.trim() ? ` query="${String(args.query)}"` : '';
|
|
1907
|
-
const patternArg = typeof args.pattern === 'string' && args.pattern.trim()
|
|
1908
|
-
? ` pattern="${String(args.pattern)}"`
|
|
1909
|
-
: '';
|
|
1910
|
-
const isWriteLikeTool = toolName === 'write_file' || toolName === 'restore_file_checkpoint';
|
|
1911
|
-
const isVerificationTool = toolName === 'run_check';
|
|
1912
|
-
if (state.workflowPhase === 'execution' &&
|
|
1913
|
-
normalizedBestCandidatePath &&
|
|
1914
|
-
this.isDiscoveryToolName(toolName) &&
|
|
1915
|
-
!trace.bestCandidateReadFailed) {
|
|
1916
|
-
const blockedMessage = [
|
|
1917
|
-
`Blocked by marathon execution guard: ${toolName} is disabled during execution.`,
|
|
1918
|
-
`Read or edit "${normalizedBestCandidatePath}" instead.`,
|
|
1919
|
-
'Broad discovery is only re-enabled if a read of the current target file fails.',
|
|
1920
|
-
].join(' ');
|
|
1921
|
-
this.pushToolTraceEntry(trace, `${toolName}${pathArg}${queryArg}${patternArg} -> ${blockedMessage}`);
|
|
1922
|
-
return blockedMessage;
|
|
1923
|
-
}
|
|
1924
|
-
if (isWriteLikeTool) {
|
|
1925
|
-
trace.attemptedWrite = true;
|
|
1926
|
-
if (state.workflowPhase === 'planning' &&
|
|
1927
|
-
normalizedPathArg &&
|
|
1928
|
-
normalizedPlanPath &&
|
|
1929
|
-
normalizedPathArg !== normalizedPlanPath) {
|
|
1930
|
-
const blockedMessage = [
|
|
1931
|
-
`Blocked by marathon planning guard: ${toolName} must target the exact plan path during planning.`,
|
|
1932
|
-
`Write the plan to "${normalizedPlanPath}" before editing any product files.`,
|
|
1933
|
-
].join(' ');
|
|
1934
|
-
this.pushToolTraceEntry(trace, `${toolName}${pathArg}${queryArg}${patternArg} -> ${blockedMessage}`);
|
|
1935
|
-
return blockedMessage;
|
|
1936
|
-
}
|
|
1937
|
-
if (state.workflowPhase === 'execution' &&
|
|
1938
|
-
normalizedPathArg &&
|
|
1939
|
-
normalizedPlanPath &&
|
|
1940
|
-
normalizedBestCandidatePath &&
|
|
1941
|
-
normalizedPathArg === normalizedPlanPath &&
|
|
1942
|
-
!trace.executionFileWritten) {
|
|
1943
|
-
const blockedMessage = [
|
|
1944
|
-
`Blocked by marathon execution guard: ${toolName} cannot update the plan file before any real repo-file edit in this execution turn.`,
|
|
1945
|
-
`Edit "${normalizedBestCandidatePath}" or another previously discovered repo file first.`,
|
|
1946
|
-
`After that, you may update "${normalizedPlanPath}" with progress.`,
|
|
1947
|
-
].join(' ');
|
|
1948
|
-
this.pushToolTraceEntry(trace, `${toolName}${pathArg}${queryArg}${patternArg} -> ${blockedMessage}`);
|
|
1949
|
-
return blockedMessage;
|
|
1950
|
-
}
|
|
1951
|
-
if (state.workflowPhase === 'execution' &&
|
|
1952
|
-
normalizedPathArg &&
|
|
1953
|
-
normalizedPathArg !== normalizedPlanPath &&
|
|
1954
|
-
!allowedWriteTargets.has(normalizedPathArg)) {
|
|
1955
|
-
const blockedMessage = [
|
|
1956
|
-
`Blocked by marathon execution guard: ${toolName} is limited to the confirmed target, the plan file, or files already discovered/read for this task.`,
|
|
1957
|
-
`Do not create scratch files like "${normalizedPathArg}".`,
|
|
1958
|
-
normalizedBestCandidatePath
|
|
1959
|
-
? `Edit "${normalizedBestCandidatePath}" or another previously discovered repo file instead.`
|
|
1960
|
-
: 'Read the current target file before writing.',
|
|
1961
|
-
].join(' ');
|
|
1962
|
-
this.pushToolTraceEntry(trace, `${toolName}${pathArg}${queryArg}${patternArg} -> ${blockedMessage}`);
|
|
1963
|
-
return blockedMessage;
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
if (this.isDiscoveryToolName(toolName)) {
|
|
1967
|
-
trace.discoveryPerformed = true;
|
|
1968
|
-
}
|
|
1969
|
-
if (toolName === 'read_file') {
|
|
1970
|
-
trace.readFiles = true;
|
|
1971
|
-
if (normalizedPathArg) {
|
|
1972
|
-
const normalizedReadPath = normalizedPathArg;
|
|
1973
|
-
trace.readPaths.push(normalizedReadPath);
|
|
1974
|
-
if (trace.readPaths.length > 12) {
|
|
1975
|
-
trace.readPaths = trace.readPaths.slice(-12);
|
|
1976
|
-
}
|
|
1977
|
-
this.addCandidateToTrace(trace, normalizedReadPath, 'explicitly read by agent');
|
|
1978
|
-
}
|
|
1979
|
-
}
|
|
1980
|
-
let result;
|
|
1981
|
-
try {
|
|
1982
|
-
result = await toolDef.execute(args);
|
|
1983
|
-
}
|
|
1984
|
-
catch (error) {
|
|
1985
|
-
if (toolName === 'read_file' &&
|
|
1986
|
-
normalizedPathArg &&
|
|
1987
|
-
normalizedBestCandidatePath &&
|
|
1988
|
-
normalizedPathArg === normalizedBestCandidatePath) {
|
|
1989
|
-
trace.bestCandidateReadFailed = true;
|
|
1990
|
-
}
|
|
1991
|
-
this.pushToolTraceEntry(trace, `${toolName}${pathArg}${queryArg}${patternArg} -> error: ${error instanceof Error ? error.message : String(error)}`);
|
|
1992
|
-
throw error;
|
|
1993
|
-
}
|
|
1994
|
-
if (isWriteLikeTool && normalizedPathArg) {
|
|
1995
|
-
trace.wroteFiles = true;
|
|
1996
|
-
if (normalizedPlanPath && normalizedPathArg === normalizedPlanPath) {
|
|
1997
|
-
trace.planWritten = true;
|
|
1998
|
-
}
|
|
1999
|
-
else if (state.workflowPhase === 'execution') {
|
|
2000
|
-
trace.executionFileWritten = true;
|
|
2001
|
-
trace.verificationPassed = false;
|
|
2002
|
-
if (normalizedBestCandidatePath &&
|
|
2003
|
-
normalizedPathArg === normalizedBestCandidatePath) {
|
|
2004
|
-
trace.bestCandidateWritten = true;
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
}
|
|
2008
|
-
const verificationResult = isVerificationTool
|
|
2009
|
-
? this.parseVerificationResult(result)
|
|
2010
|
-
: undefined;
|
|
2011
|
-
if (verificationResult) {
|
|
2012
|
-
trace.verificationAttempted = true;
|
|
2013
|
-
trace.verificationPassed = verificationResult.success;
|
|
2014
|
-
}
|
|
2015
|
-
const summarizedResult = verificationResult
|
|
2016
|
-
? [
|
|
2017
|
-
verificationResult.command || 'verification',
|
|
2018
|
-
verificationResult.success ? 'passed' : 'failed',
|
|
2019
|
-
verificationResult.error || verificationResult.output,
|
|
2020
|
-
]
|
|
2021
|
-
.filter(Boolean)
|
|
2022
|
-
.join(' | ')
|
|
2023
|
-
.slice(0, 240)
|
|
2024
|
-
: this.summarizeTextBlockForTrace(result);
|
|
2025
|
-
const resultSuffix = summarizedResult ? ` -> ${summarizedResult}` : '';
|
|
2026
|
-
this.pushToolTraceEntry(trace, `${toolName}${pathArg}${queryArg}${patternArg}${resultSuffix}`);
|
|
2027
|
-
const textResult = typeof result === 'string' ? result : '';
|
|
2028
|
-
if (toolName === 'read_file' &&
|
|
2029
|
-
normalizedPathArg &&
|
|
2030
|
-
normalizedBestCandidatePath &&
|
|
2031
|
-
normalizedPathArg === normalizedBestCandidatePath &&
|
|
2032
|
-
(trace.bestCandidateWritten || state.bestCandidateNeedsVerification)) {
|
|
2033
|
-
trace.bestCandidateVerified = true;
|
|
2034
|
-
}
|
|
2035
|
-
if (toolName === 'search_repo' && textResult) {
|
|
2036
|
-
for (const candidate of this.parseSearchRepoResultForCandidates(textResult)) {
|
|
2037
|
-
this.addCandidateToTrace(trace, candidate.path, candidate.reason);
|
|
2038
|
-
}
|
|
2039
|
-
}
|
|
2040
|
-
else if (toolName === 'glob_files' && textResult) {
|
|
2041
|
-
for (const line of textResult.split('\n')) {
|
|
2042
|
-
const candidatePath = line.trim();
|
|
2043
|
-
if (/\.(html|tsx|ts|jsx|js|md|json)$/i.test(candidatePath)) {
|
|
2044
|
-
this.addCandidateToTrace(trace, candidatePath, 'matched glob search');
|
|
2045
|
-
}
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
else if (toolName === 'list_directory' && textResult && typeof args.path === 'string') {
|
|
2049
|
-
const baseDir = this.normalizeCandidatePath(String(args.path));
|
|
2050
|
-
for (const line of textResult.split('\n')) {
|
|
2051
|
-
const candidateName = line.trim();
|
|
2052
|
-
if (/\.(html|tsx|ts|jsx|js|md|json)$/i.test(candidateName)) {
|
|
2053
|
-
this.addCandidateToTrace(trace, this.joinCandidatePath(baseDir, candidateName), `listed in directory ${baseDir || '.'}`);
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
}
|
|
2057
|
-
else if (toolName === 'read_file' && textResult && typeof args.path === 'string') {
|
|
2058
|
-
const sourcePath = this.normalizeCandidatePath(String(args.path));
|
|
2059
|
-
for (const candidate of this.extractCandidatePathsFromText(textResult, sourcePath)) {
|
|
2060
|
-
this.addCandidateToTrace(trace, candidate.path, candidate.reason);
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
return result;
|
|
2064
|
-
},
|
|
2065
|
-
};
|
|
2066
|
-
}
|
|
2067
|
-
return wrapped;
|
|
2068
|
-
}
|
|
2069
|
-
createTraceCallbacks(callbacks, trace) {
|
|
2070
|
-
if (!callbacks) {
|
|
2071
|
-
return {
|
|
2072
|
-
onToolStart: (event) => {
|
|
2073
|
-
trace.actionKeys.push(`server:${event.toolName}`);
|
|
2074
|
-
if (trace.actionKeys.length > 10)
|
|
2075
|
-
trace.actionKeys = trace.actionKeys.slice(-10);
|
|
2076
|
-
if (event.toolName === 'write_file')
|
|
2077
|
-
trace.attemptedWrite = true;
|
|
2078
|
-
this.pushToolTraceEntry(trace, `server-tool ${event.toolName} started`);
|
|
2079
|
-
},
|
|
2080
|
-
onToolComplete: (event) => {
|
|
2081
|
-
const resultSummary = this.summarizeTextBlockForTrace(event.result);
|
|
2082
|
-
this.pushToolTraceEntry(trace, `server-tool ${event.toolName} ${event.success ? 'completed' : 'failed'}${resultSummary ? ` -> ${resultSummary}` : ''}`);
|
|
2083
|
-
},
|
|
2084
|
-
};
|
|
2085
|
-
}
|
|
2086
|
-
return {
|
|
2087
|
-
...callbacks,
|
|
2088
|
-
onToolStart: (event) => {
|
|
2089
|
-
trace.actionKeys.push(`server:${event.toolName}`);
|
|
2090
|
-
if (trace.actionKeys.length > 10)
|
|
2091
|
-
trace.actionKeys = trace.actionKeys.slice(-10);
|
|
2092
|
-
if (event.toolName === 'write_file')
|
|
2093
|
-
trace.attemptedWrite = true;
|
|
2094
|
-
this.pushToolTraceEntry(trace, `server-tool ${event.toolName} started`);
|
|
2095
|
-
callbacks.onToolStart?.(event);
|
|
2096
|
-
},
|
|
2097
|
-
onToolComplete: (event) => {
|
|
2098
|
-
const resultSummary = this.summarizeTextBlockForTrace(event.result);
|
|
2099
|
-
this.pushToolTraceEntry(trace, `server-tool ${event.toolName} ${event.success ? 'completed' : 'failed'}${resultSummary ? ` -> ${resultSummary}` : ''}`);
|
|
2100
|
-
callbacks.onToolComplete?.(event);
|
|
2101
|
-
},
|
|
2102
|
-
};
|
|
2103
|
-
}
|
|
2104
|
-
buildToolTraceSummary(trace) {
|
|
2105
|
-
if (trace.entries.length === 0)
|
|
2106
|
-
return '';
|
|
2107
|
-
const lines = trace.entries.slice(-6).map((entry) => `- ${entry}`);
|
|
2108
|
-
const flags = [];
|
|
2109
|
-
if (trace.discoveryPerformed)
|
|
2110
|
-
flags.push('repo discovery used');
|
|
2111
|
-
if (trace.readFiles)
|
|
2112
|
-
flags.push('candidate files read');
|
|
2113
|
-
if (trace.wroteFiles)
|
|
2114
|
-
flags.push('files written');
|
|
2115
|
-
if (trace.localToolLoopGuardTriggered)
|
|
2116
|
-
flags.push('local-tool loop guard forced end_turn');
|
|
2117
|
-
if (trace.bestCandidateVerified)
|
|
2118
|
-
flags.push('target re-read after write');
|
|
2119
|
-
if (trace.verificationPassed)
|
|
2120
|
-
flags.push('verification passed');
|
|
2121
|
-
else if (trace.verificationAttempted)
|
|
2122
|
-
flags.push('verification failed');
|
|
2123
|
-
return [
|
|
2124
|
-
'Session working memory:',
|
|
2125
|
-
...(flags.length > 0 ? [`- ${flags.join('; ')}`] : []),
|
|
2126
|
-
...(trace.bestCandidatePath
|
|
2127
|
-
? [`- best candidate: ${trace.bestCandidatePath}${trace.bestCandidateReason ? ` (${trace.bestCandidateReason})` : ''}`]
|
|
2128
|
-
: []),
|
|
2129
|
-
...lines,
|
|
2130
|
-
]
|
|
2131
|
-
.join('\n')
|
|
2132
|
-
.slice(0, 1200);
|
|
2133
|
-
}
|
|
2134
|
-
extractBootstrapQueries(message) {
|
|
2135
|
-
const queries = [];
|
|
2136
|
-
const noisyTerms = new Set([
|
|
2137
|
-
'a',
|
|
2138
|
-
'against',
|
|
2139
|
-
'all',
|
|
2140
|
-
'analyze',
|
|
2141
|
-
'and',
|
|
2142
|
-
'as',
|
|
2143
|
-
'at',
|
|
2144
|
-
'based',
|
|
2145
|
-
'before',
|
|
2146
|
-
'best',
|
|
2147
|
-
'by',
|
|
2148
|
-
'codebase',
|
|
2149
|
-
'do',
|
|
2150
|
-
'exactly',
|
|
2151
|
-
'files',
|
|
2152
|
-
'first',
|
|
2153
|
-
'following',
|
|
2154
|
-
'goal',
|
|
2155
|
-
'go',
|
|
2156
|
-
'how',
|
|
2157
|
-
'in',
|
|
2158
|
-
'is',
|
|
2159
|
-
'it',
|
|
2160
|
-
'its',
|
|
2161
|
-
'make',
|
|
2162
|
-
'markdown',
|
|
2163
|
-
'most',
|
|
2164
|
-
'no',
|
|
2165
|
-
'of',
|
|
2166
|
-
'on',
|
|
2167
|
-
'order',
|
|
2168
|
-
'plan',
|
|
2169
|
-
'progress',
|
|
2170
|
-
'repo',
|
|
2171
|
-
'research',
|
|
2172
|
-
'right',
|
|
2173
|
-
'save',
|
|
2174
|
-
'session',
|
|
2175
|
-
'solve',
|
|
2176
|
-
'task',
|
|
2177
|
-
'that',
|
|
2178
|
-
'the',
|
|
2179
|
-
'then',
|
|
2180
|
-
'through',
|
|
2181
|
-
'to',
|
|
2182
|
-
'turn',
|
|
2183
|
-
'update',
|
|
2184
|
-
'user',
|
|
2185
|
-
'ux',
|
|
2186
|
-
'web',
|
|
2187
|
-
'when',
|
|
2188
|
-
'with',
|
|
2189
|
-
'work',
|
|
2190
|
-
'your',
|
|
2191
|
-
]);
|
|
2192
|
-
const push = (candidate) => {
|
|
2193
|
-
const normalized = candidate
|
|
2194
|
-
.replace(/^[^a-z0-9/._-]+|[^a-z0-9/._ -]+$/gi, '')
|
|
2195
|
-
.replace(/\s+/g, ' ')
|
|
2196
|
-
.trim();
|
|
2197
|
-
if (!normalized || normalized.length < 3 || normalized.length > 60)
|
|
2198
|
-
return;
|
|
2199
|
-
const words = normalized.toLowerCase().split(' ').filter(Boolean);
|
|
2200
|
-
if (words.length > 4)
|
|
2201
|
-
return;
|
|
2202
|
-
if (words.every((word) => noisyTerms.has(word)))
|
|
2203
|
-
return;
|
|
2204
|
-
if (words.length > 1 && noisyTerms.has(words[words.length - 1] || ''))
|
|
2205
|
-
return;
|
|
2206
|
-
if (!queries.some((existing) => existing.toLowerCase() === normalized.toLowerCase())) {
|
|
2207
|
-
queries.push(normalized);
|
|
2208
|
-
}
|
|
2209
|
-
};
|
|
2210
|
-
const lowerMessage = message.toLowerCase();
|
|
2211
|
-
const phraseHints = [
|
|
2212
|
-
'agent editor',
|
|
2213
|
-
'theme.html',
|
|
2214
|
-
'/theme.html',
|
|
2215
|
-
'style it visually',
|
|
2216
|
-
];
|
|
2217
|
-
for (const hint of phraseHints) {
|
|
2218
|
-
if (lowerMessage.includes(hint.toLowerCase()))
|
|
2219
|
-
push(hint);
|
|
2220
|
-
}
|
|
2221
|
-
for (const match of message.matchAll(/"([^"]{3,60})"/g)) {
|
|
2222
|
-
push(match[1] || '');
|
|
2223
|
-
}
|
|
2224
|
-
for (const match of message.matchAll(/(?:go through|review|inspect|edit|improve|update|fix|modify)\s+(?:the\s+)?([a-z0-9][a-z0-9/_-]*(?:\s+[a-z0-9][a-z0-9/_-]*){0,2})/gi)) {
|
|
2225
|
-
push(match[1] || '');
|
|
2226
|
-
}
|
|
2227
|
-
for (const match of message.matchAll(/([a-z0-9][a-z0-9/_-]*(?:\s+[a-z0-9][a-z0-9/_-]*){0,2})\s+(?:page|editor|screen|view|route|component)\b/gi)) {
|
|
2228
|
-
push(match[0] || '');
|
|
2229
|
-
push(match[1] || '');
|
|
2230
|
-
}
|
|
2231
|
-
for (const match of message.matchAll(/\b[\w./-]+\.(?:html|tsx|ts|jsx|js|md|json)\b/g)) {
|
|
2232
|
-
push(match[0] || '');
|
|
2233
|
-
}
|
|
2234
|
-
for (const match of message.matchAll(/\/[A-Za-z0-9._/-]+/g)) {
|
|
2235
|
-
push(match[0] || '');
|
|
2236
|
-
}
|
|
2237
|
-
for (const match of message.matchAll(/\b([a-z0-9]+(?:\s+[a-z0-9]+){1,2})\b/gi)) {
|
|
2238
|
-
const phrase = (match[1] || '').toLowerCase();
|
|
2239
|
-
const words = phrase.split(' ');
|
|
2240
|
-
if (words.some((word) => ['editor', 'page', 'screen', 'view', 'route', 'component'].includes(word))) {
|
|
2241
|
-
push(match[1] || '');
|
|
2242
|
-
}
|
|
2243
|
-
}
|
|
2244
|
-
return queries.slice(0, 4);
|
|
2245
|
-
}
|
|
2246
|
-
async generateBootstrapDiscoveryContext(message, localTools) {
|
|
2247
|
-
if (!localTools)
|
|
2248
|
-
return undefined;
|
|
2249
|
-
const searchTool = localTools.search_repo;
|
|
2250
|
-
const globTool = localTools.glob_files;
|
|
2251
|
-
if (!searchTool && !globTool)
|
|
2252
|
-
return undefined;
|
|
2253
|
-
const queries = this.extractBootstrapQueries(message);
|
|
2254
|
-
if (queries.length === 0)
|
|
2255
|
-
return undefined;
|
|
2256
|
-
const lines = [];
|
|
2257
|
-
for (const query of queries) {
|
|
2258
|
-
if (lines.length >= 6)
|
|
2259
|
-
break;
|
|
2260
|
-
if (searchTool) {
|
|
2261
|
-
try {
|
|
2262
|
-
const result = await searchTool.execute({ query, path: '.', maxResults: 5 });
|
|
2263
|
-
const summary = this.summarizeTextBlockForTrace(result, 3);
|
|
2264
|
-
if (summary && !summary.startsWith('No matches found')) {
|
|
2265
|
-
lines.push(`search_repo "${query}": ${summary}`);
|
|
2266
|
-
continue;
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
2269
|
-
catch {
|
|
2270
|
-
// Best effort bootstrap only
|
|
2271
|
-
}
|
|
2272
|
-
}
|
|
2273
|
-
if (globTool && /\./.test(query)) {
|
|
2274
|
-
try {
|
|
2275
|
-
const result = await globTool.execute({ pattern: `**/${query}`, path: '.', maxResults: 5 });
|
|
2276
|
-
const summary = this.summarizeTextBlockForTrace(result, 3);
|
|
2277
|
-
if (summary && !summary.startsWith('No files matched')) {
|
|
2278
|
-
lines.push(`glob_files "**/${query}": ${summary}`);
|
|
2279
|
-
}
|
|
2280
|
-
}
|
|
2281
|
-
catch {
|
|
2282
|
-
// Best effort bootstrap only
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
if (lines.length === 0)
|
|
2287
|
-
return undefined;
|
|
2288
|
-
return ['Bootstrap repo hints:', ...lines].join('\n').slice(0, 1500);
|
|
2289
|
-
}
|
|
2290
|
-
buildStuckTurnRecoveryMessage(state) {
|
|
2291
|
-
const recent = state.sessions.slice(-2);
|
|
2292
|
-
const normalizedPlanPath = typeof state.planPath === 'string' && state.planPath.trim()
|
|
2293
|
-
? this.normalizeCandidatePath(state.planPath)
|
|
2294
|
-
: undefined;
|
|
2295
|
-
const recentPlanOnlyLoop = Boolean(normalizedPlanPath) &&
|
|
2296
|
-
recent.length === 2 &&
|
|
2297
|
-
recent.every((session) => {
|
|
2298
|
-
const specificActionKeys = (session.actionKeys || [])
|
|
2299
|
-
.map((actionKey) => actionKey.replace(/\\/g, '/'))
|
|
2300
|
-
.filter((actionKey) => !actionKey.startsWith('server:'));
|
|
2301
|
-
return (specificActionKeys.length > 0 &&
|
|
2302
|
-
specificActionKeys.every((actionKey) => actionKey.includes(normalizedPlanPath)));
|
|
2303
|
-
});
|
|
2304
|
-
if (recent.length < 2 ||
|
|
2305
|
-
!(recent.every((session) => session.hadTextOutput === false && session.wroteFiles === false) ||
|
|
2306
|
-
recentPlanOnlyLoop)) {
|
|
2307
|
-
return undefined;
|
|
2308
|
-
}
|
|
2309
|
-
const repeatedSameActions = recent.length === 2 &&
|
|
2310
|
-
recent.every((session) => (session.actionKeys?.length || 0) > 0) &&
|
|
2311
|
-
JSON.stringify(recent[0]?.actionKeys || []) === JSON.stringify(recent[1]?.actionKeys || []);
|
|
2312
|
-
if (state.workflowPhase === 'planning' && state.planPath) {
|
|
2313
|
-
return [
|
|
2314
|
-
'Recovery instruction:',
|
|
2315
|
-
'Research is already complete. Stop rediscovering and write the plan now.',
|
|
2316
|
-
`Your next action must be write_file to "${state.planPath}".`,
|
|
2317
|
-
'The plan must summarize UX findings, include a "Preserve existing functionality" section, name the best candidate file, and list execution steps.',
|
|
2318
|
-
'Do not edit the product file until the plan exists.',
|
|
2319
|
-
...(repeatedSameActions
|
|
2320
|
-
? ['You are repeating the same discovery actions; break the loop by writing the plan file now.']
|
|
2321
|
-
: []),
|
|
2322
|
-
].join('\n');
|
|
2323
|
-
}
|
|
2324
|
-
if (state.workflowPhase === 'execution' && state.bestCandidatePath) {
|
|
2325
|
-
const normalizedBestCandidatePath = this.normalizeCandidatePath(state.bestCandidatePath);
|
|
2326
|
-
const recentlyReadBestCandidate = (state.recentReadPaths || [])
|
|
2327
|
-
.map((readPath) => this.normalizeCandidatePath(readPath))
|
|
2328
|
-
.includes(normalizedBestCandidatePath);
|
|
2329
|
-
return [
|
|
2330
|
-
'Recovery instruction:',
|
|
2331
|
-
'Planning should already be complete. Stop rediscovering and execute the plan.',
|
|
2332
|
-
recentlyReadBestCandidate
|
|
2333
|
-
? `Your next action must be write_file on "${state.bestCandidatePath}".`
|
|
2334
|
-
: `Your next action must be read_file on "${state.bestCandidatePath}" so you can edit it next.`,
|
|
2335
|
-
...(state.planPath
|
|
2336
|
-
? [`Do not write "${state.planPath}" again until after you complete a real repo-file edit in this session.`]
|
|
2337
|
-
: []),
|
|
2338
|
-
'After editing, run a verification command with run_check before TASK_COMPLETE.',
|
|
2339
|
-
'Do not call broad discovery tools again unless the target file is missing or invalid.',
|
|
2340
|
-
...(repeatedSameActions
|
|
2341
|
-
? ['You are repeating the same discovery actions; break the loop by editing the target file now.']
|
|
2342
|
-
: []),
|
|
2343
|
-
].join('\n');
|
|
2344
|
-
}
|
|
2345
|
-
if (state.bestCandidatePath) {
|
|
2346
|
-
const recentlyReadBestCandidate = (state.recentReadPaths || []).includes(state.bestCandidatePath);
|
|
2347
|
-
return [
|
|
2348
|
-
'Recovery instruction:',
|
|
2349
|
-
'Your previous sessions produced no final text and did not complete a useful edit.',
|
|
2350
|
-
`You already have a best candidate file: "${state.bestCandidatePath}".`,
|
|
2351
|
-
...(state.bestCandidateReason ? [`Reason: ${state.bestCandidateReason}`] : []),
|
|
2352
|
-
recentlyReadBestCandidate
|
|
2353
|
-
? `Do not keep searching. Your next action must be to edit "${state.bestCandidatePath}" with write_file, or explain why that file is not the correct target.`
|
|
2354
|
-
: `Do not keep searching. Your next action must be read_file on "${state.bestCandidatePath}".`,
|
|
2355
|
-
'Do not call list_directory, tree_directory, glob_files, or search_repo again unless that candidate path is missing or clearly wrong.',
|
|
2356
|
-
...(repeatedSameActions
|
|
2357
|
-
? ['You are repeating the same discovery actions; break the loop by acting on the best candidate now.']
|
|
2358
|
-
: []),
|
|
2359
|
-
].join('\n');
|
|
2360
|
-
}
|
|
2361
|
-
const queries = this.extractBootstrapQueries(state.originalMessage || '');
|
|
2362
|
-
const queryHint = queries.length > 0
|
|
2363
|
-
? `Start with these exact repo searches: ${queries.map((query) => `"${query}"`).join(', ')}.`
|
|
2364
|
-
: 'Start with a concrete repo search using the key nouns from the original task.';
|
|
2365
|
-
return [
|
|
2366
|
-
'Recovery instruction:',
|
|
2367
|
-
'Your previous sessions produced no final text and did not edit files.',
|
|
2368
|
-
queryHint,
|
|
2369
|
-
'Then read the most relevant existing file you find before any write_file call.',
|
|
2370
|
-
'If a route, link, or page already exists, edit that existing file instead of creating a new one.',
|
|
2371
|
-
...(repeatedSameActions
|
|
2372
|
-
? ['You are repeating the same discovery actions; pick one candidate and act on it.']
|
|
2373
|
-
: []),
|
|
2374
|
-
].join('\n');
|
|
2375
|
-
}
|
|
2376
|
-
/**
|
|
2377
|
-
* Run a long-task agent across multiple sessions with automatic state management.
|
|
2378
|
-
*
|
|
2379
|
-
* Each session is a single agent execution. The SDK drives the loop client-side,
|
|
2380
|
-
* calling the agent's execute endpoint repeatedly and accumulating context.
|
|
2381
|
-
* Progress is optionally synced to a Runtype record for dashboard visibility.
|
|
2382
|
-
*
|
|
2383
|
-
* @example
|
|
2384
|
-
* ```typescript
|
|
2385
|
-
* const result = await client.agents.runTask('agt_123', {
|
|
2386
|
-
* message: 'Build a REST API with CRUD endpoints',
|
|
2387
|
-
* maxSessions: 20,
|
|
2388
|
-
* maxCost: 5.00,
|
|
2389
|
-
* trackProgress: true,
|
|
2390
|
-
* onSession: (state) => {
|
|
2391
|
-
* console.log(`Session ${state.sessionCount}: ${state.lastStopReason} ($${state.totalCost.toFixed(4)})`)
|
|
2392
|
-
* },
|
|
2393
|
-
* })
|
|
2394
|
-
*
|
|
2395
|
-
* console.log(`Finished: ${result.status} after ${result.sessionCount} sessions`)
|
|
2396
|
-
* ```
|
|
2397
|
-
*/
|
|
2398
|
-
async runTask(id, options) {
|
|
2399
|
-
const maxSessions = options.maxSessions ?? 50;
|
|
2400
|
-
const maxCost = options.maxCost;
|
|
2401
|
-
const useStream = options.stream ?? true;
|
|
2402
|
-
// Resolve agent metadata
|
|
2403
|
-
const agent = await this.get(id);
|
|
2404
|
-
const taskName = typeof options.trackProgress === 'string'
|
|
2405
|
-
? options.trackProgress
|
|
2406
|
-
: options.trackProgress
|
|
2407
|
-
? `${agent.name} task`
|
|
2408
|
-
: '';
|
|
2409
|
-
const resolvedTaskName = taskName || `${agent.name} task`;
|
|
2410
|
-
const seededResumeState = this.sanitizeResumeState(options.resumeState, resolvedTaskName);
|
|
2411
|
-
// Initialize state
|
|
2412
|
-
const state = {
|
|
2413
|
-
agentId: id,
|
|
2414
|
-
agentName: agent.name,
|
|
2415
|
-
taskName: resolvedTaskName,
|
|
2416
|
-
status: 'running',
|
|
2417
|
-
workflowPhase: seededResumeState?.workflowPhase || 'research',
|
|
2418
|
-
planPath: seededResumeState?.planPath || this.getDefaultPlanPath(resolvedTaskName),
|
|
2419
|
-
planWritten: seededResumeState?.planWritten || false,
|
|
2420
|
-
bestCandidateNeedsVerification: seededResumeState?.bestCandidateNeedsVerification || false,
|
|
2421
|
-
bestCandidateVerified: seededResumeState?.bestCandidateVerified || false,
|
|
2422
|
-
verificationRequired: seededResumeState?.verificationRequired ?? Boolean(options.localTools?.run_check),
|
|
2423
|
-
lastVerificationPassed: seededResumeState?.lastVerificationPassed || false,
|
|
2424
|
-
sessionCount: 0,
|
|
2425
|
-
totalCost: 0,
|
|
2426
|
-
lastOutput: '',
|
|
2427
|
-
lastStopReason: 'complete',
|
|
2428
|
-
sessions: [],
|
|
2429
|
-
startedAt: new Date().toISOString(),
|
|
2430
|
-
updatedAt: new Date().toISOString(),
|
|
2431
|
-
...(seededResumeState?.originalMessage ? { originalMessage: seededResumeState.originalMessage } : {}),
|
|
2432
|
-
...(seededResumeState?.bootstrapContext ? { bootstrapContext: seededResumeState.bootstrapContext } : {}),
|
|
2433
|
-
...(seededResumeState?.bestCandidatePath
|
|
2434
|
-
? {
|
|
2435
|
-
bestCandidatePath: seededResumeState.bestCandidatePath,
|
|
2436
|
-
bestCandidateReason: seededResumeState.bestCandidateReason,
|
|
2437
|
-
}
|
|
2438
|
-
: {}),
|
|
2439
|
-
...(seededResumeState?.candidatePaths ? { candidatePaths: seededResumeState.candidatePaths } : {}),
|
|
2440
|
-
...(seededResumeState?.recentReadPaths ? { recentReadPaths: seededResumeState.recentReadPaths } : {}),
|
|
2441
|
-
...(seededResumeState?.recentActionKeys
|
|
2442
|
-
? { recentActionKeys: seededResumeState.recentActionKeys }
|
|
2443
|
-
: {}),
|
|
2444
|
-
};
|
|
2445
|
-
this.updateWorkflowPhase(state, this.createEmptyToolTrace());
|
|
2446
|
-
// Track the record ID if we're syncing
|
|
2447
|
-
let recordId;
|
|
2448
|
-
// Extract local tool names for prompt injection
|
|
2449
|
-
const localToolNames = options.localTools ? Object.keys(options.localTools) : undefined;
|
|
2450
|
-
if (!options.previousMessages) {
|
|
2451
|
-
state.bootstrapContext = await this.generateBootstrapDiscoveryContext(options.message, options.localTools);
|
|
2452
|
-
const bootstrapCandidate = this.extractBestCandidateFromBootstrapContext(state.bootstrapContext);
|
|
2453
|
-
if (bootstrapCandidate) {
|
|
2454
|
-
state.bestCandidatePath = bootstrapCandidate.path;
|
|
2455
|
-
state.bestCandidateReason = bootstrapCandidate.reason;
|
|
2456
|
-
state.candidatePaths = [bootstrapCandidate.path];
|
|
2457
|
-
}
|
|
2458
|
-
}
|
|
2459
|
-
// Session loop
|
|
2460
|
-
for (let session = 0; session < maxSessions; session++) {
|
|
2461
|
-
const sessionTrace = this.createEmptyToolTrace();
|
|
2462
|
-
const sessionLocalTools = this.wrapLocalToolsForTrace(options.localTools, sessionTrace, state);
|
|
2463
|
-
const sessionCallbacks = this.createTraceCallbacks(options.streamCallbacks, sessionTrace);
|
|
2464
|
-
// Build continuation context for resumed runs (first session only)
|
|
2465
|
-
const continuationContext = session === 0 && options.previousMessages
|
|
2466
|
-
? {
|
|
2467
|
-
previousMessages: options.previousMessages,
|
|
2468
|
-
newUserMessage: options.continuationMessage,
|
|
2469
|
-
compact: options.compact,
|
|
2470
|
-
}
|
|
2471
|
-
: undefined;
|
|
2472
|
-
// Store original message on first invocation (not a continuation)
|
|
2473
|
-
if (session === 0 && !options.previousMessages) {
|
|
2474
|
-
state.originalMessage = options.message;
|
|
2475
|
-
}
|
|
2476
|
-
// Build messages for this session
|
|
2477
|
-
const messages = this.buildSessionMessages(options.message, state, session, maxSessions, localToolNames, continuationContext);
|
|
2478
|
-
// Execute one session
|
|
2479
|
-
let sessionResult;
|
|
2480
|
-
const sessionData = {
|
|
2481
|
-
messages,
|
|
2482
|
-
debugMode: options.debugMode,
|
|
2483
|
-
model: options.model,
|
|
2484
|
-
};
|
|
2485
|
-
if (useStream && options.localTools) {
|
|
2486
|
-
// Local tools require the pause/resume streaming loop
|
|
2487
|
-
const completeEvent = await this.executeWithLocalTools(id, sessionData, sessionLocalTools || options.localTools, sessionCallbacks, {
|
|
2488
|
-
onLocalToolResult: this.createLocalToolLoopGuard(state, sessionTrace),
|
|
2489
|
-
});
|
|
2490
|
-
if (!completeEvent) {
|
|
2491
|
-
throw new Error('Agent stream ended without a complete event');
|
|
2492
|
-
}
|
|
2493
|
-
sessionResult = {
|
|
2494
|
-
success: completeEvent.success,
|
|
2495
|
-
result: completeEvent.finalOutput || '',
|
|
2496
|
-
iterations: completeEvent.iterations,
|
|
2497
|
-
totalCost: completeEvent.totalCost || 0,
|
|
2498
|
-
stopReason: completeEvent.stopReason,
|
|
2499
|
-
error: completeEvent.error,
|
|
2500
|
-
};
|
|
2501
|
-
}
|
|
2502
|
-
else if (useStream && options.streamCallbacks) {
|
|
2503
|
-
const completeEvent = await this.executeWithCallbacks(id, sessionData, sessionCallbacks || options.streamCallbacks);
|
|
2504
|
-
if (!completeEvent) {
|
|
2505
|
-
throw new Error('Agent stream ended without a complete event');
|
|
2506
|
-
}
|
|
2507
|
-
sessionResult = {
|
|
2508
|
-
success: completeEvent.success,
|
|
2509
|
-
result: completeEvent.finalOutput || '',
|
|
2510
|
-
iterations: completeEvent.iterations,
|
|
2511
|
-
totalCost: completeEvent.totalCost || 0,
|
|
2512
|
-
stopReason: completeEvent.stopReason,
|
|
2513
|
-
error: completeEvent.error,
|
|
2514
|
-
};
|
|
2515
|
-
}
|
|
2516
|
-
else {
|
|
2517
|
-
sessionResult = await this.execute(id, sessionData);
|
|
2518
|
-
}
|
|
2519
|
-
const toolTraceSummary = this.buildToolTraceSummary(sessionTrace);
|
|
2520
|
-
const effectiveSessionOutput = this.buildEffectiveSessionOutput(sessionResult.result, toolTraceSummary);
|
|
2521
|
-
// Update state
|
|
2522
|
-
const sessionCost = sessionResult.totalCost;
|
|
2523
|
-
state.sessionCount = session + 1;
|
|
2524
|
-
state.totalCost += sessionCost;
|
|
2525
|
-
state.lastOutput = effectiveSessionOutput;
|
|
2526
|
-
state.lastError =
|
|
2527
|
-
sessionResult.stopReason === 'error'
|
|
2528
|
-
? sessionResult.error || 'Agent session ended with an error.'
|
|
2529
|
-
: undefined;
|
|
2530
|
-
state.lastStopReason = sessionResult.stopReason;
|
|
2531
|
-
state.updatedAt = new Date().toISOString();
|
|
2532
|
-
state.sessions.push({
|
|
2533
|
-
index: session + 1,
|
|
2534
|
-
cost: sessionCost,
|
|
2535
|
-
iterations: sessionResult.iterations,
|
|
2536
|
-
stopReason: sessionResult.stopReason,
|
|
2537
|
-
outputPreview: effectiveSessionOutput.slice(0, 300),
|
|
2538
|
-
toolTraceSummary: toolTraceSummary || undefined,
|
|
2539
|
-
discoveryPerformed: sessionTrace.discoveryPerformed,
|
|
2540
|
-
attemptedWrite: sessionTrace.attemptedWrite,
|
|
2541
|
-
wroteFiles: sessionTrace.wroteFiles,
|
|
2542
|
-
hadTextOutput: Boolean(sessionResult.result.trim()),
|
|
2543
|
-
verificationAttempted: sessionTrace.verificationAttempted,
|
|
2544
|
-
verificationPassed: sessionTrace.verificationPassed,
|
|
2545
|
-
bestCandidatePath: sessionTrace.bestCandidatePath || undefined,
|
|
2546
|
-
actionKeys: sessionTrace.actionKeys.slice(-5),
|
|
2547
|
-
completedAt: new Date().toISOString(),
|
|
2548
|
-
});
|
|
2549
|
-
if (sessionTrace.bestCandidatePath) {
|
|
2550
|
-
state.bestCandidatePath = sessionTrace.bestCandidatePath;
|
|
2551
|
-
state.bestCandidateReason = sessionTrace.bestCandidateReason;
|
|
2552
|
-
}
|
|
2553
|
-
if (sessionTrace.candidatePaths.length > 0) {
|
|
2554
|
-
state.candidatePaths = Array.from(new Set([...(state.candidatePaths || []), ...sessionTrace.candidatePaths])).slice(-20);
|
|
2555
|
-
}
|
|
2556
|
-
if (sessionTrace.readPaths.length > 0) {
|
|
2557
|
-
state.recentReadPaths = Array.from(new Set([...(state.recentReadPaths || []), ...sessionTrace.readPaths])).slice(-20);
|
|
2558
|
-
}
|
|
2559
|
-
if (sessionTrace.actionKeys.length > 0) {
|
|
2560
|
-
state.recentActionKeys = [...(state.recentActionKeys || []), ...sessionTrace.actionKeys].slice(-20);
|
|
2561
|
-
}
|
|
2562
|
-
if (sessionTrace.planWritten) {
|
|
2563
|
-
state.planWritten = true;
|
|
2564
|
-
}
|
|
2565
|
-
if (sessionTrace.executionFileWritten) {
|
|
2566
|
-
state.lastVerificationPassed = false;
|
|
2567
|
-
}
|
|
2568
|
-
if (sessionTrace.bestCandidateWritten) {
|
|
2569
|
-
state.bestCandidateNeedsVerification = true;
|
|
2570
|
-
state.bestCandidateVerified = false;
|
|
2571
|
-
}
|
|
2572
|
-
if (sessionTrace.bestCandidateVerified) {
|
|
2573
|
-
state.bestCandidateNeedsVerification = false;
|
|
2574
|
-
state.bestCandidateVerified = true;
|
|
2575
|
-
}
|
|
2576
|
-
if (sessionTrace.verificationAttempted) {
|
|
2577
|
-
state.lastVerificationPassed = sessionTrace.verificationPassed;
|
|
2578
|
-
}
|
|
2579
|
-
// Track cost by model
|
|
2580
|
-
const modelKey = options.model || 'default';
|
|
2581
|
-
if (!state.costByModel)
|
|
2582
|
-
state.costByModel = {};
|
|
2583
|
-
state.costByModel[modelKey] = (state.costByModel[modelKey] || 0) + sessionCost;
|
|
2584
|
-
this.updateWorkflowPhase(state, sessionTrace);
|
|
2585
|
-
const phaseTransitionSummary = state.phaseTransitionSummary;
|
|
2586
|
-
if (phaseTransitionSummary) {
|
|
2587
|
-
state.lastOutput = [phaseTransitionSummary, '', state.lastOutput].join('\n').trim();
|
|
2588
|
-
const latestSession = state.sessions[state.sessions.length - 1];
|
|
2589
|
-
if (latestSession) {
|
|
2590
|
-
latestSession.outputPreview = [phaseTransitionSummary, '', latestSession.outputPreview]
|
|
2591
|
-
.join('\n')
|
|
2592
|
-
.slice(0, 300);
|
|
2593
|
-
latestSession.toolTraceSummary = [phaseTransitionSummary, '', latestSession.toolTraceSummary || '']
|
|
2594
|
-
.join('\n')
|
|
2595
|
-
.trim()
|
|
2596
|
-
.slice(0, 1200);
|
|
2597
|
-
}
|
|
2598
|
-
}
|
|
2599
|
-
// Accumulate messages for future continuation.
|
|
2600
|
-
// When buildSessionMessages returns the full history + a new continuation
|
|
2601
|
-
// message, only the NEW messages at the end are appended — otherwise the
|
|
2602
|
-
// history would be re-pushed on every session and grow exponentially.
|
|
2603
|
-
if (!state.messages)
|
|
2604
|
-
state.messages = [];
|
|
2605
|
-
if (state.messages.length > 0 && messages.length > state.messages.length) {
|
|
2606
|
-
// Continuation session: history was replayed, only append the new tail
|
|
2607
|
-
const newMessages = messages.slice(state.messages.length);
|
|
2608
|
-
state.messages.push(...newMessages);
|
|
2609
|
-
}
|
|
2610
|
-
else {
|
|
2611
|
-
// First session (or no prior history): all messages are new
|
|
2612
|
-
state.messages.push(...messages);
|
|
2613
|
-
}
|
|
2614
|
-
// Always store an assistant message so continuation sessions have full
|
|
2615
|
-
// conversation history. When the agent only made tool calls and produced
|
|
2616
|
-
// no text, fall back to a synthetic summary so the history stays coherent.
|
|
2617
|
-
const assistantContent = effectiveSessionOutput ||
|
|
2618
|
-
`[Session ${session + 1} completed (${sessionResult.stopReason}). No text output captured.]`;
|
|
2619
|
-
state.messages.push({ role: 'assistant', content: assistantContent });
|
|
2620
|
-
// Keep session log trimmed to last 50 entries
|
|
2621
|
-
if (state.sessions.length > 50) {
|
|
2622
|
-
state.sessions = state.sessions.slice(-50);
|
|
2623
|
-
}
|
|
2624
|
-
// Check terminal conditions
|
|
2625
|
-
if (sessionResult.stopReason === 'complete') {
|
|
2626
|
-
state.status = 'complete';
|
|
2627
|
-
}
|
|
2628
|
-
else if (sessionResult.stopReason === 'error') {
|
|
2629
|
-
state.status = 'error';
|
|
2630
|
-
}
|
|
2631
|
-
else if (sessionResult.stopReason === 'max_cost') {
|
|
2632
|
-
state.status = 'budget_exceeded';
|
|
2633
|
-
}
|
|
2634
|
-
else if (this.canAcceptTaskCompletion(sessionResult.result, state, sessionTrace)) {
|
|
2635
|
-
// Client-side stop-phrase detection for non-loop agents returning 'end_turn'
|
|
2636
|
-
state.status = 'complete';
|
|
2637
|
-
}
|
|
2638
|
-
else if (maxCost && state.totalCost >= maxCost) {
|
|
2639
|
-
state.status = 'budget_exceeded';
|
|
2640
|
-
}
|
|
2641
|
-
else if (session + 1 >= maxSessions) {
|
|
2642
|
-
state.status = 'max_sessions';
|
|
2643
|
-
}
|
|
2644
|
-
// Sync to record if enabled
|
|
2645
|
-
if (options.trackProgress) {
|
|
2646
|
-
recordId = await this.syncProgressRecord(state, recordId);
|
|
2647
|
-
}
|
|
2648
|
-
// Notify caller
|
|
2649
|
-
if (options.onSession) {
|
|
2650
|
-
const shouldStop = await options.onSession(state);
|
|
2651
|
-
if (shouldStop === false) {
|
|
2652
|
-
state.status = 'paused';
|
|
2653
|
-
}
|
|
2654
|
-
}
|
|
2655
|
-
// Stop if terminal
|
|
2656
|
-
if (state.status !== 'running') {
|
|
2657
|
-
break;
|
|
2658
|
-
}
|
|
2659
|
-
}
|
|
2660
|
-
return {
|
|
2661
|
-
status: state.status,
|
|
2662
|
-
sessionCount: state.sessionCount,
|
|
2663
|
-
totalCost: state.totalCost,
|
|
2664
|
-
lastOutput: state.lastOutput,
|
|
2665
|
-
sessions: state.sessions,
|
|
2666
|
-
recordId,
|
|
2667
|
-
};
|
|
2668
|
-
}
|
|
2669
|
-
/**
|
|
2670
|
-
* Client-side fallback for detecting task completion in agent output.
|
|
2671
|
-
* Mirrors the API's detectAutoComplete() for non-loop agents that return 'end_turn'.
|
|
2672
|
-
*/
|
|
2673
|
-
detectTaskCompletion(output) {
|
|
2674
|
-
const upper = output.toUpperCase();
|
|
2675
|
-
return AgentsEndpoint.STOP_PHRASES.some((phrase) => upper.includes(phrase.toUpperCase()));
|
|
2676
|
-
}
|
|
2677
|
-
/**
|
|
2678
|
-
* Generate a compact summary of prior work for continuation context.
|
|
2679
|
-
* Used when compact mode is enabled to keep token usage low.
|
|
2680
|
-
*/
|
|
2681
|
-
generateCompactSummary(state) {
|
|
2682
|
-
const sessionSummaries = (state.sessions ?? [])
|
|
2683
|
-
.map((s) => `- Session ${s.index}: ${s.stopReason} ($${s.cost.toFixed(4)}) -- ${s.outputPreview.slice(0, 100)}`)
|
|
2684
|
-
.join('\n');
|
|
2685
|
-
return [
|
|
2686
|
-
`Task: ${state.taskName}`,
|
|
2687
|
-
`Status: ${state.status}`,
|
|
2688
|
-
`Workflow phase: ${state.workflowPhase || 'research'}`,
|
|
2689
|
-
`Sessions completed: ${state.sessionCount}`,
|
|
2690
|
-
`Total cost: $${state.totalCost.toFixed(4)}`,
|
|
2691
|
-
...(state.planPath ? [`Plan path: ${state.planPath}`] : []),
|
|
2692
|
-
...(state.planWritten ? ['Plan written: yes'] : []),
|
|
2693
|
-
...(state.bestCandidatePath
|
|
2694
|
-
? [
|
|
2695
|
-
`Best candidate: ${state.bestCandidatePath}`,
|
|
2696
|
-
...(state.bestCandidateReason ? [`Candidate reason: ${state.bestCandidateReason}`] : []),
|
|
2697
|
-
]
|
|
2698
|
-
: []),
|
|
2699
|
-
...(state.bootstrapContext ? ['', state.bootstrapContext] : []),
|
|
2700
|
-
'',
|
|
2701
|
-
'Session history:',
|
|
2702
|
-
sessionSummaries,
|
|
2703
|
-
'',
|
|
2704
|
-
'Last output (truncated):',
|
|
2705
|
-
(state.lastOutput || '').slice(0, 1500),
|
|
2706
|
-
].join('\n');
|
|
2707
|
-
}
|
|
2708
|
-
/**
|
|
2709
|
-
* Build messages for a session, injecting progress context for continuation sessions.
|
|
2710
|
-
* Optionally accepts continuation context for marathon resume scenarios.
|
|
2711
|
-
*/
|
|
2712
|
-
buildSessionMessages(originalMessage, state, sessionIndex, maxSessions, localToolNames, continuationContext) {
|
|
2713
|
-
// Build local tools guidance block when tools are available
|
|
2714
|
-
const phase = state.workflowPhase || 'research';
|
|
2715
|
-
const toolsBlock = localToolNames?.length
|
|
2716
|
-
? [
|
|
2717
|
-
'',
|
|
2718
|
-
'--- Local Tools ---',
|
|
2719
|
-
`You have access to local filesystem tools (${localToolNames.join(', ')}) that execute directly on the user's machine.`,
|
|
2720
|
-
'Use these tools to inspect the existing repository and make real file edits — not just code in your response.',
|
|
2721
|
-
...(phase === 'research'
|
|
2722
|
-
? [
|
|
2723
|
-
'For repository modification tasks, before any write_file call you must perform at least one discovery action (search_repo, glob_files, or tree_directory).',
|
|
2724
|
-
'If discovery finds a plausible existing file, you must read at least one candidate file before writing.',
|
|
2725
|
-
'Before creating a new file, search the repo for existing relevant files, routes, links, components, or pages.',
|
|
2726
|
-
'Prefer editing an existing file when one already implements or links to the feature you were asked to change.',
|
|
2727
|
-
'Use search_repo, glob_files, and tree_directory to discover the right files before you call write_file.',
|
|
2728
|
-
'Only create a new file when no suitable existing file exists, and make that decision intentionally.',
|
|
2729
|
-
]
|
|
2730
|
-
: phase === 'planning'
|
|
2731
|
-
? [
|
|
2732
|
-
`Research is already complete. Focus on writing or updating the plan at: ${state.planPath || this.getDefaultPlanPath(state.taskName)}.`,
|
|
2733
|
-
'Do not restart broad repo discovery unless the saved best candidate is clearly invalid.',
|
|
2734
|
-
'You may read the current target file or supporting source files if you need evidence for the plan.',
|
|
2735
|
-
'Ground the plan in the existing implementation. Identify which current behaviors and linked files must be preserved.',
|
|
2736
|
-
'List the exact verification commands you expect to run after editing, such as lint, typecheck, tests, or build.',
|
|
2737
|
-
]
|
|
2738
|
-
: [
|
|
2739
|
-
...(state.bestCandidatePath
|
|
2740
|
-
? [
|
|
2741
|
-
`Execution-phase guard: broad discovery tools (search_repo, glob_files, tree_directory, list_directory) are locked while executing against "${state.bestCandidatePath}".`,
|
|
2742
|
-
]
|
|
2743
|
-
: [
|
|
2744
|
-
'Execution-phase guard: broad discovery tools are locked unless a read of the current target fails.',
|
|
2745
|
-
]),
|
|
2746
|
-
'Reading the markdown plan for status does not change the product target. Do not treat the plan file as the file to implement.',
|
|
2747
|
-
'Do not write the plan file first in execution. Make a real repo-file edit before you update the plan with progress.',
|
|
2748
|
-
'Do not create scratch or test files to probe the repo or tool behavior.',
|
|
2749
|
-
'write_file automatically checkpoints original repo files before overwriting them. If an edit regresses behavior, use restore_file_checkpoint on that file.',
|
|
2750
|
-
'Read the target file and edit it with write_file. Update the plan file with progress after completing real edits.',
|
|
2751
|
-
'Before large edits, read any already discovered supporting source/style files that power the target so you preserve existing behavior.',
|
|
2752
|
-
'Prefer minimal diffs over rewrites. If you cannot verify related behavior, stop and record what is still unverified instead of rewriting blindly.',
|
|
2753
|
-
'Use run_check for real verification before TASK_COMPLETE. Good examples: "pnpm lint", "pnpm exec tsc --noEmit", "pnpm test", or a focused vitest/pytest command.',
|
|
2754
|
-
'Broad discovery is only allowed if a read of the current target file fails.',
|
|
2755
|
-
]),
|
|
2756
|
-
'Always use write_file to save your output so the user can run it immediately.',
|
|
2757
|
-
].join('\n')
|
|
2758
|
-
: '';
|
|
2759
|
-
const bootstrapBlock = state.bootstrapContext
|
|
2760
|
-
? ['', '--- Initial Repository Discovery ---', state.bootstrapContext].join('\n')
|
|
2761
|
-
: '';
|
|
2762
|
-
const phaseBlock = ['', this.buildPhaseInstructions(state)].join('\n');
|
|
2763
|
-
const candidateBlock = state.bestCandidatePath
|
|
2764
|
-
? [
|
|
2765
|
-
'',
|
|
2766
|
-
'--- Best Candidate ---',
|
|
2767
|
-
`Current best candidate file: ${state.bestCandidatePath}`,
|
|
2768
|
-
...(state.bestCandidateReason ? [`Why: ${state.bestCandidateReason}`] : []),
|
|
2769
|
-
].join('\n')
|
|
2770
|
-
: '';
|
|
2771
|
-
const multiSessionInstruction = `This is a multi-session task (session ${sessionIndex + 1}/${maxSessions}). When you have fully completed the task, end your response with TASK_COMPLETE on its own line.`;
|
|
2772
|
-
// Continuation resume: first session of a resumed run with prior context
|
|
2773
|
-
if (continuationContext && sessionIndex === 0) {
|
|
2774
|
-
const defaultContinueMessage = 'Continue the task. Review your prior work above and proceed with any remaining work. If everything is already complete, respond with TASK_COMPLETE.';
|
|
2775
|
-
const userMessage = continuationContext.newUserMessage || defaultContinueMessage;
|
|
2776
|
-
if (continuationContext.compact) {
|
|
2777
|
-
// Compact mode: summarize prior work instead of full history
|
|
2778
|
-
const summary = this.generateCompactSummary(state);
|
|
2779
|
-
const messages = [
|
|
2780
|
-
{
|
|
2781
|
-
role: 'system',
|
|
2782
|
-
content: `You are continuing a previously completed task. Here is a summary of prior work:\n\n${summary}\n\nDo NOT redo any of the above work.`,
|
|
2783
|
-
},
|
|
2784
|
-
{
|
|
2785
|
-
role: 'user',
|
|
2786
|
-
content: [userMessage, phaseBlock, toolsBlock, bootstrapBlock, candidateBlock, '', multiSessionInstruction].join('\n'),
|
|
2787
|
-
},
|
|
2788
|
-
];
|
|
2789
|
-
return messages;
|
|
2790
|
-
}
|
|
2791
|
-
// Full history mode: replay all previous messages with do-not-redo instruction
|
|
2792
|
-
const messages = [
|
|
2793
|
-
...continuationContext.previousMessages,
|
|
2794
|
-
{
|
|
2795
|
-
role: 'system',
|
|
2796
|
-
content: 'IMPORTANT: You are continuing a previously completed task. The conversation above shows your prior work. Do NOT redo any of it. Build on what was already accomplished. If there is nothing new to do, respond with TASK_COMPLETE.',
|
|
2797
|
-
},
|
|
2798
|
-
{
|
|
2799
|
-
role: 'user',
|
|
2800
|
-
content: [userMessage, phaseBlock, toolsBlock, bootstrapBlock, candidateBlock, '', multiSessionInstruction].join('\n'),
|
|
2801
|
-
},
|
|
2802
|
-
];
|
|
2803
|
-
return messages;
|
|
2804
|
-
}
|
|
2805
|
-
// First session (non-continuation): user message + completion signal instruction
|
|
2806
|
-
if (sessionIndex === 0) {
|
|
2807
|
-
const content = [originalMessage, phaseBlock, toolsBlock, bootstrapBlock, candidateBlock, '', multiSessionInstruction].join('\n');
|
|
2808
|
-
return [{ role: 'user', content }];
|
|
2809
|
-
}
|
|
2810
|
-
// Continuation sessions within a run: inject progress context
|
|
2811
|
-
const recentSessions = state.sessions.slice(-5);
|
|
2812
|
-
const progressSummary = recentSessions
|
|
2813
|
-
.map((s) => ` Session ${s.index}: ${s.stopReason} ($${s.cost.toFixed(4)}) — ${s.outputPreview.slice(0, 100)}`)
|
|
2814
|
-
.join('\n');
|
|
2815
|
-
// When we have accumulated message history, replay the full conversation
|
|
2816
|
-
// so the model has complete context and doesn't start fresh each session.
|
|
2817
|
-
if (state.messages && state.messages.length > 0) {
|
|
2818
|
-
const recoveryMessage = this.buildStuckTurnRecoveryMessage(state);
|
|
2819
|
-
const continuationContent = [
|
|
2820
|
-
'Continue the task.',
|
|
2821
|
-
phaseBlock,
|
|
2822
|
-
toolsBlock,
|
|
2823
|
-
bootstrapBlock,
|
|
2824
|
-
candidateBlock,
|
|
2825
|
-
'',
|
|
2826
|
-
`--- Progress (session ${sessionIndex + 1}/${maxSessions}, $${state.totalCost.toFixed(4)} spent) ---`,
|
|
2827
|
-
`Previous sessions:`,
|
|
2828
|
-
progressSummary,
|
|
2829
|
-
'',
|
|
2830
|
-
...(recoveryMessage ? [recoveryMessage, ''] : []),
|
|
2831
|
-
'Do not redo previous work. If the task is already complete, respond with TASK_COMPLETE.',
|
|
2832
|
-
].join('\n');
|
|
2833
|
-
// Cap history to prevent context overflow on long-running marathons.
|
|
2834
|
-
// Keep the most recent 40 messages; prepend a system summary for trimmed ones.
|
|
2835
|
-
const MAX_HISTORY_MESSAGES = 40;
|
|
2836
|
-
let historyMessages = state.messages;
|
|
2837
|
-
if (historyMessages.length > MAX_HISTORY_MESSAGES) {
|
|
2838
|
-
const trimmedCount = historyMessages.length - MAX_HISTORY_MESSAGES;
|
|
2839
|
-
historyMessages = [
|
|
2840
|
-
{
|
|
2841
|
-
role: 'system',
|
|
2842
|
-
content: `[${trimmedCount} earlier messages trimmed to stay within context limits. Original task: ${(state.originalMessage || originalMessage).slice(0, 500)}]`,
|
|
2843
|
-
},
|
|
2844
|
-
...historyMessages.slice(-MAX_HISTORY_MESSAGES),
|
|
2845
|
-
];
|
|
2846
|
-
}
|
|
2847
|
-
return [
|
|
2848
|
-
...historyMessages,
|
|
2849
|
-
{ role: 'user', content: continuationContent },
|
|
2850
|
-
];
|
|
2851
|
-
}
|
|
2852
|
-
// Fallback when no message history is available: single-message summary
|
|
2853
|
-
const recoveryMessage = this.buildStuckTurnRecoveryMessage(state);
|
|
2854
|
-
const content = [
|
|
2855
|
-
originalMessage,
|
|
2856
|
-
phaseBlock,
|
|
2857
|
-
toolsBlock,
|
|
2858
|
-
bootstrapBlock,
|
|
2859
|
-
candidateBlock,
|
|
2860
|
-
'',
|
|
2861
|
-
`--- Progress (session ${sessionIndex + 1}/${maxSessions}, $${state.totalCost.toFixed(4)} spent) ---`,
|
|
2862
|
-
`Previous sessions:`,
|
|
2863
|
-
progressSummary,
|
|
2864
|
-
'',
|
|
2865
|
-
...(recoveryMessage ? [recoveryMessage, ''] : []),
|
|
2866
|
-
`Last output (do NOT repeat this — build on it):`,
|
|
2867
|
-
state.lastOutput.slice(0, 1000),
|
|
2868
|
-
'',
|
|
2869
|
-
'Continue where you left off. Do not redo previous work. If the task is already complete, respond with TASK_COMPLETE.',
|
|
2870
|
-
].join('\n');
|
|
2871
|
-
return [{ role: 'user', content }];
|
|
2872
|
-
}
|
|
2873
|
-
/**
|
|
2874
|
-
* Upsert a record to sync long-task progress to the dashboard.
|
|
2875
|
-
* Creates the record on first call, updates it on subsequent calls.
|
|
2876
|
-
*/
|
|
2877
|
-
async syncProgressRecord(state, existingRecordId) {
|
|
2878
|
-
const metadata = {
|
|
2879
|
-
agentId: state.agentId,
|
|
2880
|
-
agentName: state.agentName,
|
|
2881
|
-
status: state.status,
|
|
2882
|
-
sessionCount: state.sessionCount,
|
|
2883
|
-
totalCost: state.totalCost,
|
|
2884
|
-
lastStopReason: state.lastStopReason,
|
|
2885
|
-
lastOutputPreview: state.lastOutput.slice(0, 500),
|
|
2886
|
-
sessions: state.sessions.slice(-10), // Keep last 10 in the record
|
|
2887
|
-
startedAt: state.startedAt,
|
|
2888
|
-
updatedAt: state.updatedAt,
|
|
2889
|
-
};
|
|
2890
|
-
try {
|
|
2891
|
-
if (existingRecordId) {
|
|
2892
|
-
// Update existing record
|
|
2893
|
-
const record = await this.client.put(`/records/${existingRecordId}`, {
|
|
2894
|
-
metadata,
|
|
2895
|
-
});
|
|
2896
|
-
return record.id;
|
|
2897
|
-
}
|
|
2898
|
-
else {
|
|
2899
|
-
// Try to find existing record by type + name first
|
|
2900
|
-
const existing = await this.client.get('/records', {
|
|
2901
|
-
type: 'agent-task',
|
|
2902
|
-
name: state.taskName,
|
|
2903
|
-
limit: 1,
|
|
2904
|
-
});
|
|
2905
|
-
if (existing.data.length > 0) {
|
|
2906
|
-
const record = await this.client.put(`/records/${existing.data[0].id}`, {
|
|
2907
|
-
metadata,
|
|
2908
|
-
});
|
|
2909
|
-
return record.id;
|
|
2910
|
-
}
|
|
2911
|
-
// Create new record
|
|
2912
|
-
const record = await this.client.post('/records', {
|
|
2913
|
-
type: 'agent-task',
|
|
2914
|
-
name: state.taskName,
|
|
2915
|
-
metadata,
|
|
2916
|
-
});
|
|
2917
|
-
return record.id;
|
|
2918
|
-
}
|
|
2919
|
-
}
|
|
2920
|
-
catch {
|
|
2921
|
-
// Record sync is best-effort — don't fail the task
|
|
2922
|
-
return existingRecordId || '';
|
|
2923
|
-
}
|
|
2924
|
-
}
|
|
2925
|
-
}
|
|
2926
|
-
exports.AgentsEndpoint = AgentsEndpoint;
|
|
2927
|
-
/** Stop phrases that indicate the agent considers its task complete. */
|
|
2928
|
-
AgentsEndpoint.STOP_PHRASES = [
|
|
2929
|
-
'DONE:',
|
|
2930
|
-
'TASK_COMPLETE',
|
|
2931
|
-
'FINISHED',
|
|
2932
|
-
'[COMPLETE]',
|
|
2933
|
-
'STATUS: RESOLVED',
|
|
2934
|
-
'STATUS: COMPLETE',
|
|
2935
|
-
];
|
|
2936
|
-
//# sourceMappingURL=endpoints.js.map
|