@omerrgocmen/crewctl 1.0.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +193 -193
- package/orchestrator/README.md +473 -462
- package/orchestrator/config.default.json +79 -71
- package/orchestrator/roles/executor.md +57 -57
- package/orchestrator/roles/operator.md +77 -77
- package/orchestrator/roles/planner.md +58 -58
- package/orchestrator/roles/reviewer.md +63 -63
- package/orchestrator/skills/acceptance-criteria.md +18 -18
- package/orchestrator/skills/accessibility-audit.md +16 -16
- package/orchestrator/skills/accessible-forms.md +16 -16
- package/orchestrator/skills/api-design.md +27 -27
- package/orchestrator/skills/api-documentation.md +16 -16
- package/orchestrator/skills/architecture-decision-record.md +18 -18
- package/orchestrator/skills/authentication-design.md +16 -16
- package/orchestrator/skills/authorization-review.md +16 -16
- package/orchestrator/skills/backward-compatibility.md +17 -17
- package/orchestrator/skills/changelog-writing.md +16 -16
- package/orchestrator/skills/ci-pipeline-design.md +16 -16
- package/orchestrator/skills/cli-design.md +16 -16
- package/orchestrator/skills/code-review.md +27 -27
- package/orchestrator/skills/configuration-management.md +16 -16
- package/orchestrator/skills/container-review.md +16 -16
- package/orchestrator/skills/contract-testing.md +16 -16
- package/orchestrator/skills/dashboard-design.md +16 -16
- package/orchestrator/skills/database-migration.md +16 -16
- package/orchestrator/skills/database-schema-design.md +16 -16
- package/orchestrator/skills/debugging.md +26 -26
- package/orchestrator/skills/dependency-review.md +16 -16
- package/orchestrator/skills/design-review.md +29 -29
- package/orchestrator/skills/design-system.md +16 -16
- package/orchestrator/skills/docs-api-reference.md +16 -16
- package/orchestrator/skills/docs-troubleshooting.md +16 -16
- package/orchestrator/skills/docs-tutorial.md +16 -16
- package/orchestrator/skills/docs-writing.md +25 -25
- package/orchestrator/skills/empty-error-loading-states.md +16 -16
- package/orchestrator/skills/end-to-end-testing.md +16 -16
- package/orchestrator/skills/error-handling.md +16 -16
- package/orchestrator/skills/frontend-design.md +28 -28
- package/orchestrator/skills/git-commit-writing.md +16 -16
- package/orchestrator/skills/graphql-design.md +16 -16
- package/orchestrator/skills/incident-runbook.md +18 -18
- package/orchestrator/skills/input-validation.md +16 -16
- package/orchestrator/skills/integration-testing.md +16 -16
- package/orchestrator/skills/interaction-design.md +16 -16
- package/orchestrator/skills/landing-page-design.md +16 -16
- package/orchestrator/skills/observability-design.md +16 -16
- package/orchestrator/skills/openapi-contract.md +16 -16
- package/orchestrator/skills/performance-profiling.md +16 -16
- package/orchestrator/skills/privacy-review.md +16 -16
- package/orchestrator/skills/property-based-testing.md +16 -16
- package/orchestrator/skills/pull-request-writing.md +16 -16
- package/orchestrator/skills/refactoring.md +16 -16
- package/orchestrator/skills/release-readiness.md +16 -16
- package/orchestrator/skills/responsive-design.md +16 -16
- package/orchestrator/skills/secrets-management.md +16 -16
- package/orchestrator/skills/secure-file-upload.md +16 -16
- package/orchestrator/skills/security-review.md +26 -26
- package/orchestrator/skills/semantic-versioning.md +17 -17
- package/orchestrator/skills/seo-on-page.md +16 -16
- package/orchestrator/skills/seo-structured-data.md +16 -16
- package/orchestrator/skills/seo-technical-audit.md +16 -16
- package/orchestrator/skills/sql-query-review.md +16 -16
- package/orchestrator/skills/supply-chain-security.md +16 -16
- package/orchestrator/skills/test-strategy.md +16 -16
- package/orchestrator/skills/threat-modeling.md +16 -16
- package/orchestrator/skills/unit-testing.md +16 -16
- package/orchestrator/skills/write-tests.md +28 -28
- package/orchestrator/src/cli-registry.js +740 -616
- package/orchestrator/src/cli.js +140 -135
- package/orchestrator/src/doctor.js +64 -64
- package/orchestrator/src/engine.js +1735 -1552
- package/orchestrator/src/schedule.js +126 -126
- package/orchestrator/src/server.js +818 -766
- package/orchestrator/src/skill-registry.js +272 -272
- package/orchestrator/src/store.js +470 -399
- package/orchestrator/web/OrbitControls.js +1417 -1417
- package/orchestrator/web/board.html +146 -146
- package/orchestrator/web/code.html +213 -213
- package/orchestrator/web/flow.html +741 -741
- package/orchestrator/web/index.html +602 -588
- package/orchestrator/web/jsm/postprocessing/EffectComposer.js +231 -231
- package/orchestrator/web/jsm/postprocessing/MaskPass.js +104 -104
- package/orchestrator/web/jsm/postprocessing/Pass.js +95 -95
- package/orchestrator/web/jsm/postprocessing/RenderPass.js +99 -99
- package/orchestrator/web/jsm/postprocessing/ShaderPass.js +77 -77
- package/orchestrator/web/jsm/postprocessing/UnrealBloomPass.js +415 -415
- package/orchestrator/web/jsm/shaders/CopyShader.js +45 -45
- package/orchestrator/web/jsm/shaders/LuminosityHighPassShader.js +66 -66
- package/orchestrator/web/site.webmanifest +12 -12
- package/orchestrator/web/three.module.min.js +6 -6
- package/package.json +51 -51
|
@@ -1,1552 +1,1735 @@
|
|
|
1
|
-
const { spawn } = require("child_process");
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const path = require("path");
|
|
4
|
-
const { EventEmitter } = require("events");
|
|
5
|
-
const store = require("./store");
|
|
6
|
-
const cliRegistry = require("./cli-registry");
|
|
7
|
-
const skillRegistry = require("./skill-registry");
|
|
8
|
-
const checkpoints = require("./checkpoints");
|
|
9
|
-
|
|
10
|
-
const isWin = process.platform === "win32";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
return
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if (
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
function
|
|
417
|
-
if (
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
function
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
function
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
//
|
|
698
|
-
//
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
this.
|
|
714
|
-
this.
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
//
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
const cfg = this.cfg();
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
this.
|
|
753
|
-
this.
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
this.
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
const
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
this.
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
(
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
(
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
if (
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
this.
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
})
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
if (
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
//
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1
|
+
const { spawn } = require("child_process");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { EventEmitter } = require("events");
|
|
5
|
+
const store = require("./store");
|
|
6
|
+
const cliRegistry = require("./cli-registry");
|
|
7
|
+
const skillRegistry = require("./skill-registry");
|
|
8
|
+
const checkpoints = require("./checkpoints");
|
|
9
|
+
|
|
10
|
+
const isWin = process.platform === "win32";
|
|
11
|
+
|
|
12
|
+
// Ajan hapsi (path-aware, Claude icin). cfg.sandbox.mode === "workspace" iken orkestratorun
|
|
13
|
+
// kendi dizinlerini (veri koku ROOT = config/memory/state, paket ASSETS, kurulum WORK_BASE)
|
|
14
|
+
// Claude'un gozunden gizler: Read/Edit/Write bu yollara permissions.deny ile kapatilir.
|
|
15
|
+
// Docker/Git GEREKTIRMEZ. Codex ayni korumayi effectiveAgent'ta OS-native sandbox ile alir;
|
|
16
|
+
// gemini/opencode icin path-scoped native kural olmadigindan yalnizca prompt talimati gecerli.
|
|
17
|
+
// Calisma klasoru bu korunan dizinlerden birinin ICINDEYSE (or. gelistirme modunda repo'nun
|
|
18
|
+
// kendisinde calismak) o dizin KORUNMAZ — mevcut is bozulmasin diye.
|
|
19
|
+
function claudeSandboxSettings(agent, cwd, cfg) {
|
|
20
|
+
if (cfg?.sandbox?.mode !== "workspace") return null;
|
|
21
|
+
const adapter = agent?.adapter || cliRegistry.adapterId(agent?.cmd);
|
|
22
|
+
if (adapter !== "claude") return null;
|
|
23
|
+
const norm = (p) => path.resolve(p).replace(/\\/g, "/");
|
|
24
|
+
// Windows dosya sistemi buyuk/kucuk harfe duyarsizdir: C:\Test ile c:\test ayni dizindir.
|
|
25
|
+
// Karsilastirmayi case-fold'la yap; aksi halde farkli case yazilmis bir cwd, kurulum
|
|
26
|
+
// dizinini yanlislikla "disarida" sanip DENY eder (ajan kendi calisma agacinin ustune
|
|
27
|
+
// erisemez, hatta cwd==kurulum dizini iken kendi klasorune yazamaz).
|
|
28
|
+
const fold = (p) => (isWin ? p.toLowerCase() : p);
|
|
29
|
+
const workDir = norm(cwd);
|
|
30
|
+
const workCmp = fold(workDir);
|
|
31
|
+
const overlaps = (dir) => {
|
|
32
|
+
const d = fold(dir);
|
|
33
|
+
return d === workCmp || d.startsWith(workCmp + "/") || workCmp.startsWith(d + "/");
|
|
34
|
+
};
|
|
35
|
+
const protectedDirs = [store.ROOT, store.ASSETS, store.WORK_BASE]
|
|
36
|
+
.map(norm)
|
|
37
|
+
.filter((dir, i, all) => all.findIndex((other) => fold(other) === fold(dir)) === i)
|
|
38
|
+
.filter((dir) => !overlaps(dir));
|
|
39
|
+
if (!protectedDirs.length) return null;
|
|
40
|
+
const deny = [];
|
|
41
|
+
for (const dir of protectedDirs) {
|
|
42
|
+
for (const tool of ["Read", "Edit", "Write"]) deny.push(`${tool}(${dir}/**)`);
|
|
43
|
+
}
|
|
44
|
+
return { permissions: { deny } };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function snapshotDir(dir) {
|
|
48
|
+
const map = new Map();
|
|
49
|
+
const ignored = (rel) => {
|
|
50
|
+
const r = rel.replace(/\\/g, "/");
|
|
51
|
+
// .crewctl/ orkestratorun kendi proje-context'ini (CONTEXT.md) tutar; gorev diff'lerinde,
|
|
52
|
+
// live-diff'te ve teslimat dosya listesinde gozukmesin (kullanicinin isi degil).
|
|
53
|
+
return r.includes("node_modules") || r.includes(".git/") || r.startsWith(".git") ||
|
|
54
|
+
r === ".crewctl" || r.startsWith(".crewctl/") ||
|
|
55
|
+
r.startsWith("orchestrator/queue") || r.startsWith("orchestrator/state") ||
|
|
56
|
+
r.startsWith("orchestrator/memory") || r.startsWith("orchestrator/node_modules");
|
|
57
|
+
};
|
|
58
|
+
let count = 0;
|
|
59
|
+
const walk = (abs, rel) => {
|
|
60
|
+
if (count > 12000) return;
|
|
61
|
+
let entries;
|
|
62
|
+
try { entries = fs.readdirSync(abs, { withFileTypes: true }); } catch { return; }
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
65
|
+
if (ignored(childRel)) continue;
|
|
66
|
+
const childAbs = path.join(abs, entry.name);
|
|
67
|
+
if (entry.isDirectory()) walk(childAbs, childRel);
|
|
68
|
+
else {
|
|
69
|
+
try {
|
|
70
|
+
const s = fs.statSync(childAbs);
|
|
71
|
+
map.set(childRel, `${s.mtimeMs}:${s.size}`);
|
|
72
|
+
count++;
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
walk(dir, "");
|
|
78
|
+
return map;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function diffSnapshots(before, after) {
|
|
82
|
+
const created = [], modified = [], deleted = [];
|
|
83
|
+
for (const [file, stamp] of after) {
|
|
84
|
+
if (!before.has(file)) created.push(file);
|
|
85
|
+
else if (before.get(file) !== stamp) modified.push(file);
|
|
86
|
+
}
|
|
87
|
+
for (const file of before.keys()) if (!after.has(file)) deleted.push(file);
|
|
88
|
+
return { created: created.sort(), modified: modified.sort(), deleted: deleted.sort() };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const DIFF_MAX_FILE_BYTES = 256 * 1024;
|
|
92
|
+
const DIFF_MAX_BASELINE_BYTES = 24 * 1024 * 1024;
|
|
93
|
+
const DIFF_MAX_BASELINE_FILES = 2000;
|
|
94
|
+
const DIFF_MAX_MATRIX_CELLS = 250000;
|
|
95
|
+
const DIFF_CONTEXT_LINES = 3;
|
|
96
|
+
const DIFF_MAX_RENDERED_LINES = 240;
|
|
97
|
+
const DIFF_MAX_EVENT_LINES = 1000;
|
|
98
|
+
const DIFF_MAX_SOURCE_LINES = 5000;
|
|
99
|
+
|
|
100
|
+
function isSensitiveDiffPath(file) {
|
|
101
|
+
const normalized = String(file || "").replace(/\\/g, "/").toLowerCase();
|
|
102
|
+
const base = path.posix.basename(normalized);
|
|
103
|
+
return /^\.env(?:\.|$)/.test(base) || [".npmrc", ".pypirc", ".netrc"].includes(base) ||
|
|
104
|
+
/(^|[._-])(secret|secrets|credential|credentials)([._-]|$)/.test(base) ||
|
|
105
|
+
/\.(pem|key|p12|pfx)$/.test(base) || /^id_(rsa|dsa|ecdsa|ed25519)(\.pub)?$/.test(base);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function readDiffText(file, maxBytes = DIFF_MAX_FILE_BYTES) {
|
|
109
|
+
try {
|
|
110
|
+
const stat = fs.statSync(file);
|
|
111
|
+
if (!stat.isFile()) return { ok: false, status: "unavailable" };
|
|
112
|
+
if (stat.size > maxBytes) return { ok: false, status: "too-large", size: stat.size };
|
|
113
|
+
const buffer = fs.readFileSync(file);
|
|
114
|
+
if (buffer.includes(0)) return { ok: false, status: "binary", size: stat.size };
|
|
115
|
+
let controls = 0;
|
|
116
|
+
for (const byte of buffer) {
|
|
117
|
+
if (byte < 32 && byte !== 9 && byte !== 10 && byte !== 13) controls++;
|
|
118
|
+
}
|
|
119
|
+
if (buffer.length && controls / buffer.length > 0.02) return { ok: false, status: "binary", size: stat.size };
|
|
120
|
+
return { ok: true, text: buffer.toString("utf8"), size: stat.size };
|
|
121
|
+
} catch {
|
|
122
|
+
return { ok: false, status: "unavailable" };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Gorev basindaki metin iceriklerini sinirli bir butceyle saklar. Bu taban, Git deposu
|
|
127
|
+
// olmayan klasorlerde de modified/deleted dosyalar icin satir diff'i uretebilmemizi saglar.
|
|
128
|
+
function captureTextSnapshot(dir, snapshot, options = {}) {
|
|
129
|
+
const maxBytes = Math.max(1024, Number(options.maxBytes) || DIFF_MAX_BASELINE_BYTES);
|
|
130
|
+
const maxFiles = Math.max(1, Number(options.maxFiles) || DIFF_MAX_BASELINE_FILES);
|
|
131
|
+
const maxFileBytes = Math.max(1024, Number(options.maxFileBytes) || DIFF_MAX_FILE_BYTES);
|
|
132
|
+
const result = new Map();
|
|
133
|
+
let bytes = 0, files = 0;
|
|
134
|
+
for (const file of [...(snapshot?.keys?.() || [])].sort()) {
|
|
135
|
+
if (isSensitiveDiffPath(file)) {
|
|
136
|
+
result.set(file, { ok: false, status: "redacted" });
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (files >= maxFiles || bytes >= maxBytes) {
|
|
140
|
+
result.set(file, { ok: false, status: "baseline-limit" });
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const value = readDiffText(path.join(dir, file), maxFileBytes);
|
|
144
|
+
if (value.ok && bytes + value.size > maxBytes) {
|
|
145
|
+
result.set(file, { ok: false, status: "baseline-limit" });
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
result.set(file, value);
|
|
149
|
+
if (value.ok) { bytes += value.size; files++; }
|
|
150
|
+
}
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function splitDiffLines(text) {
|
|
155
|
+
const value = String(text || "").replace(/\r\n/g, "\n");
|
|
156
|
+
if (!value) return [];
|
|
157
|
+
const lines = value.split("\n");
|
|
158
|
+
if (lines[lines.length - 1] === "") lines.pop();
|
|
159
|
+
return lines;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function lineOperations(beforeText, afterText) {
|
|
163
|
+
const before = splitDiffLines(beforeText), after = splitDiffLines(afterText);
|
|
164
|
+
const cells = before.length * after.length;
|
|
165
|
+
let approximate = false;
|
|
166
|
+
const operations = [];
|
|
167
|
+
if (cells <= DIFF_MAX_MATRIX_CELLS) {
|
|
168
|
+
const width = after.length + 1;
|
|
169
|
+
const table = new Uint32Array((before.length + 1) * width);
|
|
170
|
+
for (let i = before.length - 1; i >= 0; i--) {
|
|
171
|
+
for (let j = after.length - 1; j >= 0; j--) {
|
|
172
|
+
table[i * width + j] = before[i] === after[j]
|
|
173
|
+
? table[(i + 1) * width + j + 1] + 1
|
|
174
|
+
: Math.max(table[(i + 1) * width + j], table[i * width + j + 1]);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
let i = 0, j = 0;
|
|
178
|
+
while (i < before.length || j < after.length) {
|
|
179
|
+
if (i < before.length && j < after.length && before[i] === after[j]) {
|
|
180
|
+
operations.push({ type: "context", text: before[i++] }); j++;
|
|
181
|
+
} else if (i < before.length && (j >= after.length || table[(i + 1) * width + j] >= table[i * width + j + 1])) {
|
|
182
|
+
operations.push({ type: "delete", text: before[i++] });
|
|
183
|
+
} else {
|
|
184
|
+
operations.push({ type: "add", text: after[j++] });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
// Cok buyuk dosyalarda karesel matris kurma. Ortak bas/sonu koruyup degisen orta
|
|
189
|
+
// bolumu tek hunk olarak goster; UI bu sonucu yaklasik olarak etiketler.
|
|
190
|
+
approximate = true;
|
|
191
|
+
let prefix = 0;
|
|
192
|
+
while (prefix < before.length && prefix < after.length && before[prefix] === after[prefix]) prefix++;
|
|
193
|
+
let suffix = 0;
|
|
194
|
+
while (suffix < before.length - prefix && suffix < after.length - prefix &&
|
|
195
|
+
before[before.length - 1 - suffix] === after[after.length - 1 - suffix]) suffix++;
|
|
196
|
+
for (let i = 0; i < prefix; i++) operations.push({ type: "context", text: before[i] });
|
|
197
|
+
for (let i = prefix; i < before.length - suffix; i++) operations.push({ type: "delete", text: before[i] });
|
|
198
|
+
for (let i = prefix; i < after.length - suffix; i++) operations.push({ type: "add", text: after[i] });
|
|
199
|
+
for (let i = suffix; i > 0; i--) operations.push({ type: "context", text: before[before.length - i] });
|
|
200
|
+
}
|
|
201
|
+
return { operations, approximate };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function buildLineDiff(beforeText, afterText, options = {}) {
|
|
205
|
+
const context = Math.max(0, Number(options.context) || DIFF_CONTEXT_LINES);
|
|
206
|
+
const maxLines = Math.max(20, Number(options.maxLines) || DIFF_MAX_RENDERED_LINES);
|
|
207
|
+
const { operations, approximate } = lineOperations(beforeText, afterText);
|
|
208
|
+
let oldNo = 1, newNo = 1;
|
|
209
|
+
const annotated = operations.map((operation) => {
|
|
210
|
+
const line = { ...operation, oldNumber: null, newNumber: null };
|
|
211
|
+
if (operation.type !== "add") line.oldNumber = oldNo++;
|
|
212
|
+
if (operation.type !== "delete") line.newNumber = newNo++;
|
|
213
|
+
return line;
|
|
214
|
+
});
|
|
215
|
+
const additions = annotated.filter((line) => line.type === "add").length;
|
|
216
|
+
const deletions = annotated.filter((line) => line.type === "delete").length;
|
|
217
|
+
const ranges = [];
|
|
218
|
+
for (let i = 0; i < annotated.length; i++) {
|
|
219
|
+
if (annotated[i].type === "context") continue;
|
|
220
|
+
const start = Math.max(0, i - context), end = Math.min(annotated.length, i + context + 1);
|
|
221
|
+
const last = ranges[ranges.length - 1];
|
|
222
|
+
if (last && start <= last.end) last.end = Math.max(last.end, end);
|
|
223
|
+
else ranges.push({ start, end });
|
|
224
|
+
}
|
|
225
|
+
const hunks = [];
|
|
226
|
+
let rendered = 0, truncated = false;
|
|
227
|
+
for (const range of ranges) {
|
|
228
|
+
if (rendered >= maxLines) { truncated = true; break; }
|
|
229
|
+
const oldStart = 1 + annotated.slice(0, range.start).filter((line) => line.type !== "add").length;
|
|
230
|
+
const newStart = 1 + annotated.slice(0, range.start).filter((line) => line.type !== "delete").length;
|
|
231
|
+
const available = Math.max(0, maxLines - rendered);
|
|
232
|
+
const selected = annotated.slice(range.start, Math.min(range.end, range.start + available));
|
|
233
|
+
if (selected.length < range.end - range.start) truncated = true;
|
|
234
|
+
hunks.push({
|
|
235
|
+
oldStart,
|
|
236
|
+
oldLines: selected.filter((line) => line.type !== "add").length,
|
|
237
|
+
newStart,
|
|
238
|
+
newLines: selected.filter((line) => line.type !== "delete").length,
|
|
239
|
+
lines: selected.map((line) => ({ ...line, text: String(line.text).slice(0, 1000) })),
|
|
240
|
+
});
|
|
241
|
+
rendered += selected.length;
|
|
242
|
+
}
|
|
243
|
+
return { additions, deletions, hunks, truncated, approximate };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function describeFileDiff(root, file, action, baseline, options = {}) {
|
|
247
|
+
const base = action === "created" ? { ok: true, text: "" } : baseline?.get(file);
|
|
248
|
+
const current = action === "deleted" ? { ok: true, text: "" } :
|
|
249
|
+
(isSensitiveDiffPath(file) ? { ok: false, status: "redacted" } : readDiffText(path.join(root, file)));
|
|
250
|
+
const unavailable = !base?.ok ? base : (!current?.ok ? current : null);
|
|
251
|
+
if (unavailable) return { path: file, action, additions: null, deletions: null, previewStatus: unavailable.status || "unavailable", hunks: [] };
|
|
252
|
+
const lineCount = (text) => text ? (String(text).match(/\n/g) || []).length + 1 : 0;
|
|
253
|
+
if (lineCount(base.text) > DIFF_MAX_SOURCE_LINES || lineCount(current.text) > DIFF_MAX_SOURCE_LINES) {
|
|
254
|
+
return { path: file, action, additions: null, deletions: null, previewStatus: "too-many-lines", hunks: [] };
|
|
255
|
+
}
|
|
256
|
+
return { path: file, action, ...buildLineDiff(base.text, current.text, options) };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function clip(value, limit = 12000) {
|
|
260
|
+
const text = String(value || "");
|
|
261
|
+
return text.length <= limit ? text : `${text.slice(0, limit)}\n...[kesildi]`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Sohbet odakli CLI'lar profili bazen ``` ile sarar; profil dosyasina ham metni yazariz.
|
|
265
|
+
// Yalnizca tum icerigi saran TEK bir dis fence'i soyar; ic kod bloklarina dokunmaz.
|
|
266
|
+
function stripCodeFence(text) {
|
|
267
|
+
const trimmed = String(text || "").trim();
|
|
268
|
+
const match = trimmed.match(/^```[a-zA-Z0-9]*\s*\n([\s\S]*?)\n?```$/);
|
|
269
|
+
return match ? match[1] : trimmed;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Bas ve sonu koruyarak kirpar. Denetci raporlarinda VERDICT satiri metnin SONUNDadir;
|
|
273
|
+
// yalnizca bastan kirpmak operatorun karari gormemesine ve gereksiz yeniden inceleme
|
|
274
|
+
// turlarina yol acar.
|
|
275
|
+
function clipMiddle(value, limit = 12000) {
|
|
276
|
+
const text = String(value || "");
|
|
277
|
+
if (text.length <= limit) return text;
|
|
278
|
+
const head = Math.ceil(limit * 0.6);
|
|
279
|
+
const tail = Math.max(1, limit - head);
|
|
280
|
+
return `${text.slice(0, head)}\n...[orta kisim kesildi]...\n${text.slice(text.length - tail)}`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function extractVerdict(text) {
|
|
284
|
+
const matches = String(text || "").match(/VERDICT:\s*(PASS|FAIL)/gi);
|
|
285
|
+
if (!matches || !matches.length) return null;
|
|
286
|
+
return /PASS/i.test(matches[matches.length - 1]) ? "PASS" : "FAIL";
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Siniflandirilamayan hatada "gemini cikis kodu 1." gibi bilgisiz bir ilk satir yerine metnin
|
|
290
|
+
// icindeki GERCEK hata cumlesini bulur; kullaniciya gosterilen tek satir buysa anlamli olmali.
|
|
291
|
+
function meaningfulErrorLine(raw) {
|
|
292
|
+
const all = String(raw).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
293
|
+
// Yigin izleri ve motorun kendi sardigi "<cli> cikis kodu N." satiri teshis tasimaz;
|
|
294
|
+
// gercek neden hemen arkalarindaki satirdadir.
|
|
295
|
+
const useful = all.filter((line) => !/^at\s|^node:internal|^[{}[\]]+$/.test(line) && !/cikis kodu/i.test(line));
|
|
296
|
+
const signal = useful.find((line) => /error|hata|failed|failure|denied|invalid|exceeded|limit|quota|unable|cannot|must/i.test(line) && line.length > 20);
|
|
297
|
+
return signal || useful[0] || all[0] || String(raw).trim();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Ham CLI ciktisini kullaniciya "ne oldu + ne yapmali" seklinde anlamli bir teshise cevirir.
|
|
301
|
+
// SIRA ONEMLIDIR: spesifik desenler once gelmeli. Ornegin Gemini'nin "GEMINI_API_KEY ...
|
|
302
|
+
// not found" mesaji bir PATH/kurulum sorunu DEGILDIR; genis /not found/ deseni bu yuzden
|
|
303
|
+
// daraltildi ve kimlik kontrollerinin arkasina alindi.
|
|
304
|
+
function classifyCliError(error) {
|
|
305
|
+
const raw = String(error?.message || error || "Bilinmeyen CLI hatasi");
|
|
306
|
+
const of = (code, summary, action) => ({ code, summary, action, raw: clip(raw, 5000) });
|
|
307
|
+
if (/requires a newer version|upgrade to the latest|model metadata.*not found|unsupported.*model|model.*unsupported/i.test(raw)) {
|
|
308
|
+
return of("VERSION_INCOMPATIBLE", "CLI sürümü seçilen modeli desteklemiyor.", "CLI aracını güncelleyin veya desteklenen bir model seçin.");
|
|
309
|
+
}
|
|
310
|
+
if (/sessiz kaldi|cikti uretmedi|CLI_STALLED/i.test(raw)) {
|
|
311
|
+
return of("CLI_STALLED", "CLI uzun süre yeni çıktı üretmeyince otomatik durduruldu.", "Önceki ilerleme kayıtları korundu; operatör bu oturumda farklı bir agent kullanacak.");
|
|
312
|
+
}
|
|
313
|
+
if (/API key not valid|API_KEY_INVALID|invalid[_ ]api[_ ]key|incorrect api key/i.test(raw)) {
|
|
314
|
+
return of("AUTH_INVALID", "API anahtarı geçersiz veya reddedildi.", "Agent ayarlarındaki API anahtarını yenileyin (Gemini için GEMINI_API_KEY / GOOGLE_API_KEY).");
|
|
315
|
+
}
|
|
316
|
+
// Gemini CLI oturum acilmadiginda "Please set an Auth method" / "GEMINI_API_KEY environment
|
|
317
|
+
// variable not found" yazar; her ikisi de kurulum degil OTURUM sorunudur.
|
|
318
|
+
if (/set an auth method|GEMINI_API_KEY|GOOGLE_API_KEY|unauthorized|unauthenticated|authentication|login required|not logged in|please (run )?login|credentials? (not found|missing|expired)|PERMISSION_DENIED|\b40[13]\b/i.test(raw)) {
|
|
319
|
+
return of("AUTH_REQUIRED", "CLI oturum açmamış; kimlik doğrulaması gerekiyor.", "Terminalde ilgili CLI ile oturum açın (Gemini: `gemini` çalıştırıp 'Login with Google' veya GEMINI_API_KEY tanımlayın), sonra görevi tekrar deneyin.");
|
|
320
|
+
}
|
|
321
|
+
// Kota (gunluk/aylik hak bitti) ile hiz siniri (kisa sureli) AYRI teshislerdir: ilki
|
|
322
|
+
// beklemekle gecmez, ikincisi gecer. Kullanicinin sordugu "limitim mi bitti" ayrimi budur.
|
|
323
|
+
if (/RESOURCE_EXHAUSTED|quota exceeded|exceeded your current quota|daily limit|günlük limit|gunluk limit|out of credits|insufficient (credit|balance|quota)|billing/i.test(raw)) {
|
|
324
|
+
return of("QUOTA_EXCEEDED", "Sağlayıcı kotanız doldu (günlük/aylık hak veya bakiye bitti).", "Kota yenilenene kadar bu agent kullanılamaz; başka bir agent seçin, faturalandırmayı yükseltin veya API anahtarını kotası olan bir hesapla değiştirin.");
|
|
325
|
+
}
|
|
326
|
+
if (/rate.?limit|too many requests|\b429\b/i.test(raw)) {
|
|
327
|
+
return of("RATE_LIMIT", "İstek hızı sınırına takıldı (kota bitmedi, çok sık istek gönderildi).", "Kısa bir süre bekleyip tekrar deneyin; operatör bu turda alternatif bir agent kullanabilir.");
|
|
328
|
+
}
|
|
329
|
+
if (/overloaded|\bUNAVAILABLE\b|\b(503|500)\b|internal error|try again later/i.test(raw)) {
|
|
330
|
+
return of("MODEL_OVERLOADED", "Model sağlayıcısı geçici olarak aşırı yüklü ya da hata döndürdü.", "Sağlayıcı kaynaklı geçici bir sorun; birkaç dakika sonra tekrar deneyin.");
|
|
331
|
+
}
|
|
332
|
+
if (/location is not supported|not available in your country|region.*not supported/i.test(raw)) {
|
|
333
|
+
return of("REGION_BLOCKED", "Model bulunduğunuz bölgede kullanıma kapalı.", "Desteklenen bir bölge/hesap kullanın veya başka bir sağlayıcının agent'ını seçin.");
|
|
334
|
+
}
|
|
335
|
+
if (/ConnectionRefused|Unable to connect|provider hatasi|provider error|ECONNREFUSED/i.test(raw)) {
|
|
336
|
+
return of("PROVIDER_UNAVAILABLE", "Seçilen model sağlayıcısına bağlanılamadı.", "Sağlayıcı bağlantısını kontrol edin veya başka bir model seçin.");
|
|
337
|
+
}
|
|
338
|
+
if (/ENOTFOUND|EAI_AGAIN|ECONNRESET|ETIMEDOUT|getaddrinfo|fetch failed|socket hang up|network (error|unreachable)|proxy/i.test(raw)) {
|
|
339
|
+
return of("NETWORK_ERROR", "Ağ bağlantısı kurulamadı (DNS/proxy/internet).", "İnternet veya kurumsal proxy ayarlarınızı kontrol edip görevi tekrar çalıştırın.");
|
|
340
|
+
}
|
|
341
|
+
if (/not recognized as an internal|is not recognized|command not found|spawn .*ENOENT|\bENOENT\b|no such file or directory/i.test(raw)) {
|
|
342
|
+
return of("CLI_NOT_FOUND", "CLI komutu bulunamadı; kurulu değil veya PATH üzerinde görünmüyor.", "CLI'yi kurun (Gemini: `npm install -g @google/gemini-cli`) ve Ayarlar → Agent'lar bölümünde komut adını doğrulayın.");
|
|
343
|
+
}
|
|
344
|
+
if (/timeout|timed out|zaman asim|zaman aşım/i.test(raw)) {
|
|
345
|
+
return of("TIMEOUT", "CLI ayrılan süre içinde tamamlanamadı.", "Agent ayarlarından timeout değerini artırın veya görevi daha küçük parçalara bölün.");
|
|
346
|
+
}
|
|
347
|
+
return of("CLI_FAILED", clip(meaningfulErrorLine(raw), 500), "Ham CLI çıktısı teknik ayrıntılarda saklandı; sorun sürerse alternatif bir agent deneyin.");
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const RECOVERABLE_CLI_ERRORS = new Set(["AUTH_INVALID", "AUTH_REQUIRED", "RATE_LIMIT", "QUOTA_EXCEEDED", "MODEL_OVERLOADED", "NETWORK_ERROR", "REGION_BLOCKED", "PROVIDER_UNAVAILABLE", "CLI_NOT_FOUND", "TIMEOUT", "CLI_STALLED", "CLI_FAILED", "VERSION_INCOMPATIBLE"]);
|
|
351
|
+
// Karantina = bu oturumda tekrar denemek anlamsiz. Kota/bolge kisiti beklemekle gecmez;
|
|
352
|
+
// hiz siniri (RATE_LIMIT) ve gecici saglayici hatasi (MODEL_OVERLOADED) gecer, karantinaya alinmaz.
|
|
353
|
+
const QUARANTINE_CLI_ERRORS = new Set(["AUTH_INVALID", "AUTH_REQUIRED", "QUOTA_EXCEEDED", "REGION_BLOCKED", "PROVIDER_UNAVAILABLE", "CLI_NOT_FOUND", "CLI_STALLED", "VERSION_INCOMPATIBLE"]);
|
|
354
|
+
|
|
355
|
+
function resolveExecutionMode(task) {
|
|
356
|
+
if (["fast", "balanced", "deep"].includes(task.executionMode)) return task.executionMode;
|
|
357
|
+
const prompt = String(task.prompt || "");
|
|
358
|
+
const complex = /(mimari|migration|refactor|guvenlik|güvenlik|deploy|production|veritabani|veritabanı|authentication|entegrasyon|çoklu|multi|kapsamli|kapsamlı)/i.test(prompt);
|
|
359
|
+
// Not: "oyun/sayfa" gibi sifirdan build isleri BASIT sayilmaz — bunlar plan+uygula+incele
|
|
360
|
+
// gerektiren gercek gorevlerdir; fast moda dusurulup ekip kullanilmadan gecilmemeli.
|
|
361
|
+
// Not: "dosya" gibi genis kelimeler fast'a dusurmez; yorumdaki ilkeyle celisiyordu
|
|
362
|
+
// (sifirdan build/dosya olusturma gercek bir gorevdir). Yalnizca acik "kucuk/hizli" sinyalleri.
|
|
363
|
+
const simple = prompt.length < 350 && /(basit|ufak|küçük|kucuk|hizli|hızlı|simple|small|quick)/i.test(prompt);
|
|
364
|
+
return simple && !complex ? "fast" : "balanced";
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function applyExecutionPolicy(base, mode) {
|
|
368
|
+
const cfg = { ...base, operator: { ...(base.operator || {}) } };
|
|
369
|
+
if (mode === "fast") {
|
|
370
|
+
cfg.operator.maxRounds = Math.min(cfg.operator.maxRounds || 6, 2);
|
|
371
|
+
cfg.operator.maxDelegationsPerRound = 1;
|
|
372
|
+
cfg.memoryCharBudget = Math.min(cfg.memoryCharBudget || 8000, 2500);
|
|
373
|
+
cfg.teamContextCharBudget = Math.min(cfg.teamContextCharBudget || 30000, 10000);
|
|
374
|
+
} else if (mode === "balanced") {
|
|
375
|
+
// Saglikli bir balanced gorev tek turda biter (uygulama + zincirli inceleme);
|
|
376
|
+
// 3 tur, iki duzeltme/yeniden dogrulama turuna yer birakir.
|
|
377
|
+
cfg.operator.maxRounds = Math.min(cfg.operator.maxRounds || 6, 3);
|
|
378
|
+
cfg.operator.maxDelegationsPerRound = Math.min(cfg.operator.maxDelegationsPerRound || 8, 3);
|
|
379
|
+
cfg.memoryCharBudget = Math.min(cfg.memoryCharBudget || 8000, 6000);
|
|
380
|
+
cfg.teamContextCharBudget = Math.min(cfg.teamContextCharBudget || 30000, 24000);
|
|
381
|
+
}
|
|
382
|
+
return cfg;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const EMPTY_USAGE = { input: 0, output: 0, reasoning: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
386
|
+
|
|
387
|
+
function addUsage(target, extra) {
|
|
388
|
+
const sum = { ...(target || EMPTY_USAGE) };
|
|
389
|
+
for (const key of Object.keys(EMPTY_USAGE)) sum[key] = (sum[key] || 0) + (Number(extra?.[key]) || 0);
|
|
390
|
+
return sum;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function usageTotal(usage) {
|
|
394
|
+
return (Number(usage?.input) || 0) + (Number(usage?.output) || 0);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// OpenCode'un step_finish olayindaki token/maliyet alanlarini normalize eder. Alan adlari
|
|
398
|
+
// surumler arasinda degisebildigi icin eksik alan 0 sayilir; hicbir sayi yoksa null doner
|
|
399
|
+
// ki "veri yok" ile "sifir maliyet" ayirt edilebilsin.
|
|
400
|
+
function readStepUsage(event) {
|
|
401
|
+
const tokens = event.part?.tokens || event.tokens;
|
|
402
|
+
const cost = Number(event.part?.cost ?? event.cost);
|
|
403
|
+
if (!tokens && !Number.isFinite(cost)) return null;
|
|
404
|
+
return {
|
|
405
|
+
input: Number(tokens?.input) || 0,
|
|
406
|
+
output: Number(tokens?.output) || 0,
|
|
407
|
+
reasoning: Number(tokens?.reasoning) || 0,
|
|
408
|
+
cacheRead: Number(tokens?.cache?.read) || 0,
|
|
409
|
+
cacheWrite: Number(tokens?.cache?.write) || 0,
|
|
410
|
+
cost: Number.isFinite(cost) ? cost : 0,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// DIKKAT: text/error sozlesmesi degismemeli — operatorun JSON protokolu bu metni okuyor.
|
|
415
|
+
// usage yalnizca EK bir alandir; cikarilamadiginda null kalir ve hicbir akisi etkilemez.
|
|
416
|
+
function normalizeCliOutput(agent, stdout) {
|
|
417
|
+
if (agent.adapter !== "opencode" || !(agent.args || []).includes("json")) {
|
|
418
|
+
return { text: String(stdout || "").trim(), error: "", usage: null };
|
|
419
|
+
}
|
|
420
|
+
const texts = [], errors = [];
|
|
421
|
+
let usage = null;
|
|
422
|
+
for (const line of String(stdout || "").split(/\r?\n/)) {
|
|
423
|
+
if (!line.trim()) continue;
|
|
424
|
+
try {
|
|
425
|
+
const event = JSON.parse(line);
|
|
426
|
+
if (event.type === "text" && event.part?.text) texts.push(String(event.part.text));
|
|
427
|
+
if (event.type === "error") errors.push(String(event.error?.message || event.error || "OpenCode model/provider hatasi"));
|
|
428
|
+
// Bir kosumda birden fazla adim olabilir; hepsi toplanir.
|
|
429
|
+
if (event.type === "step_finish") {
|
|
430
|
+
const step = readStepUsage(event);
|
|
431
|
+
if (step) usage = addUsage(usage, step);
|
|
432
|
+
}
|
|
433
|
+
} catch {}
|
|
434
|
+
}
|
|
435
|
+
return { text: texts.join("\n").trim(), error: errors.join("\n").trim(), usage };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Metindeki DENGELI suslu parantezli en dis JSON nesnesi adaylarini cikarir. String icindeki
|
|
439
|
+
// parantez/kacis karakterleri sayilmaz; boylece "final" metninde gecen bir { veya } eski
|
|
440
|
+
// "ilk-{ .. son-}" kestirmesini bozmaz. Ayrica model aciklama + nesne + ek aciklama yazdiginda
|
|
441
|
+
// (ya da birden fazla nesne dondurdugunde) her nesne ayri aday olur.
|
|
442
|
+
function jsonObjectCandidates(text) {
|
|
443
|
+
const src = String(text);
|
|
444
|
+
const found = [];
|
|
445
|
+
const stack = [];
|
|
446
|
+
let inString = false, escaped = false;
|
|
447
|
+
for (let i = 0; i < src.length; i++) {
|
|
448
|
+
const ch = src[i];
|
|
449
|
+
if (inString) {
|
|
450
|
+
if (escaped) escaped = false;
|
|
451
|
+
else if (ch === "\\") escaped = true;
|
|
452
|
+
else if (ch === '"') inString = false;
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (ch === '"') inString = true;
|
|
456
|
+
else if (ch === "{") stack.push(i);
|
|
457
|
+
else if (ch === "}" && stack.length) {
|
|
458
|
+
const start = stack.pop();
|
|
459
|
+
if (!stack.length) found.push(src.slice(start, i + 1));
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return found;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Model bazen JSON'u tek tirnak, sondaki virgul veya akilli tirnakla yazar. Kati parse
|
|
466
|
+
// basarisiz oldugunda son care olarak bu yaygin sapmalari onarip bir kez daha dener.
|
|
467
|
+
function repairJsonText(candidate) {
|
|
468
|
+
return String(candidate)
|
|
469
|
+
.replace(/[“”]/g, '"')
|
|
470
|
+
.replace(/[‘’]/g, "'")
|
|
471
|
+
.replace(/,\s*([}\]])/g, "$1");
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const PROTOCOL_KEYS = /"(status|assignments|summary|completionCriteria|final)"\s*:/;
|
|
475
|
+
|
|
476
|
+
function parseJson(text, label) {
|
|
477
|
+
const raw = String(text);
|
|
478
|
+
const candidates = [];
|
|
479
|
+
for (const fence of raw.matchAll(/```(?:json)?\s*([\s\S]*?)```/gi)) candidates.push(fence[1]);
|
|
480
|
+
candidates.push(raw.trim());
|
|
481
|
+
// Protokol anahtari iceren nesneler once denenir; ayni onem sirasinda SONDAKI nesne kazanir
|
|
482
|
+
// (modeller once dusunce/aciklama, en sona nihai protokol nesnesini yazar).
|
|
483
|
+
const objects = jsonObjectCandidates(raw).reverse();
|
|
484
|
+
candidates.push(...objects.filter((o) => PROTOCOL_KEYS.test(o)), ...objects.filter((o) => !PROTOCOL_KEYS.test(o)));
|
|
485
|
+
for (const candidate of candidates) {
|
|
486
|
+
for (const attempt of [candidate, repairJsonText(candidate)]) {
|
|
487
|
+
try {
|
|
488
|
+
const parsed = JSON.parse(attempt);
|
|
489
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
490
|
+
} catch {}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const error = new Error(`${label} gecerli JSON dondurmedi.`);
|
|
494
|
+
error.rawText = raw;
|
|
495
|
+
throw error;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Protokol hatasinda operatorun ham metnini kullaniciya gosterilebilir bir cevaba cevirir.
|
|
499
|
+
// Yarim kalmis/kirik JSON parcalarini ve kod bloklarini atar; geriye anlamli duz metin
|
|
500
|
+
// kalmiyorsa null doner (o zaman normal hata akisi calisir).
|
|
501
|
+
function conversationalAnswer(error) {
|
|
502
|
+
if (!/gecerli JSON dondurmedi/i.test(String(error?.message || ""))) return null;
|
|
503
|
+
const raw = String(error.rawText || "");
|
|
504
|
+
if (!raw.trim()) return null;
|
|
505
|
+
const stripped = raw
|
|
506
|
+
.replace(/```[\s\S]*?```/g, " ")
|
|
507
|
+
.replace(/\{[\s\S]*\}/g, " ")
|
|
508
|
+
.replace(/[\s\S]*?^\s*\{[\s\S]*$/m, (match) => match.split("{")[0])
|
|
509
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
510
|
+
.trim();
|
|
511
|
+
// Cok kisa artiklar (or. tek noktalama) cevap sayilmaz.
|
|
512
|
+
return stripped.length >= 8 ? stripped : null;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function capabilitiesFor(agent) {
|
|
516
|
+
// Rol dosyasi bir uzmanlik dayatiyorsa yetenekler DOGRUDAN rolden turer. Aksi halde
|
|
517
|
+
// config'teki eski/genis capabilities listesi (or. planner'da "implementation") katalogda
|
|
518
|
+
// allowedKinds ile celisir ve operatore yaniltici sinyal verirdi. Rol yoksa config'e duseriz.
|
|
519
|
+
const role = String(agent.roleFile || "").toLowerCase();
|
|
520
|
+
if (role.includes("executor")) return new Set(["implementation", "debugging", "testing"]);
|
|
521
|
+
if (role.includes("review")) return new Set(["review", "testing", "analysis"]);
|
|
522
|
+
if (role.includes("planner")) return new Set(["planning", "analysis"]);
|
|
523
|
+
if (role.includes("operator")) return new Set(["planning", "delegation"]);
|
|
524
|
+
return new Set(Array.isArray(agent.capabilities) ? agent.capabilities.map((x) => String(x).toLowerCase()) : []);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function inferAssignmentKind(raw, instruction) {
|
|
528
|
+
const explicit = String(raw.kind || "").toLowerCase();
|
|
529
|
+
if (["implement", "review", "research", "plan"].includes(explicit)) return explicit;
|
|
530
|
+
if (/\b(olustur|oluştur|yaz|uygula|kur|gelistir|geliştir|duzelt|düzelt|implement|build|create|edit|fix)\b/i.test(instruction)) return "implement";
|
|
531
|
+
if (/\b(incele|denetle|dogrula|doğrula|review|audit|verify|test et)\b/i.test(instruction)) return "review";
|
|
532
|
+
if (/\b(arastir|araştır|research|web)\b/i.test(instruction)) return "research";
|
|
533
|
+
if (/\b(planla|plan|tasarla|design)\b/i.test(instruction)) return "plan";
|
|
534
|
+
return "implement";
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Gorev metni gercek bir yapim/degisiklik isi mi? Operator, plan evresinde "bilgi sorusu
|
|
538
|
+
// kestirmesi" ile (delegasyon acmadan complete) yaniti dogrudan verebilir. Ama prompt acikca
|
|
539
|
+
// bir uygulama/olusturma istiyorsa bu kestirme kullaniciya HIC is uretmeden sahte basari doner
|
|
540
|
+
// (or. proje hafizasindaki gecmis teslimati "zaten yapildi" sanmak). Bu durumda kestirmeyi
|
|
541
|
+
// engelleyip yeniden planlama isteriz. inferAssignmentKind'in implement fiil kumesiyle uyumlu.
|
|
542
|
+
function taskRequiresDelegation(prompt) {
|
|
543
|
+
return /\b(olustur|oluştur|yaz|uygula|kur|gelistir|geliştir|duzelt|düzelt|refactor|implement|build|create|edit|fix|generate|ekle|degistir|değiştir)\b/i.test(String(prompt || ""));
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function roleAllowedKinds(agent) {
|
|
547
|
+
const role = path.basename(String(agent?.roleFile || "")).toLowerCase();
|
|
548
|
+
if (role.includes("executor")) return ["implement"];
|
|
549
|
+
if (role.includes("review")) return ["review"];
|
|
550
|
+
if (role.includes("planner")) return ["plan"];
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function supportsKind(agent, kind) {
|
|
555
|
+
const roleKinds = roleAllowedKinds(agent);
|
|
556
|
+
if (roleKinds) return roleKinds.includes(kind);
|
|
557
|
+
const caps = capabilitiesFor(agent);
|
|
558
|
+
const wanted = {
|
|
559
|
+
implement: ["implementation", "coding", "development", "debugging"],
|
|
560
|
+
review: ["review", "testing", "security", "analysis"],
|
|
561
|
+
research: ["research", "web", "analysis"],
|
|
562
|
+
plan: ["planning", "analysis", "architecture"],
|
|
563
|
+
}[kind] || [];
|
|
564
|
+
return wanted.some((cap) => caps.has(cap));
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function agentUsable(agent) {
|
|
568
|
+
return agent?.health?.status ? agent.health.status === "ready" : true;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function normalizeAssignments(value, cfg, operatorName, usedIds, taskText = "") {
|
|
572
|
+
if (!Array.isArray(value)) throw new Error("Operator assignments dizisi dondurmedi.");
|
|
573
|
+
const max = Math.max(1, cfg.operator?.maxDelegationsPerRound || 8);
|
|
574
|
+
// Operator daha once kullanilan bir kimligi yinelerse gorevi oldurmek yerine kimligi
|
|
575
|
+
// otomatik yeniden adlandiririz; saatlerce uretilmis teslimat bir ID cakismasi yuzunden
|
|
576
|
+
// basarisiz sayilmamalidir. Ayni turdaki dependsOn referanslari da yeni ada tasinir.
|
|
577
|
+
const renames = new Map();
|
|
578
|
+
return value.slice(0, max).map((raw, index) => {
|
|
579
|
+
let id = String(raw.id || `task-${Date.now()}-${index + 1}`).replace(/[^a-zA-Z0-9_.-]/g, "-");
|
|
580
|
+
let renamedFrom;
|
|
581
|
+
if (usedIds.has(id)) {
|
|
582
|
+
let n = 2;
|
|
583
|
+
while (usedIds.has(`${id}-r${n}`)) n++;
|
|
584
|
+
renamedFrom = id;
|
|
585
|
+
renames.set(id, `${id}-r${n}`);
|
|
586
|
+
id = `${id}-r${n}`;
|
|
587
|
+
}
|
|
588
|
+
usedIds.add(id);
|
|
589
|
+
const requestedAgent = String(raw.agent || "");
|
|
590
|
+
if (!cfg.agents[requestedAgent]) throw new Error(`Operator tanimsiz agent secti: ${requestedAgent}`);
|
|
591
|
+
if (cfg.agents[requestedAgent].enabled === false) throw new Error(`Operator devre disi agent secti: ${requestedAgent}`);
|
|
592
|
+
if (!agentUsable(cfg.agents[requestedAgent])) throw new Error(`Operator kullanilamaz agent secti: ${requestedAgent} (${cfg.agents[requestedAgent].health?.label || "saglik testi basarisiz"})`);
|
|
593
|
+
if (requestedAgent === operatorName) throw new Error("Operator kendisine uzman gorevi atayamaz.");
|
|
594
|
+
const instruction = String(raw.instruction || raw.task || "").trim();
|
|
595
|
+
if (!instruction) throw new Error(`${id} delegasyonunda instruction eksik.`);
|
|
596
|
+
const kind = inferAssignmentKind(raw, instruction);
|
|
597
|
+
// Beceriler kullanici-kapilidir: operator yalnizca cfg.skills.enabled icindekileri iliştirebilir.
|
|
598
|
+
// Tanimsiz/etkin olmayan beceri sessizce dusurulur; asla gorevi olduren bir hata degildir.
|
|
599
|
+
const hasExplicitSkills = Object.prototype.hasOwnProperty.call(raw, "skills");
|
|
600
|
+
const autoMatchOn = cfg.skills?.autoMatch !== false;
|
|
601
|
+
const requestedSkills = Array.isArray(raw.skills)
|
|
602
|
+
? raw.skills
|
|
603
|
+
: (!hasExplicitSkills && autoMatchOn
|
|
604
|
+
? skillRegistry.suggest(cfg, `${taskText}\n${instruction}`, kind)
|
|
605
|
+
: []);
|
|
606
|
+
let skills = skillRegistry.resolveForAssignment(requestedSkills, cfg).map((skill) => skill.name);
|
|
607
|
+
// Operator kodlama/planlama/review isinde beceri iliştirmeyi atlarsa (or. skills:[] dönerse),
|
|
608
|
+
// kullanicinin etkin becerilerinden goreve GERCEKTEN uyanlari otomatik ekle. suggest() yalnizca
|
|
609
|
+
// lexical eslesme oldugunda skill döner; alakasiz beceri asla iliştirilmez. Boylece "operator
|
|
610
|
+
// becerileri kullanmiyor" durumu ortadan kalkar.
|
|
611
|
+
if (!skills.length && autoMatchOn && ["implement", "plan", "review"].includes(kind)) {
|
|
612
|
+
skills = skillRegistry.resolveForAssignment(
|
|
613
|
+
skillRegistry.suggest(cfg, `${taskText}\n${instruction}`, kind), cfg
|
|
614
|
+
).map((skill) => skill.name);
|
|
615
|
+
}
|
|
616
|
+
let agent = requestedAgent;
|
|
617
|
+
const unavailable = new Set(cfg.runtimeUnavailableAgents || []);
|
|
618
|
+
if (unavailable.has(agent) || !supportsKind(cfg.agents[agent], kind)) {
|
|
619
|
+
const compatible = Object.entries(cfg.agents).find(([name, candidate]) => name !== operatorName && candidate.enabled !== false && agentUsable(candidate) && !unavailable.has(name) && supportsKind(candidate, kind));
|
|
620
|
+
if (compatible) agent = compatible[0];
|
|
621
|
+
else if (unavailable.has(agent)) throw new Error(`Operator bu oturumda kullanilamaz agent secti: ${requestedAgent}`);
|
|
622
|
+
}
|
|
623
|
+
return {
|
|
624
|
+
id, agent, kind, instruction, skills,
|
|
625
|
+
renamedFrom,
|
|
626
|
+
requestedAgent: agent === requestedAgent ? undefined : requestedAgent,
|
|
627
|
+
routingReason: agent === requestedAgent ? undefined : `${requestedAgent} ${unavailable.has(requestedAgent) ? "bu oturumda kullanilamaz" : `${kind} yetenegine sahip degil`}; ${agent} secildi.`,
|
|
628
|
+
dependsOn: Array.isArray(raw.dependsOn) ? raw.dependsOn.map((dep) => renames.get(String(dep)) || String(dep)) : [],
|
|
629
|
+
};
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function compatibleAgentForKind(cfg, operatorName, kind) {
|
|
634
|
+
const unavailable = new Set(cfg.runtimeUnavailableAgents || []);
|
|
635
|
+
return Object.entries(cfg.agents || {}).find(([name, agent]) =>
|
|
636
|
+
name !== operatorName && agent.enabled !== false && agentUsable(agent) &&
|
|
637
|
+
!unavailable.has(name) && supportsKind(agent, kind)
|
|
638
|
+
)?.[0] || "";
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function nextAssignmentId(base, usedIds) {
|
|
642
|
+
let id = base;
|
|
643
|
+
let suffix = 2;
|
|
644
|
+
while (usedIds.has(id)) id = `${base}-${suffix++}`;
|
|
645
|
+
usedIds.add(id);
|
|
646
|
+
return id;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Balanced bir uygulama gorevinde katalogda standart roller varsa ilk turu
|
|
650
|
+
// PLAN -> IMPLEMENT -> REVIEW olarak garanti eder. Operatorun bir rolu yanlislikla
|
|
651
|
+
// atlamasi, kullanicinin etkinlestirdigi uzmani sessizce devre disi birakamaz.
|
|
652
|
+
function ensureBalancedRoleChain(assignments, cfg, operatorName, task, usedIds) {
|
|
653
|
+
if (task.executionMode !== "balanced" || !assignments.some((item) => item.kind === "implement")) return assignments;
|
|
654
|
+
const chained = assignments.map((item) => ({ ...item, dependsOn: [...item.dependsOn] }));
|
|
655
|
+
let plan = chained.find((item) => item.kind === "plan");
|
|
656
|
+
if (!plan) {
|
|
657
|
+
const planner = compatibleAgentForKind(cfg, operatorName, "plan");
|
|
658
|
+
if (planner) {
|
|
659
|
+
const instruction = "Kullanici hedefini ve mevcut calisma klasorunu salt okunur incele. Executor icin dosya kapsamini, uygulama siralamasini, riskleri ve dogrulama adimlarini iceren somut bir plan hazirla; dosyalari degistirme.";
|
|
660
|
+
plan = {
|
|
661
|
+
id: nextAssignmentId("balanced-plan", usedIds),
|
|
662
|
+
agent: planner,
|
|
663
|
+
kind: "plan",
|
|
664
|
+
instruction,
|
|
665
|
+
dependsOn: [],
|
|
666
|
+
skills: cfg.skills?.autoMatch === false ? [] : skillRegistry.suggest(cfg, `${task.prompt || ""}\n${instruction}`, "plan"),
|
|
667
|
+
routingReason: "Balanced rol zinciri: kullanilabilir planner ilk tura otomatik eklendi.",
|
|
668
|
+
};
|
|
669
|
+
chained.unshift(plan);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
const implementations = chained.filter((item) => item.kind === "implement");
|
|
673
|
+
if (plan) {
|
|
674
|
+
for (const implementation of implementations) {
|
|
675
|
+
if (!implementation.dependsOn.includes(plan.id)) implementation.dependsOn.push(plan.id);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
let reviews = chained.filter((item) => item.kind === "review");
|
|
679
|
+
if (!reviews.length) {
|
|
680
|
+
const reviewer = compatibleAgentForKind(cfg, operatorName, "review");
|
|
681
|
+
if (reviewer) {
|
|
682
|
+
const instruction = "Tamamlanan uygulamayi kullanici hedefi ve kabul kriterlerine gore bagimsiz, salt okunur olarak denetle. Dosya ve test kanitlarini raporla; sonunda VERDICT: PASS veya VERDICT: FAIL yaz.";
|
|
683
|
+
const review = {
|
|
684
|
+
id: nextAssignmentId("balanced-review", usedIds),
|
|
685
|
+
agent: reviewer,
|
|
686
|
+
kind: "review",
|
|
687
|
+
instruction,
|
|
688
|
+
dependsOn: implementations.map((item) => item.id),
|
|
689
|
+
skills: cfg.skills?.autoMatch === false ? [] : skillRegistry.suggest(cfg, `${task.prompt || ""}\n${instruction}`, "review"),
|
|
690
|
+
routingReason: "Balanced rol zinciri: kullanilabilir reviewer ilk tura otomatik eklendi.",
|
|
691
|
+
};
|
|
692
|
+
chained.push(review);
|
|
693
|
+
reviews = [review];
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
for (const review of reviews) {
|
|
697
|
+
// Operator belirli bir uygulamayi incelemeye bagladiysa bu kapsami genisletme;
|
|
698
|
+
// ayni turdaki bagimsiz/yardimci bir implement hatasi ana incelemeyi bloke etmemeli.
|
|
699
|
+
if (!review.dependsOn.length) {
|
|
700
|
+
for (const implementation of implementations) review.dependsOn.push(implementation.id);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const rank = { plan: 0, research: 1, implement: 2, review: 3 };
|
|
704
|
+
return chained.map((item, index) => ({ item, index }))
|
|
705
|
+
.sort((a, b) => (rank[a.item.kind] ?? 2) - (rank[b.item.kind] ?? 2) || a.index - b.index)
|
|
706
|
+
.map(({ item }) => item);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
class Engine extends EventEmitter {
|
|
710
|
+
constructor() {
|
|
711
|
+
super();
|
|
712
|
+
this.running = false;
|
|
713
|
+
this.busy = false;
|
|
714
|
+
this.current = null;
|
|
715
|
+
this.activeChild = null;
|
|
716
|
+
this.callSequence = 0;
|
|
717
|
+
// Kesin kimlik/kurulum hatasi veren agent'lari ayni motor oturumunda tekrar secme.
|
|
718
|
+
// Kalici config'i degistirmeyiz; kullanici kimlik bilgisini duzeltip motoru yeniden
|
|
719
|
+
// baslattiginda agent tekrar denenebilir.
|
|
720
|
+
this.unhealthyAgents = new Map();
|
|
721
|
+
// Canli kodlama akisi: gorev calisirken calisma klasorunu periyodik tarayan zamanlayici
|
|
722
|
+
// ve son yayinlanan degisiklik imzasi (ayni durum tekrar tekrar yayinlanmasin diye).
|
|
723
|
+
this._liveTimer = null;
|
|
724
|
+
this._lastFileChangeKey = "";
|
|
725
|
+
this._textBefore = null;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
cfg() { return store.loadConfig(); }
|
|
729
|
+
|
|
730
|
+
status() {
|
|
731
|
+
const cfg = this.cfg();
|
|
732
|
+
return {
|
|
733
|
+
running: this.running,
|
|
734
|
+
busy: this.busy,
|
|
735
|
+
current: this.current,
|
|
736
|
+
mode: cfg.approvalMode,
|
|
737
|
+
defaultOperator: cfg.operator?.cli || "",
|
|
738
|
+
callsToday: store.getCallCount(),
|
|
739
|
+
budget: cfg.dailyCallBudget,
|
|
740
|
+
usageToday: store.getDailyUsage(),
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// KULLANIM TELEMETRISI: gorev suresince agent bazinda token/maliyet toplar. Tamamen
|
|
745
|
+
// additive ve hata toleranslidir — veri veremeyen CLI'lar (metin modunda calisan
|
|
746
|
+
// codex/claude/gemini) icin sayaclar bos kalir, hicbir akis bundan etkilenmez.
|
|
747
|
+
resetUsage() {
|
|
748
|
+
this._usage = { total: { ...EMPTY_USAGE }, byAgent: {}, calls: 0, reportingCalls: 0 };
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
recordUsage(agentName, usage) {
|
|
752
|
+
if (!this._usage) this.resetUsage();
|
|
753
|
+
this._usage.total = addUsage(this._usage.total, usage);
|
|
754
|
+
this._usage.byAgent[agentName] = addUsage(this._usage.byAgent[agentName], usage);
|
|
755
|
+
this._usage.reportingCalls++;
|
|
756
|
+
try { store.addDailyUsage(usage); } catch {}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// Gorev kapanisinda saklanacak sekil. Hicbir cagri veri vermediyse null doner ki arayuz
|
|
760
|
+
// "0 token" gibi yaniltici bir sayi yerine "veri yok" gosterebilsin.
|
|
761
|
+
usageSummary() {
|
|
762
|
+
const usage = this._usage;
|
|
763
|
+
if (!usage || !usage.reportingCalls) return null;
|
|
764
|
+
return {
|
|
765
|
+
total: usage.total,
|
|
766
|
+
byAgent: usage.byAgent,
|
|
767
|
+
calls: usage.calls,
|
|
768
|
+
reportingCalls: usage.reportingCalls,
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
publish(type, data, taskId = this.current?.id) {
|
|
773
|
+
const event = { at: new Date().toISOString(), taskId: taskId || null, ...data, type };
|
|
774
|
+
if (taskId) store.appendRunEvent(taskId, event);
|
|
775
|
+
this.emit(type, event);
|
|
776
|
+
return event;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// CANLI KODLAMA: gorev calisirken calisma klasorunu _snapBefore'a gore tarar ve olusan/
|
|
780
|
+
// degisen/silinen dosyalari "filechange" olayiyla yayinlar; complete()'teki nihai diff'in
|
|
781
|
+
// canli on-izlemesidir. Yalnizca durum degistiginde olay basar (ayni diff tekrarlanmaz).
|
|
782
|
+
// Tamamen additive ve salt-okunurdur; hata verirse gorevi etkilemeden sessizce atlar.
|
|
783
|
+
publishFileChanges(taskId = this.current?.id) {
|
|
784
|
+
if (!taskId || !this._cwd || !this._snapBefore) return null;
|
|
785
|
+
let changes, currentSnapshot;
|
|
786
|
+
try {
|
|
787
|
+
currentSnapshot = snapshotDir(this._cwd);
|
|
788
|
+
changes = diffSnapshots(this._snapBefore, currentSnapshot);
|
|
789
|
+
}
|
|
790
|
+
catch { return null; }
|
|
791
|
+
// Yalnizca dosya adlarini degil guncel stamp'i da imzaya kat. Ayni dosya gorev
|
|
792
|
+
// sirasinda ikinci kez degistiginde yeni satir diff'i mutlaka yayinlanmalidir.
|
|
793
|
+
const stampKey = [...changes.created, ...changes.modified]
|
|
794
|
+
.map((file) => `${file}:${currentSnapshot.get(file) || ""}`).join("|");
|
|
795
|
+
const key = `${stampKey}#${changes.deleted.join("|")}`;
|
|
796
|
+
if (key === this._lastFileChangeKey) return null;
|
|
797
|
+
const previouslyHadChanges = this._lastFileChangeKey !== "" && this._lastFileChangeKey !== "#";
|
|
798
|
+
const changedFiles = [
|
|
799
|
+
...changes.created.map((file) => ({ file, action: "created" })),
|
|
800
|
+
...changes.modified.map((file) => ({ file, action: "modified" })),
|
|
801
|
+
...changes.deleted.map((file) => ({ file, action: "deleted" })),
|
|
802
|
+
];
|
|
803
|
+
let remainingLines = DIFF_MAX_EVENT_LINES;
|
|
804
|
+
const files = changedFiles.map(({ file, action }) => {
|
|
805
|
+
if (remainingLines <= 0) return { path: file, action, additions: null, deletions: null, previewStatus: "event-limit", hunks: [] };
|
|
806
|
+
const described = describeFileDiff(this._cwd, file, action, this._textBefore, { maxLines: Math.min(DIFF_MAX_RENDERED_LINES, remainingLines) });
|
|
807
|
+
remainingLines -= (described.hunks || []).reduce((sum, hunk) => sum + (hunk.lines || []).length, 0);
|
|
808
|
+
return described;
|
|
809
|
+
});
|
|
810
|
+
// Bos degisim setini (or. baslangictaki taban veya tum degisikliklerin geri alinmasi)
|
|
811
|
+
// imzasini hatirla. Gorev basinda gurultu uretme; ancak daha once gorunen tum degisiklikler
|
|
812
|
+
// geri alindiysa bos olayi yayinla ki dashboard bayat diff'i temizlesin.
|
|
813
|
+
this._lastFileChangeKey = key;
|
|
814
|
+
if (!files.length && !previouslyHadChanges) return null;
|
|
815
|
+
return this.publish("filechange", {
|
|
816
|
+
files,
|
|
817
|
+
counts: { created: changes.created.length, modified: changes.modified.length, deleted: changes.deleted.length },
|
|
818
|
+
lineCounts: {
|
|
819
|
+
additions: files.reduce((sum, file) => sum + (Number(file.additions) || 0), 0),
|
|
820
|
+
deletions: files.reduce((sum, file) => sum + (Number(file.deletions) || 0), 0),
|
|
821
|
+
unavailable: files.filter((file) => file.additions == null || file.deletions == null).length,
|
|
822
|
+
},
|
|
823
|
+
}, taskId);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
startLiveDiff(task) {
|
|
827
|
+
this.stopLiveDiff();
|
|
828
|
+
this._lastFileChangeKey = "";
|
|
829
|
+
// liveDiff kesin olarak false yapilmadikca aciktir (mevcut davranisi degistirmeyen additive
|
|
830
|
+
// ozellik). Tarama araligi config'ten ayarlanabilir; en dusuk 500ms ile sinirli.
|
|
831
|
+
const cfg = this.cfg();
|
|
832
|
+
if (cfg.liveDiff === false) { this._textBefore = null; return; }
|
|
833
|
+
this._textBefore = captureTextSnapshot(this._cwd, this._snapBefore);
|
|
834
|
+
const intervalMs = Math.max(500, Number(cfg.liveDiffIntervalMs) || 2500);
|
|
835
|
+
this._liveTimer = setInterval(() => this.publishFileChanges(task.id), intervalMs);
|
|
836
|
+
// unref: bekleyen bir tarama zamanlayicisi surecin kapanmasini asla engellemesin.
|
|
837
|
+
if (this._liveTimer && typeof this._liveTimer.unref === "function") this._liveTimer.unref();
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
stopLiveDiff() {
|
|
841
|
+
if (this._liveTimer) { clearInterval(this._liveTimer); this._liveTimer = null; }
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
setMode(mode) {
|
|
845
|
+
const cfg = this.cfg();
|
|
846
|
+
cfg.approvalMode = mode === "auto" ? "auto" : "ask";
|
|
847
|
+
store.saveConfig(cfg);
|
|
848
|
+
this.emit("status", this.status());
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
start() {
|
|
852
|
+
if (this.running) return;
|
|
853
|
+
if (!this.cfg().autonomousConsentAcceptedAt) {
|
|
854
|
+
const error = new Error("Otonom CLI calistirma kosullari henuz kabul edilmedi. Dashboard'daki ilk kullanim uyarisini onaylayin.");
|
|
855
|
+
error.code = "AUTONOMOUS_CONSENT_REQUIRED";
|
|
856
|
+
throw error;
|
|
857
|
+
}
|
|
858
|
+
this.running = true;
|
|
859
|
+
this.publish("log", { level: "info", msg: `Motor basladi. Mod=${this.cfg().approvalMode}` }, null);
|
|
860
|
+
this.emit("status", this.status());
|
|
861
|
+
this.loop();
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
stop() {
|
|
865
|
+
this.running = false;
|
|
866
|
+
this.wake();
|
|
867
|
+
this.stopLiveDiff();
|
|
868
|
+
if (this.activeChild) {
|
|
869
|
+
try { this.activeChild.kill(); } catch {}
|
|
870
|
+
}
|
|
871
|
+
this.publish("log", { level: "warn", msg: "Motor durduruluyor; aktif CLI islemi sonlandirildi." });
|
|
872
|
+
this.emit("status", this.status());
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
wake() {
|
|
876
|
+
if (this._wakeResolve) {
|
|
877
|
+
clearTimeout(this._wakeTimer);
|
|
878
|
+
const resolve = this._wakeResolve;
|
|
879
|
+
this._wakeResolve = null;
|
|
880
|
+
resolve();
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
sleepWake(ms) {
|
|
885
|
+
return new Promise((resolve) => {
|
|
886
|
+
this._wakeResolve = resolve;
|
|
887
|
+
this._wakeTimer = setTimeout(() => { this._wakeResolve = null; resolve(); }, ms);
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
async loop() {
|
|
892
|
+
while (this.running) {
|
|
893
|
+
const task = store.nextPending();
|
|
894
|
+
if (!task) {
|
|
895
|
+
await this.sleepWake((this.cfg().pollSeconds || 15) * 1000);
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
try {
|
|
899
|
+
await this.runTask(task);
|
|
900
|
+
} catch (error) {
|
|
901
|
+
if (!(await this.salvage(task, error))) {
|
|
902
|
+
const found = store.findTask(task.id);
|
|
903
|
+
const failure = classifyCliError(error);
|
|
904
|
+
task.status = "failed";
|
|
905
|
+
task.error = error.message;
|
|
906
|
+
// Siniflandirilmis teshis gorevle birlikte saklanir; arayuz "ne oldu / ne yapmali"
|
|
907
|
+
// ayrimini ham hata metnini kirparak tahmin etmek yerine buradan okur.
|
|
908
|
+
task.failure = failure;
|
|
909
|
+
// Basarisiz gorev de para harcar; telemetri burada da saklanmali.
|
|
910
|
+
task.usage = this.usageSummary();
|
|
911
|
+
task.finishedAt = new Date().toISOString();
|
|
912
|
+
if (found) store.moveTask(found.state, "failed", task);
|
|
913
|
+
this.publish("log", { level: "error", msg: `HATA (${failure.code}): ${failure.summary}` }, task.id);
|
|
914
|
+
this.publish("log", { level: "warn", msg: `Ne yapmali: ${failure.action}` }, task.id);
|
|
915
|
+
if (task.kind === "operator-chat") {
|
|
916
|
+
this.publish("result", { id: task.id, kind: "operator-chat", parentTaskId: task.parentTaskId, status: "failed", error: failure.summary, failure }, task.id);
|
|
917
|
+
}
|
|
918
|
+
this.notifyOutcome(task, "failed", { error: failure.summary, failureCode: failure.code, action: failure.action });
|
|
919
|
+
this.emit("queue");
|
|
920
|
+
}
|
|
921
|
+
} finally {
|
|
922
|
+
this.stopLiveDiff();
|
|
923
|
+
this._textBefore = null;
|
|
924
|
+
this.busy = false;
|
|
925
|
+
this.current = null;
|
|
926
|
+
this.activeChild = null;
|
|
927
|
+
this.emit("status", this.status());
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// Uzman agent'i (operatorun altindaki ekip) cfg.agents'ten cozup calistirir.
|
|
933
|
+
invokeAgent(agentName, prompt, cfg, meta = {}) {
|
|
934
|
+
const configuredAgent = cfg.agents[agentName];
|
|
935
|
+
const agent = configuredAgent && cliRegistry.effectiveAgent(configuredAgent, cfg);
|
|
936
|
+
if (!agent) return Promise.reject(new Error(`Agent tanimsiz: ${agentName}`));
|
|
937
|
+
return this.runCli(agentName, agent, prompt, cfg, meta);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Operatoru, uzman agent'lardan BAGIMSIZ olarak dogrudan CLI'dan (claude/codex/gemini/opencode)
|
|
941
|
+
// calistirir. operator.md rolunu bu CLI giyer.
|
|
942
|
+
invokeOperator(cli, prompt, cfg, meta = {}) {
|
|
943
|
+
const agent = cliRegistry.operatorSpec(cli, cfg);
|
|
944
|
+
if (!agent) return Promise.reject(new Error(`Operator CLI tanimsiz veya kurulu degil: ${cli}`));
|
|
945
|
+
return this.runCli(cli, agent, prompt, cfg, meta);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
runCli(displayName, agent, prompt, cfg, meta = {}) {
|
|
949
|
+
return new Promise((resolve, reject) => {
|
|
950
|
+
const count = store.bumpCallCount();
|
|
951
|
+
if (count > (cfg.dailyCallBudget || Number.MAX_SAFE_INTEGER)) {
|
|
952
|
+
return reject(new Error(`Gunluk cagri butcesi asildi (${cfg.dailyCallBudget}).`));
|
|
953
|
+
}
|
|
954
|
+
if (!this._usage) this.resetUsage();
|
|
955
|
+
this._usage.calls++;
|
|
956
|
+
|
|
957
|
+
const callId = `${this.current?.id || "run"}-${++this.callSequence}`;
|
|
958
|
+
let promptFile = "";
|
|
959
|
+
let useStdin = true;
|
|
960
|
+
const rawArgs = (agent.args || []).map((arg) => {
|
|
961
|
+
if (String(arg).includes("{PROMPT}")) {
|
|
962
|
+
useStdin = false;
|
|
963
|
+
return String(arg).replaceAll("{PROMPT}", prompt);
|
|
964
|
+
}
|
|
965
|
+
if (String(arg).includes("{PROMPT_FILE}")) {
|
|
966
|
+
useStdin = false;
|
|
967
|
+
if (!promptFile) {
|
|
968
|
+
const promptDir = path.join(store.ROOT, "state", "prompts");
|
|
969
|
+
fs.mkdirSync(promptDir, { recursive: true });
|
|
970
|
+
promptFile = path.join(promptDir, `${callId}.md`);
|
|
971
|
+
fs.writeFileSync(promptFile, prompt, "utf8");
|
|
972
|
+
}
|
|
973
|
+
return String(arg).replaceAll("{PROMPT_FILE}", promptFile);
|
|
974
|
+
}
|
|
975
|
+
return String(arg);
|
|
976
|
+
});
|
|
977
|
+
const cwd = this._cwd || path.resolve(store.WORK_BASE, cfg.workingDir || ".");
|
|
978
|
+
// Path-aware hapis: codex sandbox'i disariyi zaten OS-native kapatir (effectiveAgent).
|
|
979
|
+
// Claude'un OS sandbox'i her platformda (ozellikle Windows'ta) olmadigi icin en azindan
|
|
980
|
+
// orkestratorun kendi topragini (config/memory/kurulum dizini) permissions.deny ile gizler.
|
|
981
|
+
// JSON'u arguman olarak gecmek Windows cmd.exe'de tirnaklari bozar; bu yuzden dosyaya yazip
|
|
982
|
+
// yolunu veriyoruz (prompt dosyasiyla ayni desen).
|
|
983
|
+
const sandboxSettings = claudeSandboxSettings(agent, cwd, cfg);
|
|
984
|
+
if (sandboxSettings) {
|
|
985
|
+
const sbDir = path.join(store.ROOT, "state", "prompts");
|
|
986
|
+
fs.mkdirSync(sbDir, { recursive: true });
|
|
987
|
+
const sbFile = path.join(sbDir, `${callId}.settings.json`);
|
|
988
|
+
fs.writeFileSync(sbFile, JSON.stringify(sandboxSettings), "utf8");
|
|
989
|
+
rawArgs.push("--settings", sbFile);
|
|
990
|
+
}
|
|
991
|
+
const command = cliRegistry.buildCommand(agent.cmd, rawArgs);
|
|
992
|
+
const file = command.file;
|
|
993
|
+
const args = command.args;
|
|
994
|
+
const started = Date.now();
|
|
995
|
+
let stdout = "", stderr = "", settled = false, timedOut = false, silenceTimedOut = false;
|
|
996
|
+
let timer, silenceTimer, progressTimer;
|
|
997
|
+
const base = { callId, agent: displayName, stage: meta.stage || "agent", assignmentId: meta.assignmentId || null };
|
|
998
|
+
|
|
999
|
+
this.current = { id: this.current?.id, stage: base.stage, agent: displayName, callId };
|
|
1000
|
+
this.emit("status", this.status());
|
|
1001
|
+
this.publish("activity", { ...base, kind: "process.started", cmd: agent.cmd, args: rawArgs, cwd });
|
|
1002
|
+
|
|
1003
|
+
// Motor onceliklidir: canli CLI saglik/model probe'lari (or. codex app-server / codex exec)
|
|
1004
|
+
// ayni CLI'yi ayni anda calistirirsa codex ikinci exec oturumunu SIGTERM ile keser. Bu yuzden
|
|
1005
|
+
// operatör/agent kosumuna girmeden uctaki tum probe'lari sonlandiriyoruz.
|
|
1006
|
+
cliRegistry.abortActiveProbes();
|
|
1007
|
+
let child;
|
|
1008
|
+
try { child = spawn(file, args, { cwd, env: cliRegistry.agentEnvironment(agent), windowsHide: true, shell: command.shell, windowsVerbatimArguments: !!command.verbatim }); }
|
|
1009
|
+
catch (error) { return reject(error); }
|
|
1010
|
+
this.activeChild = child;
|
|
1011
|
+
child.stdout.setEncoding("utf8");
|
|
1012
|
+
child.stderr.setEncoding("utf8");
|
|
1013
|
+
|
|
1014
|
+
const timeoutMs = Math.max(10, agent.timeoutSeconds || cfg.agentTimeoutSeconds || 900) * 1000;
|
|
1015
|
+
const silenceTimeoutMs = Math.max(1, agent.silenceTimeoutSeconds || cfg.cliSilenceTimeoutSeconds || 300) * 1000;
|
|
1016
|
+
let lastOutputAt = Date.now();
|
|
1017
|
+
const terminateTree = () => {
|
|
1018
|
+
if (isWin && child.pid) {
|
|
1019
|
+
try { spawn("taskkill", ["/pid", String(child.pid), "/t", "/f"], { windowsHide: true, stdio: "ignore" }); } catch {}
|
|
1020
|
+
} else {
|
|
1021
|
+
try { child.kill("SIGKILL"); } catch {}
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
const armSilenceTimer = () => {
|
|
1025
|
+
clearTimeout(silenceTimer);
|
|
1026
|
+
silenceTimer = setTimeout(() => {
|
|
1027
|
+
if (settled) return;
|
|
1028
|
+
silenceTimedOut = true;
|
|
1029
|
+
this.publish("activity", { ...base, kind: "process.silence-timeout", silenceTimeoutMs, elapsedMs: Date.now() - started });
|
|
1030
|
+
terminateTree();
|
|
1031
|
+
}, silenceTimeoutMs);
|
|
1032
|
+
};
|
|
1033
|
+
armSilenceTimer();
|
|
1034
|
+
progressTimer = setInterval(() => {
|
|
1035
|
+
if (settled) return;
|
|
1036
|
+
this.publish("activity", { ...base, kind: "process.progress", elapsedMs: Date.now() - started, silentMs: Date.now() - lastOutputAt, silenceTimeoutMs });
|
|
1037
|
+
}, 15000);
|
|
1038
|
+
timer = setTimeout(() => {
|
|
1039
|
+
if (settled) return;
|
|
1040
|
+
timedOut = true;
|
|
1041
|
+
this.publish("activity", { ...base, kind: "process.timeout", timeoutMs });
|
|
1042
|
+
terminateTree();
|
|
1043
|
+
}, timeoutMs);
|
|
1044
|
+
|
|
1045
|
+
child.stdout.on("data", (data) => {
|
|
1046
|
+
const text = String(data);
|
|
1047
|
+
stdout += text;
|
|
1048
|
+
lastOutputAt = Date.now();
|
|
1049
|
+
armSilenceTimer();
|
|
1050
|
+
this.publish("activity", { ...base, kind: "stdout", text });
|
|
1051
|
+
});
|
|
1052
|
+
child.stderr.on("data", (data) => {
|
|
1053
|
+
const text = String(data);
|
|
1054
|
+
stderr += text;
|
|
1055
|
+
lastOutputAt = Date.now();
|
|
1056
|
+
armSilenceTimer();
|
|
1057
|
+
this.publish("activity", { ...base, kind: "stderr", text });
|
|
1058
|
+
});
|
|
1059
|
+
child.on("error", (error) => {
|
|
1060
|
+
if (settled) return;
|
|
1061
|
+
settled = true;
|
|
1062
|
+
clearTimeout(timer);
|
|
1063
|
+
clearTimeout(silenceTimer);
|
|
1064
|
+
clearInterval(progressTimer);
|
|
1065
|
+
this.activeChild = null;
|
|
1066
|
+
if (promptFile) { try { fs.rmSync(promptFile); } catch {} }
|
|
1067
|
+
this.publish("activity", { ...base, kind: "process.failed", error: error.message, durationMs: Date.now() - started });
|
|
1068
|
+
reject(error);
|
|
1069
|
+
});
|
|
1070
|
+
child.on("close", (code, signal) => {
|
|
1071
|
+
if (settled) return;
|
|
1072
|
+
settled = true;
|
|
1073
|
+
clearTimeout(timer);
|
|
1074
|
+
clearTimeout(silenceTimer);
|
|
1075
|
+
clearInterval(progressTimer);
|
|
1076
|
+
this.activeChild = null;
|
|
1077
|
+
if (promptFile) { try { fs.rmSync(promptFile); } catch {} }
|
|
1078
|
+
const durationMs = Date.now() - started;
|
|
1079
|
+
// Cikti tek kez ayristirilir; hem kullanim telemetrisi hem metin ayni sonuctan okunur.
|
|
1080
|
+
const normalized = normalizeCliOutput(agent, stdout);
|
|
1081
|
+
if (normalized.usage) this.recordUsage(displayName, normalized.usage);
|
|
1082
|
+
this.publish("activity", { ...base, kind: "process.finished", exitCode: code, signal, durationMs, usage: normalized.usage, reason: silenceTimedOut ? "silence-timeout" : (timedOut ? "timeout" : null) });
|
|
1083
|
+
if (silenceTimedOut) return reject(new Error(`CLI_STALLED: ${agent.cmd} ${Math.round(silenceTimeoutMs / 1000)} saniye boyunca cikti uretmedi ve otomatik durduruldu.`));
|
|
1084
|
+
if (timedOut) return reject(new Error(`${agent.cmd} ${Math.round(timeoutMs / 1000)} saniyede zaman asimina ugradi${signal ? ` (signal=${signal})` : ""}. ${clip(stdout || stderr, 500)}`));
|
|
1085
|
+
// Teshis icin HER IKI akis da gerekir: bazi CLI'lar (Gemini dahil) kota/oturum hatasini
|
|
1086
|
+
// stdout'a, yigin izini stderr'e yazar. Yalnizca birini almak siniflandiriciyi kor birakiyordu.
|
|
1087
|
+
if (code !== 0) {
|
|
1088
|
+
const detail = [stderr, stdout].map((stream) => String(stream || "").trim()).filter(Boolean).join("\n");
|
|
1089
|
+
return reject(new Error(`${agent.cmd} cikis kodu ${code ?? "yok"}${signal ? ` (signal=${signal})` : ""}.\n${clip(detail, 2000)}`));
|
|
1090
|
+
}
|
|
1091
|
+
if (normalized.error) return reject(new Error(`OpenCode model/provider hatasi: ${clip(normalized.error, 500)}`));
|
|
1092
|
+
if (!normalized.text) return reject(new Error(`${agent.cmd} kullanilabilir cikti dondurmedi.${stderr ? ` ${clip(stderr, 300)}` : ""}`));
|
|
1093
|
+
resolve({ text: normalized.text, stderr: stderr.trim(), exitCode: code, durationMs, callId, usage: normalized.usage });
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
// Prompt argumanla/dosyayla verilse bile stdin MUTLAKA kapatilmali. OpenCode gibi CLI'lar
|
|
1097
|
+
// stdin TTY degilse borudan mesaj okur; EOF gelmezse model cagrisina hic gecmeden bloke olur.
|
|
1098
|
+
child.stdin.on("error", () => {});
|
|
1099
|
+
child.stdin.end(useStdin ? prompt : "");
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// Katalogda dosya yazabilen (implement) kullanilabilir bir uzman var mi? Operatorun kendisi ve
|
|
1104
|
+
// bu oturumda karantinaya alinan agentler haric. Tek executor (or. opencode) sessizlik nedeniyle
|
|
1105
|
+
// dustugunde takim fiilen dosya uretemez hale gelir; bu kontrol o durumu erken yakalar.
|
|
1106
|
+
hasUsableImplementAgent(cfg, operatorName) {
|
|
1107
|
+
return Object.entries(cfg.agents || {}).some(([name, agent]) =>
|
|
1108
|
+
name !== operatorName && agent.enabled !== false && agentUsable(agent)
|
|
1109
|
+
&& !this.unhealthyAgents.has(name) && supportsKind(agent, "implement"));
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
agentCatalog(cfg, operatorName) {
|
|
1113
|
+
return Object.entries(cfg.agents)
|
|
1114
|
+
.filter(([name, agent]) => name !== operatorName && agent.enabled !== false && agentUsable(agent) && !this.unhealthyAgents.has(name))
|
|
1115
|
+
.map(([name, a]) => ({
|
|
1116
|
+
name,
|
|
1117
|
+
description: a.description || "",
|
|
1118
|
+
capabilities: [...capabilitiesFor(a)],
|
|
1119
|
+
allowedKinds: roleAllowedKinds(a) || ["implement", "review", "research", "plan"].filter((kind) => supportsKind(a, kind)),
|
|
1120
|
+
roleFile: a.roleFile || "",
|
|
1121
|
+
costTier: a.costTier || "standard",
|
|
1122
|
+
}));
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
async invokeOperatorJson(operatorCli, prompt, cfg, stage, label) {
|
|
1126
|
+
const retries = Math.max(0, cfg.operator?.protocolRetries ?? 1);
|
|
1127
|
+
let lastError;
|
|
1128
|
+
let correction = "";
|
|
1129
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
1130
|
+
try {
|
|
1131
|
+
const response = await this.invokeOperator(operatorCli, prompt + correction, cfg, { stage });
|
|
1132
|
+
return parseJson(response.text, label);
|
|
1133
|
+
} catch (error) {
|
|
1134
|
+
lastError = error;
|
|
1135
|
+
// Yalnizca gercek JSON protokol hatasi yeniden denenir. CLI'nin sessiz kalmasi,
|
|
1136
|
+
// auth/timeout veya proses hatasi ayni operatoru tekrar calistirmamalidir.
|
|
1137
|
+
if (!/gecerli JSON dondurmedi/i.test(String(error?.message || ""))) throw error;
|
|
1138
|
+
if (attempt >= retries) break;
|
|
1139
|
+
// Modelin ne dondurdugunu duzeltme istegine geri veriyoruz: "yalnizca JSON dondur"
|
|
1140
|
+
// talimatini tekrarlamak, hatayi goremeyen model icin cogu zaman ayni cevabi uretiyordu.
|
|
1141
|
+
correction = `\n\nONCEKI CEVAP PROTOKOLE UYMADI: ${error.message}\n` +
|
|
1142
|
+
`Senin onceki cevabin (kirpilmis):\n"""\n${clip(String(error.rawText || ""), 1200)}\n"""\n` +
|
|
1143
|
+
`Bu cevap gecerli bir JSON nesnesi degildi. Simdi SADECE tek bir JSON nesnesi dondur: ` +
|
|
1144
|
+
`ilk karakter { ve son karakter } olsun; oncesinde/sonrasinda selamlama, aciklama, dusunce, ` +
|
|
1145
|
+
`Markdown veya \`\`\` kod bloğu OLMASIN. Metin alanlarindaki cift tirnaklari \\" ile kacir.`;
|
|
1146
|
+
this.publish("log", { level: "warn", msg: `${label} protokol hatasi; operator yeniden deneniyor (${attempt + 2}/${retries + 1}).` });
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
throw lastError;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// Insertion sirasi tamamlanma sirasidir; en son biten denetim en gecerli karardir.
|
|
1153
|
+
latestReview(state) {
|
|
1154
|
+
return Object.values(state.results || {}).reverse()
|
|
1155
|
+
.find((result) => result.kind === "review" && result.status === "completed" && result.verdict) || null;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// Operatorun karar evresine verilen takim kaydi. Ham state dokumu yerine sonuc odakli
|
|
1159
|
+
// bir ozet uretir; her sonucu kendi icinde ortadan kirpar ki en yeni denetim karari
|
|
1160
|
+
// (metnin sonundaki VERDICT satiri) butce asiminda kaybolmasin.
|
|
1161
|
+
teamDigest(state, cfg) {
|
|
1162
|
+
const results = Object.values(state.results || {});
|
|
1163
|
+
const budget = cfg.teamContextCharBudget || 30000;
|
|
1164
|
+
const perResult = Math.max(900, Math.floor(budget / Math.max(1, results.length)));
|
|
1165
|
+
const digest = {
|
|
1166
|
+
round: state.round,
|
|
1167
|
+
completionCriteria: state.criteria,
|
|
1168
|
+
results: results.map((result) => ({
|
|
1169
|
+
id: result.id, agent: result.agent, kind: result.kind, status: result.status,
|
|
1170
|
+
...(result.verdict ? { verdict: result.verdict } : {}),
|
|
1171
|
+
result: clipMiddle(result.result, perResult),
|
|
1172
|
+
})),
|
|
1173
|
+
};
|
|
1174
|
+
return clipMiddle(JSON.stringify(digest, null, 2), budget);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
operatorPrompt(task, cfg, memory, state, phase) {
|
|
1178
|
+
const operatorCli = task.operatorCli || cfg.operator?.cli;
|
|
1179
|
+
// Operatör rolü daima operator.md'dir; operatör bir CLI'dir, uzman agent'lar onun altında çalışır.
|
|
1180
|
+
const roleFile = cfg.operator?.roleFile || "roles/operator.md";
|
|
1181
|
+
const role = store.readRole(path.basename(roleFile));
|
|
1182
|
+
const catalog = this.agentCatalog(cfg, operatorCli);
|
|
1183
|
+
const skillContext = `${task.prompt}\n${phase === "review" ? Object.values(state.results || {}).map((result) => result.instruction || "").join("\n") : ""}`;
|
|
1184
|
+
const skillDiscovery = skillRegistry.discover(cfg, skillContext);
|
|
1185
|
+
const skillCatalog = skillDiscovery.catalog;
|
|
1186
|
+
const skillStats = skillDiscovery.stats;
|
|
1187
|
+
const enabledSkillNames = skillRegistry.enabledSkills(cfg).map((skill) => skill.name);
|
|
1188
|
+
// Beceri envanteri motor tarafindan uretilen OTORITER veridir. Operatore TAM etkin listeyi
|
|
1189
|
+
// (sayi + tum adlar) DAIMA veririz; boylece "kac beceri var" gibi sorular icin operatorun
|
|
1190
|
+
// kendi CLI'sinin dahili becerilerini veya bir alt agent'in iddiasini kullanmasi gerekmez.
|
|
1191
|
+
// Eslesen kisa liste yalnizca DELEGASYONA iliştirme icindir, envanterin tamami degildir.
|
|
1192
|
+
const skillSection = skillStats.enabled
|
|
1193
|
+
? `## Beceriler (OTORITER kaynak)\nSistemde tam olarak ${skillStats.enabled} etkin beceri var: ${enabledSkillNames.join(", ")}.\n` +
|
|
1194
|
+
`Beceri sayisi/adi/varligi sorulursa YALNIZCA bu listeyi esas al ve dogrudan kendin cevapla; calistigin CLI'nin dahili becerilerini bu sisteme KATMA, bir alt agent farkli bir sayi soylerse bu listeyi degil onu YOK say.\n` +
|
|
1195
|
+
(skillCatalog.length
|
|
1196
|
+
? `Bu goreve en ilgili ${skillCatalog.length} beceri (delegasyonun skills alanina yalnizca bunlardan uygun olanlari ekle):\n${JSON.stringify(skillCatalog, null, 2)}\n`
|
|
1197
|
+
: `Bu goreve gore taramada eslesme cikmadi; bu delegasyonlarda beceri iliştirme.\n`)
|
|
1198
|
+
: "";
|
|
1199
|
+
// Beceriler tamamen opsiyoneldir ve YALNIZCA kullanici etkinlestirdiginde katalogda gorunur.
|
|
1200
|
+
// Operator, assignment'a yalnizca bu kataloktaki adlari "skills" dizisinde iliştirebilir.
|
|
1201
|
+
const skillField = skillCatalog.length ? `, "skills":["beceri-adi"]` : "";
|
|
1202
|
+
const skillProtocol = skillCatalog.length
|
|
1203
|
+
? ` BECERI: Kisa listedeki gercekten ilgili adlari skills dizisine ekle; ilgisiz veya liste disi ad kullanma, uygun yoksa alani bos birak.`
|
|
1204
|
+
: "";
|
|
1205
|
+
const protocol = phase === "plan"
|
|
1206
|
+
? `Yalnizca gecerli JSON dondur. Gorev bir is/degisiklik/arastirma gerektiriyorsa delege et: {"summary":"yaklasim", "completionCriteria":["..."], "assignments":[{"id":"benzersiz-id", "agent":"catalog-name", "kind":"implement|review|research|plan", "instruction":"net gorev ve teslimat", "dependsOn":[]${skillField}}]} (en az bir assignment). ` +
|
|
1207
|
+
`Gorev yalnizca bir bilgi/soru VEYA bir selamlasma/sohbet mesaji ise (or. "merhaba", "nasilsin", "kac beceri var") ve yaniti dogrudan verebiliyorsan, HICBIR delegasyon acmadan: {"status":"complete", "final":"kullaniciya gosterilecek CEVABIN TAM METNI", "verification":"cevabin dayandigi kaynak"}. ` +
|
|
1208
|
+
`Selamlasmalarda bile cevabi JSON DISINA yazma: kullaniciya gosterilen tek alan "final"dir, nesnenin disindaki her metin atilir. Gercek is gerektiren gorevi bu kestirmeyle atlatma.${skillProtocol}`
|
|
1209
|
+
: `Yalnizca gecerli JSON dondur. Is tamamlanmadiysa {"status":"continue", "reason":"...", "assignments":[{"id":"...", "agent":"...", "kind":"implement|review|research|plan", "instruction":"...", "dependsOn":[]${skillField}}]}; tum kabul kriterleri karsilandiysa {"status":"complete", "final":"en fazla 5 kisa maddeyle ne yapildi", "verification":"tek satir dogrulama"}. Dosya listesini motor ekleyecek; final icinde uzun log veya ham agent cevabi tekrarlama. Continue icin en az bir yeni assignment zorunlu.${skillProtocol}\n` +
|
|
1210
|
+
`INCELEME DISIPLINI: Bagimsiz denetim VERDICT: PASS verdiyse ayni teslimat icin YENI review delegasyonu acma; complete dondur ve dusuk/orta onemdeki kalan notlari final metninde kalan risk olarak belirt. Ekipte bulunmayan dogrulama yetenegini (or. canli tarayici) tamamlanma sarti yapma; NOT RUN kalan dusuk riskli kontroller teslimati engellemez.`;
|
|
1211
|
+
const strategy = task.executionMode === "fast"
|
|
1212
|
+
? "HIZLI MOD: Kucuk bir is. Tek bir implementation agenti kullan. Ayri planlama veya review delegasyonu acma; uzman raporu hedefi karsiliyorsa ilk degerlendirmede tamamla."
|
|
1213
|
+
: task.executionMode === "deep"
|
|
1214
|
+
? "DERIN MOD: Isi uygun uzmanliklar arasinda dagit — planlama, uygulama, test ve bagimsiz inceleme icin AYRI ve dogru uzmanlari kullan."
|
|
1215
|
+
: "DENGELI MOD: Katalogda planner, executor ve reviewer varsa ucunu da ILK planda kullan. PLANLAMA -> UYGULAMA -> BAGIMSIZ INCELEME delegasyonlarini dependsOn ile ayni turda zincirle ve isi tek turda bitirmeyi hedefle. Ayni rolde birden fazla esdeger agent varsa yalnizca en uygun olani sec.";
|
|
1216
|
+
return `${role}\n\n---\n## Operator protokolu\n${protocol}\n` +
|
|
1217
|
+
// OpenCode gibi sohbet odakli CLI'lar varsayilan olarak once bir cumle yazip JSON'u
|
|
1218
|
+
// arkasina ekliyor; motor bunu ayiklayabilse de belirsizlik protokol hatasi uretiyordu.
|
|
1219
|
+
`## Cikti bicimi (KATI)\nCevabin TAMAMI tek bir JSON nesnesi olmali: ilk karakter { , son karakter } . ` +
|
|
1220
|
+
`Oncesinde/sonrasinda selamlama, aciklama, dusunce notu, Markdown basligi veya \`\`\` kod bloğu YAZMA. ` +
|
|
1221
|
+
`Kullaniciya iletmek istedigin her seyi ilgili JSON alanina ("final" veya "summary") koy; nesnenin disindaki metin kullaniciya ULASMAZ.\n` +
|
|
1222
|
+
`Yalnizca katalogdaki agent adlarini kullan. Her assignment kind degeri secilen agentin allowedKinds listesinde olmali. Kendine gorev atama. Ayni delegasyon id'sini tekrar kullanma.\n` +
|
|
1223
|
+
`Bir agent basarisiz veya kullanilamaz raporlandiysa ayni isi ona tekrar verme; katalogdaki alternatif bir uzmani sec.\n` +
|
|
1224
|
+
`AJAN SECIMI: Her alt gorevi, katalogdaki YETENEKLERE ve role gore o ise EN UYGUN uzmana ata; tum isi tek bir CLI'ye yigma. Isin ihtiyac duydugu her uzmanlik icin dogru uzmani sec (plan/tasarim -> planlama yetenegi; uygulama -> implementation; inceleme/dogrulama -> review/test; arastirma -> research/web). Ayni KIND icin birden fazla eşdeğer agent varsa YALNIZCA birini (en uygun ve en dusuk maliyetli) kullan; ayni isi iki eşdeğer agente verme. Farkli roller (or. bir uygulayici + bir inceleyici) FARKLI islerdir, tekrar sayilmaz.\n` +
|
|
1225
|
+
`## Calisma stratejisi\n${strategy}\n` +
|
|
1226
|
+
`## Agent katalogu\n${JSON.stringify(catalog, null, 2)}\n` +
|
|
1227
|
+
skillSection +
|
|
1228
|
+
`## Kullanici gorevi\n${task.prompt}\n` +
|
|
1229
|
+
`## Calisma klasoru\n${this._cwd}\n` +
|
|
1230
|
+
`SANDBOX: Butun is YALNIZCA bu klasorde yapilir. Bu klasor disina (ust dizinler, orkestrator/kurulum dizini, sistem dosyalari) dosya YAZMA/DEGISTIRME/SILME ve gerekmedikce disariyi OKUMA. Delegasyon talimatlarini da bu sinira gore yaz.\n` +
|
|
1231
|
+
`## Proje profili (bu klasorun birikmis baglami — tum kodu bastan taramak zorunda kalmayasin diye. IPUCUDUR, kanit degil: kritik kararlarda DOSYADAN dogrula, celiski gorursen profile guvenme. Gecmis teslimatlar mevcut gorevi TAMAMLANMIS SAYDIRMAZ.)\n${clip(memory || "(henuz proje profili yok — ilk gorevde olusturulacak)", cfg.projectContextCharBudget || cfg.memoryCharBudget || 8000)}\n` +
|
|
1232
|
+
(phase === "review"
|
|
1233
|
+
? `## Son bagimsiz denetim\n${(() => { const review = this.latestReview(state); return review ? `${review.id} (${review.agent}) → VERDICT: ${review.verdict}` : "Henuz tamamlanmis denetim yok."; })()}\n` +
|
|
1234
|
+
`## Takim calisma kaydi\n${this.teamDigest(state, cfg)}\n`
|
|
1235
|
+
: "");
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
specialistPrompt(task, cfg, assignment, state) {
|
|
1239
|
+
const agent = cfg.agents[assignment.agent];
|
|
1240
|
+
const roleByKind = { implement: "executor.md", review: "reviewer.md", plan: "planner.md" };
|
|
1241
|
+
const selectedRole = roleByKind[assignment.kind] || (agent.roleFile ? path.basename(agent.roleFile) : "executor.md");
|
|
1242
|
+
const role = store.readRole(selectedRole);
|
|
1243
|
+
const instructionByKind = {
|
|
1244
|
+
implement: "Delegasyonu calisma klasorunde uygula, degisiklikleri dogrula ve rolundeki teslimat formatinda raporla.",
|
|
1245
|
+
review: "Teslimati bagimsiz ve salt okunur olarak incele; gerekli kontrolleri calistir, dosyalari degistirme ve rolundeki karar formatinda raporla.",
|
|
1246
|
+
plan: "Calisma klasorunu salt okunur olarak incele; hicbir dosyayi degistirmeden uygulanabilir plani rolundeki formatta raporla.",
|
|
1247
|
+
research: "Delegasyon kapsaminda kanit topla; kaynaklari ve belirsizlikleri ayirarak kisa bir sonuc raporla.",
|
|
1248
|
+
};
|
|
1249
|
+
const completed = Object.values(state.results).map((r) => ({
|
|
1250
|
+
id: r.id, agent: r.agent, instruction: r.instruction, ...(r.verdict ? { verdict: r.verdict } : {}), result: clipMiddle(r.result, 5000),
|
|
1251
|
+
}));
|
|
1252
|
+
// Progressive disclosure: operatorun bu delegasyona sectigi becerilerin tam govdesini YIGMAYIZ;
|
|
1253
|
+
// yalnizca ad + kisa ozet + rehber dosya yolunu veririz. Uzman gercekten ihtiyac duyarsa dosyayi
|
|
1254
|
+
// kendisi okur. Operator hicbir beceri secmediyse (veya beceriler kapali ise) blok bostur.
|
|
1255
|
+
const skills = skillRegistry.resolveForAssignment(assignment.skills, cfg);
|
|
1256
|
+
const skillRefs = skillRegistry.toPromptRefs(skills, cfg.skills?.referenceCharBudget || 1200);
|
|
1257
|
+
return `${role}\n\n---\n## Takim agenti protokolu\n` +
|
|
1258
|
+
`Operator sana asagidaki isi devretti. ${instructionByKind[assignment.kind] || instructionByKind.implement} ` +
|
|
1259
|
+
`Planda olmayan riskli bir is gerekiyorsa yapma; BLOCKED olarak bildir. Yalnizca gercekten gozlemledigin veya dogruladigin sonuclari yaz.\n` +
|
|
1260
|
+
`SANDBOX: Butun is YALNIZCA calisma klasorunde (${this._cwd}) yapilir. Bu klasor disina dosya YAZMA/DEGISTIRME/SILME ve gerekmedikce disariyi OKUMA.\n` +
|
|
1261
|
+
(skillRefs ? `## Uygulanacak beceriler\nBu is icin asagidaki beceri rehberleri secildi. Her satirda becerinin OZETI ve TAM rehberin DOSYA YOLU var. Ozet yeterliyse dogrudan uygula; daha fazla ayrinti gerekiyorsa ilgili dosyayi OKU ve prosedure uy. Ilgisiz bir sey varsa gormezden gel.\n${skillRefs}\n` : "") +
|
|
1262
|
+
`## Ana hedef\n${task.prompt}\n## Delegasyon\nID: ${assignment.id}\n${assignment.instruction}\n` +
|
|
1263
|
+
`## Onceki tamamlanan takim isleri\n${clip(JSON.stringify(completed, null, 2), cfg.teamContextCharBudget || 30000)}`;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
async runAssignments(task, cfg, assignments, state) {
|
|
1267
|
+
const pending = assignments.slice();
|
|
1268
|
+
while (pending.length) {
|
|
1269
|
+
let index = pending.findIndex((a) => a.dependsOn.every((id) => state.results[id]?.status === "completed"));
|
|
1270
|
+
if (index < 0) {
|
|
1271
|
+
index = pending.findIndex((a) => a.dependsOn.some((id) => state.results[id] && state.results[id].status !== "completed"));
|
|
1272
|
+
if (index >= 0) {
|
|
1273
|
+
const blocked = pending.splice(index, 1)[0];
|
|
1274
|
+
state.usedIds.push(blocked.id);
|
|
1275
|
+
const failedDeps = blocked.dependsOn.filter((id) => state.results[id]?.status !== "completed");
|
|
1276
|
+
state.results[blocked.id] = { ...blocked, status: "blocked", result: `Bagimli gorev basarisiz: ${failedDeps.join(", ")}` };
|
|
1277
|
+
const blockedMessage = { from: "system", to: task.operatorCli, messageType: "blocked", assignmentId: blocked.id, body: `${blocked.id} calistirilmadi; bagimli gorev basarisiz: ${failedDeps.join(", ")}`, at: new Date().toISOString() };
|
|
1278
|
+
state.messages.push(blockedMessage);
|
|
1279
|
+
this.publish("message", blockedMessage, task.id);
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
throw new Error(`Delegasyon bagimliliklari cozumlenemedi: ${pending.map((a) => a.id).join(", ")}`);
|
|
1283
|
+
}
|
|
1284
|
+
const assignment = pending.splice(index, 1)[0];
|
|
1285
|
+
state.usedIds.push(assignment.id);
|
|
1286
|
+
state.messages.push({ from: task.operatorCli, to: assignment.agent, messageType: "delegation", assignmentId: assignment.id, body: assignment.instruction, at: new Date().toISOString() });
|
|
1287
|
+
this.publish("message", state.messages[state.messages.length - 1], task.id);
|
|
1288
|
+
if (assignment.routingReason) this.publish("log", { level: "info", msg: `Otomatik yonlendirme: ${assignment.routingReason}` }, task.id);
|
|
1289
|
+
if (assignment.renamedFrom) this.publish("log", { level: "warn", msg: `Operator delegasyon kimligini yineledi; ${assignment.renamedFrom} otomatik olarak ${assignment.id} yapildi.` }, task.id);
|
|
1290
|
+
this.publish("log", { level: "stage", msg: `DELEGE ${assignment.id} -> ${assignment.agent}` }, task.id);
|
|
1291
|
+
if (assignment.skills?.length) this.publish("log", { level: "info", msg: `Beceriler: ${assignment.skills.join(", ")}` }, task.id);
|
|
1292
|
+
try {
|
|
1293
|
+
const response = await this.invokeAgent(
|
|
1294
|
+
assignment.agent,
|
|
1295
|
+
this.specialistPrompt(task, cfg, assignment, state),
|
|
1296
|
+
cfg,
|
|
1297
|
+
{ stage: "delegate", assignmentId: assignment.id }
|
|
1298
|
+
);
|
|
1299
|
+
const result = { ...assignment, status: "completed", result: response.text, durationMs: response.durationMs, callId: response.callId };
|
|
1300
|
+
if (assignment.kind === "review") {
|
|
1301
|
+
result.verdict = extractVerdict(response.text);
|
|
1302
|
+
this.publish("log", { level: "info", msg: `Denetim ${assignment.id}: VERDICT ${result.verdict || "BELIRSIZ"}` }, task.id);
|
|
1303
|
+
}
|
|
1304
|
+
state.results[assignment.id] = result;
|
|
1305
|
+
const message = { from: assignment.agent, to: task.operatorCli, messageType: "result", assignmentId: assignment.id, body: response.text, at: new Date().toISOString() };
|
|
1306
|
+
state.messages.push(message);
|
|
1307
|
+
this.publish("message", message, task.id);
|
|
1308
|
+
} catch (error) {
|
|
1309
|
+
const failure = classifyCliError(error);
|
|
1310
|
+
if (QUARANTINE_CLI_ERRORS.has(failure.code)) {
|
|
1311
|
+
this.unhealthyAgents.set(assignment.agent, { code: failure.code, at: new Date().toISOString() });
|
|
1312
|
+
cfg.runtimeUnavailableAgents = [...this.unhealthyAgents.keys()];
|
|
1313
|
+
}
|
|
1314
|
+
state.results[assignment.id] = { ...assignment, status: "failed", result: failure.summary, error: failure };
|
|
1315
|
+
const message = { from: assignment.agent, to: task.operatorCli, messageType: "failure", assignmentId: assignment.id, body: `${failure.summary}\n${failure.action}`, errorCode: failure.code, at: new Date().toISOString() };
|
|
1316
|
+
state.messages.push(message);
|
|
1317
|
+
this.publish("message", message, task.id);
|
|
1318
|
+
this.publish("log", { level: "warn", msg: `${assignment.agent} kullanilamadi [${failure.code}]. Operator alternatif agent sececek.` }, task.id);
|
|
1319
|
+
}
|
|
1320
|
+
task.teamState = state;
|
|
1321
|
+
store.saveTask("pending", task);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
isRisky(text, cfg) {
|
|
1326
|
+
const lower = String(text).toLowerCase();
|
|
1327
|
+
return (cfg.riskyPatterns || []).some((pattern) => lower.includes(String(pattern).toLowerCase()));
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
async runTask(task) {
|
|
1331
|
+
const baseCfg = this.cfg();
|
|
1332
|
+
if (!baseCfg.autonomousConsentAcceptedAt) throw new Error("Otonom CLI calistirma kosullari kabul edilmeden gorev calistirilamaz.");
|
|
1333
|
+
if (task.kind === "operator-chat") return this.runChatTask(task, baseCfg);
|
|
1334
|
+
task.executionMode = resolveExecutionMode(task);
|
|
1335
|
+
const cfg = applyExecutionPolicy(baseCfg, task.executionMode);
|
|
1336
|
+
const operatorCli = task.operatorCli || cfg.operator?.cli;
|
|
1337
|
+
if (!operatorCli || !cliRegistry.operatorSpec(operatorCli, cfg)) throw new Error("Gecerli bir operator CLI secilmedi (claude/codex/gemini/opencode).");
|
|
1338
|
+
task.operatorCli = operatorCli;
|
|
1339
|
+
this.busy = true;
|
|
1340
|
+
this.current = { id: task.id, stage: "operator", agent: operatorCli };
|
|
1341
|
+
this._cwd = path.resolve(store.WORK_BASE, task.targetDir || cfg.workingDir || ".");
|
|
1342
|
+
this._snapBefore = snapshotDir(this._cwd);
|
|
1343
|
+
this.startLiveDiff(task);
|
|
1344
|
+
// Onay sonrasi devam eden gorevde sayaci sifirlamayiz; ilk girise ozgu.
|
|
1345
|
+
if (!task.approved || !this._usage) this.resetUsage();
|
|
1346
|
+
this.publish("log", { level: "task", msg: `GOREV ${task.id}: ${task.prompt}` }, task.id);
|
|
1347
|
+
// Otomatik surumleme: gorev CALISMADAN ONCE calisma klasorunun bir surumunu al ki ajan
|
|
1348
|
+
// mevcut kodu bozarsa bu gorev oncesine tek tikla donulebilsin. Yalnizca ilk girus'te
|
|
1349
|
+
// (approval sonrasi devam da ayni checkpoint'i korur). Basarisizsa gorevi asla oldurmez.
|
|
1350
|
+
if (baseCfg.versioning !== false && !task.checkpointId) {
|
|
1351
|
+
const cp = checkpoints.createCheckpoint(this._cwd, { taskId: task.id, label: task.prompt, kind: "pre-task", retention: baseCfg.versioningRetention });
|
|
1352
|
+
if (cp.ok) {
|
|
1353
|
+
task.checkpointId = cp.id;
|
|
1354
|
+
store.saveTask("pending", task);
|
|
1355
|
+
this.publish("log", { level: "info", msg: `Surum alindi (${cp.fileCount} dosya · ${cp.backend}); bu gorev oncesine geri donulebilir.` }, task.id);
|
|
1356
|
+
} else if (cp.skipped) {
|
|
1357
|
+
this.publish("log", { level: "warn", msg: `Surum alinamadi: ${cp.reason}` }, task.id);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
this.publish("log", { level: "info", msg: `Calisma modu: ${task.executionMode.toUpperCase()} · en fazla ${cfg.operator.maxRounds} tur / ${cfg.operator.maxDelegationsPerRound} delegasyon` }, task.id);
|
|
1361
|
+
this.emit("status", this.status());
|
|
1362
|
+
this.emit("queue");
|
|
1363
|
+
|
|
1364
|
+
cfg.runtimeUnavailableAgents = [...this.unhealthyAgents.keys()];
|
|
1365
|
+
// Proje context: varsayilan olarak bu calisma klasorunun .crewctl/CONTEXT.md profilini
|
|
1366
|
+
// yukleriz (path'e ozel, tum kodu bastan taramaya gerek kalmasin). task.freshContext ise
|
|
1367
|
+
// temiz sayfa. projectContext:false ise eski GLOBAL hafiza davranisi korunur.
|
|
1368
|
+
const memory = cfg.projectContext === false
|
|
1369
|
+
? store.getMemory(cfg.memoryCharBudget)
|
|
1370
|
+
: (task.freshContext ? "" : store.readProjectContext(this._cwd));
|
|
1371
|
+
const state = task.teamState || { round: 0, plan: null, criteria: [], results: {}, messages: [], operatorDecisions: [], usedIds: [] };
|
|
1372
|
+
const usedIds = new Set(state.usedIds || []);
|
|
1373
|
+
|
|
1374
|
+
let assignments;
|
|
1375
|
+
if (state.plan && task.approved) {
|
|
1376
|
+
assignments = normalizeAssignments(state.plan.assignments, cfg, operatorCli, usedIds, task.prompt);
|
|
1377
|
+
assignments = ensureBalancedRoleChain(assignments, cfg, operatorCli, task, usedIds);
|
|
1378
|
+
this.publish("log", { level: "info", msg: "Onaylanmis operator plani zorunlu rol zinciri korunarak devam ettiriliyor." }, task.id);
|
|
1379
|
+
} else {
|
|
1380
|
+
// Bilgi sorusu kestirmesi: operator, delege edilecek bir is olmadan yaniti dogrudan
|
|
1381
|
+
// verdiginde (or. "kac beceri var") tek turda tamamla. Boylece motorun otoriter verisi
|
|
1382
|
+
// bir alt agente aktarilirken kaybolmaz/carpitilmaz ve zorunlu assignment semasi bu
|
|
1383
|
+
// tur sorularda gorevi bosuna oldurmez. ANCAK gorev acikca bir yapim/degisiklik isiyse
|
|
1384
|
+
// kestirmeye izin verilmez: operator (cogunlukla proje hafizasindaki gecmis teslimati
|
|
1385
|
+
// "zaten yapildi" sanip) is uretmeden kapatmaya calisir. Once yeniden planlama isteriz;
|
|
1386
|
+
// israr ederse gorevi sahte basari yerine net bir hatayla dururuz.
|
|
1387
|
+
const requiresWork = taskRequiresDelegation(task.prompt);
|
|
1388
|
+
const shortcutRetries = requiresWork ? Math.max(1, cfg.operator?.protocolRetries ?? 1) : 0;
|
|
1389
|
+
let plan, shortcutAttempt = 0, planCorrection = "";
|
|
1390
|
+
while (true) {
|
|
1391
|
+
try {
|
|
1392
|
+
plan = await this.invokeOperatorJson(operatorCli, this.operatorPrompt(task, cfg, memory, state, "plan") + planCorrection, cfg, "operator-plan", "Operator plani");
|
|
1393
|
+
} catch (error) {
|
|
1394
|
+
// Sohbet kurtarma: gorev delegasyon gerektirmiyorsa (selamlasma, kisa soru) ve operator
|
|
1395
|
+
// protokol nesnesi yerine duz metinle cevap verdiyse, kullaniciyi "gecerli JSON
|
|
1396
|
+
// dondurmedi" hatasiyla karsilamak yanlistir — istenen sey zaten o metindi. Yalnizca
|
|
1397
|
+
// is gerektirmeyen gorevlerde ve elde gercek bir metin varken devreye girer.
|
|
1398
|
+
const prose = conversationalAnswer(error);
|
|
1399
|
+
if (requiresWork || !prose) throw error;
|
|
1400
|
+
this.publish("log", { level: "warn", msg: "Operator protokol nesnesi dondurmedi; gorev is gerektirmedigi icin duz metin cevabi teslimat sayildi." }, task.id);
|
|
1401
|
+
task.teamState = state;
|
|
1402
|
+
return this.complete(task, state, "done", prose, { verification: "Operatorun dogrudan yaniti (protokol nesnesi yerine duz metin)." });
|
|
1403
|
+
}
|
|
1404
|
+
const isShortcut = plan && String(plan.status).toLowerCase() === "complete" && !Array.isArray(plan.assignments);
|
|
1405
|
+
if (!isShortcut) break;
|
|
1406
|
+
if (!requiresWork) {
|
|
1407
|
+
if (!String(plan.final || "").trim()) throw new Error("Operator complete dedi fakat final sonucu bos birakti.");
|
|
1408
|
+
this.publish("log", { level: "info", msg: "Operator gorevi delegasyona gerek gormeden dogrudan yanitladi." }, task.id);
|
|
1409
|
+
task.teamState = state;
|
|
1410
|
+
return this.complete(task, state, "done", String(plan.final), plan);
|
|
1411
|
+
}
|
|
1412
|
+
if (shortcutAttempt++ >= shortcutRetries) {
|
|
1413
|
+
throw new Error("Operator bu yapim gorevini delege etmeden kapatmaya calisti. Proje hafizasindaki gecmis teslimatlar gorevi tamamlanmis saymaz; en az bir uygulama delegasyonu gerekir.");
|
|
1414
|
+
}
|
|
1415
|
+
this.publish("log", { level: "warn", msg: "Operator yapim gorevini delegasyonsuz kapatmaya calisti; hafiza gecmisi kanit sayilmaz, yeniden planlama isteniyor." }, task.id);
|
|
1416
|
+
planCorrection = "\n\nUYARI: Bu GERCEK bir yapim/degisiklik gorevidir. Proje hafizasindaki gecmis teslimatlar (or. onceki turlarda uretilmis dosyalar) bu gorevi TAMAMLANMIS SAYDIRMAZ; kullanici isin bu oturumda yeniden uretilip dogrulanmasini istiyor. status=complete DONME; en az bir 'implement' delegasyonu iceren bir plan uret.";
|
|
1417
|
+
}
|
|
1418
|
+
assignments = normalizeAssignments(plan.assignments, cfg, operatorCli, usedIds, task.prompt);
|
|
1419
|
+
assignments = ensureBalancedRoleChain(assignments, cfg, operatorCli, task, usedIds);
|
|
1420
|
+
state.plan = { summary: String(plan.summary || ""), completionCriteria: Array.isArray(plan.completionCriteria) ? plan.completionCriteria.map(String) : [], assignments };
|
|
1421
|
+
state.criteria = state.plan.completionCriteria;
|
|
1422
|
+
state.operatorDecisions.push({ round: 0, ...state.plan });
|
|
1423
|
+
task.teamState = state;
|
|
1424
|
+
task.planPreview = JSON.stringify(state.plan, null, 2);
|
|
1425
|
+
store.saveTask("pending", task);
|
|
1426
|
+
if (cfg.approvalMode === "ask" && this.isRisky(task.planPreview, cfg)) {
|
|
1427
|
+
task.status = "awaiting-approval";
|
|
1428
|
+
task.planHash = store.hashText(task.planPreview);
|
|
1429
|
+
store.moveTask("pending", "approval", task);
|
|
1430
|
+
this.publish("log", { level: "warn", msg: "Riskli operator plani insan onayina alindi." }, task.id);
|
|
1431
|
+
this.busy = false;
|
|
1432
|
+
this.current = null;
|
|
1433
|
+
this.emit("queue");
|
|
1434
|
+
this.emit("status", this.status());
|
|
1435
|
+
this.stopLiveDiff();
|
|
1436
|
+
this._textBefore = null;
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
const maxRounds = Math.max(1, cfg.operator?.maxRounds || 6);
|
|
1442
|
+
const maxRecoveryRounds = Math.max(0, cfg.operator?.maxInfrastructureRecoveryRounds ?? 2);
|
|
1443
|
+
let recoveryRounds = 0;
|
|
1444
|
+
while (this.running && state.round < maxRounds + recoveryRounds) {
|
|
1445
|
+
state.round++;
|
|
1446
|
+
await this.runAssignments(task, cfg, assignments, state);
|
|
1447
|
+
const infrastructureFailures = assignments.filter((assignment) => {
|
|
1448
|
+
const result = state.results[assignment.id];
|
|
1449
|
+
return result?.status === "failed" && RECOVERABLE_CLI_ERRORS.has(result.error?.code);
|
|
1450
|
+
});
|
|
1451
|
+
if (infrastructureFailures.length && recoveryRounds < maxRecoveryRounds) {
|
|
1452
|
+
recoveryRounds++;
|
|
1453
|
+
this.publish("log", { level: "warn", msg: `CLI altyapi hatasi tur butcesinden dusulmedi; ${maxRecoveryRounds - recoveryRounds} ek kurtarma turu kaldi.` }, task.id);
|
|
1454
|
+
}
|
|
1455
|
+
// Uygulama agenti kalmadi guvenligi: Gorev acikca dosya olusturma/degistirme gerektiriyor
|
|
1456
|
+
// fakat katalogda 'implement' yapabilen kullanilabilir hicbir agent kalmadiysa (or. tek
|
|
1457
|
+
// executor opencode sessizlik nedeniyle karantinaya alindi) ve simdiye kadar tamamlanmis bir
|
|
1458
|
+
// uygulama isi da yoksa, HICBIR ek tur dosya uretemez. Operatoru planlayici/gap-analizi
|
|
1459
|
+
// dongusunde bosuna dondurup tur ve cagri butcesini yakmak yerine net, eyleme donuk bir
|
|
1460
|
+
// hatayla dur (kullanicinin gordugu "circle seklinde uzayip gitme" sorununu bu keser).
|
|
1461
|
+
if (taskRequiresDelegation(task.prompt)
|
|
1462
|
+
&& !this.hasUsableImplementAgent(cfg, operatorCli)
|
|
1463
|
+
&& !Object.values(state.results).some((result) => result.kind === "implement" && result.status === "completed")) {
|
|
1464
|
+
const dead = [...this.unhealthyAgents.keys()];
|
|
1465
|
+
throw new Error(
|
|
1466
|
+
"Bu gorev dosya olusturma/degistirme gerektiriyor fakat kullanilabilir bir uygulama (implement) agenti yok" +
|
|
1467
|
+
(dead.length ? ` (${dead.join(", ")} bu oturumda kullanim disi kaldi)` : "") +
|
|
1468
|
+
". Ayarlar > Agent'lar'dan 'implementation' yetenekli bir agent (Codex/Claude/Gemini/OpenCode) ekleyip etkinlestirin veya opencode'un oturum/model durumunu kontrol edin, sonra gorevi tekrar calistirin."
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1471
|
+
if (task.executionMode === "fast" && assignments.every((assignment) => state.results[assignment.id]?.status === "completed")) {
|
|
1472
|
+
const reports = assignments.map((assignment) => state.results[assignment.id].result).join("\n\n");
|
|
1473
|
+
this.publish("log", { level: "info", msg: "FAST mod: uzman teslimati basarili; ikinci operator degerlendirme cagrisi atlandi." }, task.id);
|
|
1474
|
+
return this.complete(task, state, "done", reports, {});
|
|
1475
|
+
}
|
|
1476
|
+
// PASS hizli yolu: turdaki tum delegasyonlar tamamlandi ve turun kendi bagimsiz
|
|
1477
|
+
// denetimi PASS verdiyse operatorun ikinci degerlendirme cagrisi bilgi eklemez,
|
|
1478
|
+
// yalnizca dakikalar kaybettirir. Bayat bir PASS'in yeni turu kapatmamasi icin
|
|
1479
|
+
// karar en guncel denetim olmalidir. operator.passFastPath=false ile kapatilabilir.
|
|
1480
|
+
const roundReview = assignments
|
|
1481
|
+
.map((assignment) => state.results[assignment.id])
|
|
1482
|
+
.filter((result) => result?.kind === "review" && result.status === "completed" && result.verdict)
|
|
1483
|
+
.pop();
|
|
1484
|
+
if (cfg.operator?.passFastPath !== false
|
|
1485
|
+
&& assignments.every((assignment) => state.results[assignment.id]?.status === "completed")
|
|
1486
|
+
&& roundReview?.verdict === "PASS"
|
|
1487
|
+
&& roundReview === this.latestReview(state)) {
|
|
1488
|
+
this.publish("log", { level: "ok", msg: `Denetim ${roundReview.id} PASS verdi; operator degerlendirme cagrisi atlandi ve teslimat tamamlandi.` }, task.id);
|
|
1489
|
+
const completedWork = Object.values(state.results).filter((result) => result.status === "completed");
|
|
1490
|
+
const final = `Teslimat bagimsiz denetimden gecti (${roundReview.id} → VERDICT: PASS).\n` +
|
|
1491
|
+
completedWork.map((result) => `- ${result.id} (${result.agent}${result.verdict ? ` · VERDICT: ${result.verdict}` : ""})`).join("\n");
|
|
1492
|
+
return this.complete(task, state, "done", final, { verification: `${roundReview.agent} → VERDICT: PASS` });
|
|
1493
|
+
}
|
|
1494
|
+
const decision = await this.invokeOperatorJson(operatorCli, this.operatorPrompt(task, cfg, memory, state, "review"), cfg, "operator-review", "Operator karari");
|
|
1495
|
+
state.operatorDecisions.push({ round: state.round, ...decision });
|
|
1496
|
+
task.teamState = state;
|
|
1497
|
+
store.saveTask("pending", task);
|
|
1498
|
+
if (String(decision.status).toLowerCase() === "complete") {
|
|
1499
|
+
if (!String(decision.final || "").trim()) throw new Error("Operator complete dedi fakat final sonucu bos birakti.");
|
|
1500
|
+
return this.complete(task, state, "done", String(decision.final), decision);
|
|
1501
|
+
}
|
|
1502
|
+
if (String(decision.status).toLowerCase() !== "continue") throw new Error("Operator status alani continue veya complete olmali.");
|
|
1503
|
+
assignments = normalizeAssignments(decision.assignments, cfg, operatorCli, new Set(state.usedIds), task.prompt);
|
|
1504
|
+
// Inceleme dongusu valisi: bagimsiz denetim PASS verdiyse ve operator yalnizca yeni
|
|
1505
|
+
// inceleme turlari acmak istiyorsa dongu burada kesilir. Sonsuz review ping-pong'u
|
|
1506
|
+
// hem tur butcesini tuketiyor hem de basarili teslimati kullaniciya geciktiriyordu.
|
|
1507
|
+
const lastReview = this.latestReview(state);
|
|
1508
|
+
if (lastReview?.verdict === "PASS" && assignments.every((assignment) => assignment.kind === "review")) {
|
|
1509
|
+
this.publish("log", { level: "warn", msg: `Denetim ${lastReview.id} PASS verdi; yalnizca yeni inceleme iceren tur acilmadi ve teslimat tamamlandi.` }, task.id);
|
|
1510
|
+
const final = `Teslimat bagimsiz denetimden gecti (${lastReview.id} → VERDICT: PASS). ` +
|
|
1511
|
+
`Operatorun ek inceleme talebi tur butcesini korumak icin motor tarafindan sonlandirildi.` +
|
|
1512
|
+
(String(decision.reason || "").trim() ? `\nOperator notu: ${clip(decision.reason, 600)}` : "");
|
|
1513
|
+
return this.complete(task, state, "done", final, { verification: `${lastReview.agent} → VERDICT: PASS` });
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
if (!this.running) throw new Error("Motor durduruldu; gorev tamamlanmadan kesildi.");
|
|
1517
|
+
return this.finishExhausted(task, state, recoveryRounds);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// Tur butcesi doldugunda tamamlanmis isi cope atmak yerine uyarili kismi teslimat yapar.
|
|
1521
|
+
// Kullanicinin gordugu sonuc "saatlerce bekledim ve hata aldim" degil, "teslimat hazir,
|
|
1522
|
+
// su riskler acik kaldi" olmalidir. Hic tamamlanan is yoksa eski davranis korunur.
|
|
1523
|
+
finishExhausted(task, state, recoveryRounds) {
|
|
1524
|
+
const completedWork = Object.values(state.results || {}).filter((result) => result.status === "completed");
|
|
1525
|
+
if (!completedWork.length) {
|
|
1526
|
+
throw new Error(`Operator ${state.round} tur sonunda gorevi tamamlayamadi${recoveryRounds ? ` (${recoveryRounds} CLI kurtarma turu kullanildi)` : ""}.`);
|
|
1527
|
+
}
|
|
1528
|
+
const lastReview = this.latestReview(state);
|
|
1529
|
+
const lastDecision = [...(state.operatorDecisions || [])].reverse().find((decision) => String(decision.status).toLowerCase() === "continue");
|
|
1530
|
+
const warnings = [
|
|
1531
|
+
`Tur butcesi doldu (${state.round} tur); operator complete karari veremeden teslimat kapatildi.`,
|
|
1532
|
+
...(lastReview ? [`Son bagimsiz denetim: ${lastReview.id} → VERDICT: ${lastReview.verdict}.`] : []),
|
|
1533
|
+
...(lastDecision && String(lastDecision.reason || "").trim() ? [`Operatorun acik biraktigi konu: ${clip(lastDecision.reason, 400)}`] : []),
|
|
1534
|
+
];
|
|
1535
|
+
this.publish("log", { level: "warn", msg: `Tur butcesi doldu; tamamlanan is kismi teslimat olarak kapatiliyor (${completedWork.length} tamamlanmis delegasyon).` }, task.id);
|
|
1536
|
+
const final = `KISMI TESLIMAT: Tur butcesi doldugu icin gorev, tamamlanan isle kapatildi.\n` +
|
|
1537
|
+
completedWork.map((result) => `- ${result.id} (${result.agent}${result.verdict ? ` · VERDICT: ${result.verdict}` : ""})`).join("\n") +
|
|
1538
|
+
`\nAcik kalan konular teslimat uyarilarinda listelendi.`;
|
|
1539
|
+
return this.complete(task, state, "done", final, {
|
|
1540
|
+
warnings,
|
|
1541
|
+
verification: lastReview ? `${lastReview.agent} → VERDICT: ${lastReview.verdict}` : "",
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// Gorev beklenmedik bir hatayla kesildiginde (protokol hatasi, butce asimi vb.) somut is
|
|
1546
|
+
// uretilmisse gorevi failed yerine uyarili kismi teslimatla kapatir. Basarili olamazsa
|
|
1547
|
+
// false doner ve normal hata akisi calisir.
|
|
1548
|
+
async salvage(task, error) {
|
|
1549
|
+
try {
|
|
1550
|
+
if (task.kind === "operator-chat") return false;
|
|
1551
|
+
if (!this._cwd || !this._snapBefore) return false;
|
|
1552
|
+
const state = task.teamState;
|
|
1553
|
+
const completedWork = Object.values(state?.results || {}).filter((result) => result.status === "completed");
|
|
1554
|
+
const changes = diffSnapshots(this._snapBefore, snapshotDir(this._cwd));
|
|
1555
|
+
const hasFileChanges = Boolean(changes.created.length || changes.modified.length || changes.deleted.length);
|
|
1556
|
+
const hasImplementation = completedWork.some((result) => result.kind === "implement");
|
|
1557
|
+
// Kurtarma kosulu: YA tamamlanmis bir uygulama delegasyonu var (ekip uretti) YA DA calisma
|
|
1558
|
+
// klasoru fiilen degisti. Ikincisi, operatorun kendisi standart JSON protokolune uymadan
|
|
1559
|
+
// isi dogrudan yaptigi durumu da kapsar (or. OpenCode operator rolunde JSON plan yerine
|
|
1560
|
+
// dosyalari kendisi degistirip duz metin doner). Hicbir is yoksa normal hata akisina birak.
|
|
1561
|
+
if (!hasFileChanges && !hasImplementation) return false;
|
|
1562
|
+
const failure = classifyCliError(error);
|
|
1563
|
+
const rescueState = state || { round: 0, plan: null, criteria: [], results: {}, messages: [], operatorDecisions: [], usedIds: [] };
|
|
1564
|
+
const operatorDirect = !completedWork.length && hasFileChanges;
|
|
1565
|
+
this.publish("log", { level: "warn", msg: `Gorev hatayla kesildi (${failure.summary}); ${operatorDirect ? "operatorun dogrudan yaptigi degisiklikler" : "tamamlanan is"} kismi teslimat olarak korunuyor.` }, task.id);
|
|
1566
|
+
const lastReview = this.latestReview(rescueState);
|
|
1567
|
+
const changedFiles = [...changes.created, ...changes.modified, ...changes.deleted];
|
|
1568
|
+
const final = operatorDirect
|
|
1569
|
+
? `KISMI TESLIMAT: Operator (${task.operatorCli || "?"}) standart delegasyon protokolune uymadan isi dogrudan uyguladi; degisiklikler korundu.\n` +
|
|
1570
|
+
changedFiles.slice(0, 40).map((file) => `- ${file}`).join("\n")
|
|
1571
|
+
: `KISMI TESLIMAT: Gorev bir altyapi/protokol hatasiyla kesildi fakat tamamlanan is korundu.\n` +
|
|
1572
|
+
completedWork.map((result) => `- ${result.id} (${result.agent}${result.verdict ? ` · VERDICT: ${result.verdict}` : ""})`).join("\n");
|
|
1573
|
+
await this.complete(task, rescueState, "done", final, {
|
|
1574
|
+
warnings: [
|
|
1575
|
+
`Gorev su hatayla kesildi: ${failure.summary}`,
|
|
1576
|
+
...(operatorDirect ? [`Operator CLI (${task.operatorCli || "?"}) plan JSON'i yerine isi dogrudan yapti. Daha guvenilir orkestrasyon icin operator olarak JSON planlamaya uygun bir CLI (or. Codex veya Claude) secmeyi dusunun.`] : []),
|
|
1577
|
+
...(lastReview ? [`Son bagimsiz denetim: ${lastReview.id} → VERDICT: ${lastReview.verdict}.`] : []),
|
|
1578
|
+
],
|
|
1579
|
+
verification: lastReview ? `${lastReview.agent} → VERDICT: ${lastReview.verdict}` : "",
|
|
1580
|
+
});
|
|
1581
|
+
return true;
|
|
1582
|
+
} catch {
|
|
1583
|
+
return false;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
async runChatTask(task, cfg) {
|
|
1588
|
+
const parentFound = store.findTask(task.parentTaskId);
|
|
1589
|
+
if (!parentFound) throw new Error("Sohbetin bagli oldugu tamamlanmis gorev bulunamadi.");
|
|
1590
|
+
const parent = parentFound.task;
|
|
1591
|
+
let operatorCli = parent.operatorCli;
|
|
1592
|
+
if (!operatorCli || !cliRegistry.operatorSpec(operatorCli, cfg)) operatorCli = cfg.operator?.cli;
|
|
1593
|
+
if (!operatorCli || !cliRegistry.operatorSpec(operatorCli, cfg)) throw new Error("Sohbet icin operator CLI bulunamadi.");
|
|
1594
|
+
this.busy = true;
|
|
1595
|
+
this.current = { id: task.id, stage: "operator-chat", agent: operatorCli };
|
|
1596
|
+
this._cwd = path.resolve(store.WORK_BASE, parent.targetDir || cfg.workingDir || ".");
|
|
1597
|
+
this.publish("log", { level: "task", msg: `OPERATOR SOHBETI ${parent.id}: ${task.prompt}` }, task.id);
|
|
1598
|
+
this.emit("status", this.status());
|
|
1599
|
+
|
|
1600
|
+
const role = store.readRole("operator-chat.md");
|
|
1601
|
+
const resultContext = Object.values(parent.teamState?.results || {}).map((result) => ({
|
|
1602
|
+
id: result.id, agent: result.agent, status: result.status, result: clip(result.result, 1800),
|
|
1603
|
+
}));
|
|
1604
|
+
const prompt = `${role}\n\n---\n## Tamamlanmis gorev hakkinda takip sohbeti\n` +
|
|
1605
|
+
`Kullanici daha once tamamladiginiz gorev hakkinda soru soruyor. Yeni dosya degistirme, arac kullanma veya delegasyon yapma. ` +
|
|
1606
|
+
`Asagidaki kayitlara dayanarak dogrudan, kisa ve acik cevap ver. Bilmiyorsan bunu belirt.\n` +
|
|
1607
|
+
`## Gorev kimligi\n${parent.id}\n## Gorev\n${parent.prompt}\n## Teslimat\n${JSON.stringify(parent.delivery || { summary: parent.summary, changes: parent.changes }, null, 2)}\n` +
|
|
1608
|
+
`## Takim raporlari\n${clip(JSON.stringify(resultContext, null, 2), 12000)}\n` +
|
|
1609
|
+
`## Onceki sohbet\n${clip(JSON.stringify(parent.conversation || [], null, 2), 8000)}\n## Yeni soru\n${task.prompt}`;
|
|
1610
|
+
const response = await this.invokeOperator(operatorCli, prompt, cfg, { stage: "operator-chat" });
|
|
1611
|
+
const entry = { question: task.prompt, answer: response.text, at: new Date().toISOString(), taskId: task.id };
|
|
1612
|
+
parent.conversation ||= [];
|
|
1613
|
+
parent.conversation.push(entry);
|
|
1614
|
+
store.saveTask(parentFound.state, parent);
|
|
1615
|
+
task.status = "done";
|
|
1616
|
+
task.finishedAt = new Date().toISOString();
|
|
1617
|
+
task.final = response.text;
|
|
1618
|
+
task.summary = clip(response.text, 1000);
|
|
1619
|
+
store.moveTask("pending", "done", task);
|
|
1620
|
+
this.publish("result", { id: task.id, kind: "operator-chat", parentTaskId: parent.id, answer: response.text, status: "done", operator: operatorCli }, task.id);
|
|
1621
|
+
this.publish("log", { level: "ok", msg: `Operator soruyu yanitladi: ${parent.id}` }, task.id);
|
|
1622
|
+
this.emit("queue");
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
async complete(task, state, status, finalText, decision = {}) {
|
|
1626
|
+
const changes = diffSnapshots(this._snapBefore || new Map(), snapshotDir(this._cwd));
|
|
1627
|
+
// Son periyodik taramadan sonra yapilan degisiklikleri de replay kaydina ve dashboard'a
|
|
1628
|
+
// aktar; ardindan run --once yolunda da timer'in acik kalmamasini garanti et.
|
|
1629
|
+
this.publishFileChanges(task.id);
|
|
1630
|
+
this.stopLiveDiff();
|
|
1631
|
+
task.status = status;
|
|
1632
|
+
task.finishedAt = new Date().toISOString();
|
|
1633
|
+
const files = [
|
|
1634
|
+
...changes.created.map((file) => ({ path: file, action: "created" })),
|
|
1635
|
+
...changes.modified.map((file) => ({ path: file, action: "modified" })),
|
|
1636
|
+
...changes.deleted.map((file) => ({ path: file, action: "deleted" })),
|
|
1637
|
+
];
|
|
1638
|
+
const concise = clip(String(finalText).replace(/\n{3,}/g, "\n\n"), 700);
|
|
1639
|
+
let verification = String(decision.verification || "").trim();
|
|
1640
|
+
if (!verification) {
|
|
1641
|
+
for (const result of Object.values(state.results)) {
|
|
1642
|
+
const match = String(result.result || "").match(/DO(?:Ğ|G\u0306|G)RULAMA:\s*([^\n`]+)/i);
|
|
1643
|
+
if (match) { verification = match[1].trim(); break; }
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
task.summary = concise;
|
|
1647
|
+
task.final = finalText;
|
|
1648
|
+
task.changes = changes;
|
|
1649
|
+
task.usage = this.usageSummary();
|
|
1650
|
+
const warnings = (Array.isArray(decision.warnings) ? decision.warnings : []).map(String).filter(Boolean);
|
|
1651
|
+
task.delivery = {
|
|
1652
|
+
summary: concise,
|
|
1653
|
+
files,
|
|
1654
|
+
location: this._cwd,
|
|
1655
|
+
verification,
|
|
1656
|
+
mode: task.executionMode,
|
|
1657
|
+
rounds: state.round,
|
|
1658
|
+
agents: [...new Set(Object.values(state.results).map((result) => result.agent))],
|
|
1659
|
+
...(warnings.length ? { warnings } : {}),
|
|
1660
|
+
};
|
|
1661
|
+
task.teamState = state;
|
|
1662
|
+
const found = store.findTask(task.id);
|
|
1663
|
+
store.moveTask(found?.state || "pending", status, task);
|
|
1664
|
+
store.appendMemory(`${task.id} [${status}] ${task.prompt}`, `${task.summary}\nDosyalar: ${[...changes.created, ...changes.modified, ...changes.deleted].join(", ")}`);
|
|
1665
|
+
this.publish("result", { id: task.id, prompt: task.prompt, status, dir: this._cwd, changes, summary: task.summary, delivery: task.delivery, usage: task.usage, operator: task.operatorCli, rounds: state.round }, task.id);
|
|
1666
|
+
this.publish("log", { level: "ok", msg: `GOREV tamamlandi: ${task.id}` }, task.id);
|
|
1667
|
+
this.notifyOutcome(task, status, { summary: task.summary, rounds: state.round, fileCount: files.length, verification: task.delivery.verification });
|
|
1668
|
+
this.emit("queue");
|
|
1669
|
+
this._textBefore = null;
|
|
1670
|
+
// Proje profilini guncelle (sonuc zaten kullaniciya iletildi; motor mesgul kaldigindan
|
|
1671
|
+
// sonraki gorevle es zamanli operator cagrisi olmaz). Basarisizsa gorevi ASLA etkilemez.
|
|
1672
|
+
await this.reviseProjectContext(task, status, files);
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
// Gorev bitiminde bu calisma klasorunun .crewctl/CONTEXT.md proje profilini operatore REVIZE
|
|
1676
|
+
// ettirir (append degil): mimari/yapi/konvansiyon/komutlar guncel kalir, bayatlamaz. Yalnizca
|
|
1677
|
+
// basarili ve DOSYA DEGISTIREN gorevlerde calisir (bilgi sorulari profili degistirmez).
|
|
1678
|
+
async reviseProjectContext(task, status, files) {
|
|
1679
|
+
try {
|
|
1680
|
+
const cfg = this.cfg();
|
|
1681
|
+
if (cfg.projectContext === false) return;
|
|
1682
|
+
if (status !== "done" || !this._cwd) return;
|
|
1683
|
+
if (!Array.isArray(files) || !files.length) return;
|
|
1684
|
+
const operatorCli = task.operatorCli || cfg.operator?.cli;
|
|
1685
|
+
if (!operatorCli || !cliRegistry.operatorSpec(operatorCli, cfg)) return;
|
|
1686
|
+
const budget = cfg.projectContextCharBudget || 6000;
|
|
1687
|
+
const current = store.readProjectContext(this._cwd);
|
|
1688
|
+
const changed = files.map((f) => `${f.action}: ${f.path}`).join("\n");
|
|
1689
|
+
const prompt =
|
|
1690
|
+
`Sen bir projenin YASAYAN bilgi tabanini (proje profili) guncelliyorsun. Amac: sonraki gorevlerde ` +
|
|
1691
|
+
`ajan tum kodu bastan taramadan projeyi anlasin.\n` +
|
|
1692
|
+
`Asagida MEVCUT profil ve SON tamamlanan gorevin ozeti + degisen dosyalar var. Guncel, oz ve DOGRU ` +
|
|
1693
|
+
`bir proje profili YAZ (Markdown). Kurallar:\n` +
|
|
1694
|
+
`- Degisen gerceklere gore REVIZE et; silinen/tasinan/degisen seyleri guncelle, bayat bilgi birakma.\n` +
|
|
1695
|
+
`- Dayanikli bilgiyi tut: amac, mimari, klasor/dosya yapisi, giris noktalari, onemli moduller, ` +
|
|
1696
|
+
`konvansiyonlar, kurulum/calistirma/test komutlari, dikkat noktalari.\n` +
|
|
1697
|
+
`- Task-by-task changelog YAZMA; tek bir GUNCEL DURUM profili olsun.\n` +
|
|
1698
|
+
`- En fazla ~${budget} karakter. YALNIZCA profil metnini dondur; aciklama/selamlama/JSON/kod-bloğu isareti YAZMA.\n\n` +
|
|
1699
|
+
`## Mevcut profil\n${current || "(henuz yok — sifirdan olustur)"}\n\n` +
|
|
1700
|
+
`## Son tamamlanan gorev\n${task.prompt}\n\n## Sonuc ozeti\n${task.summary || ""}\n\n## Degisen dosyalar\n${changed}`;
|
|
1701
|
+
const response = await this.invokeOperator(operatorCli, prompt, cfg, { stage: "operator-context" });
|
|
1702
|
+
const profile = stripCodeFence(String(response?.text || "")).trim();
|
|
1703
|
+
if (profile && store.writeProjectContext(this._cwd, clip(profile, budget * 2))) {
|
|
1704
|
+
this.publish("log", { level: "info", msg: "Proje profili (.crewctl/CONTEXT.md) guncellendi." }, task.id);
|
|
1705
|
+
}
|
|
1706
|
+
} catch (error) {
|
|
1707
|
+
this.publish("log", { level: "warn", msg: `Proje profili guncellenemedi: ${error.message}` }, task.id);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
// Dis bildirim icin tek cikis noktasi: server.js bu event'i dinleyip (varsa) webhook'a POST atar.
|
|
1712
|
+
// Motoru HTTP'den bagimsiz tutar; bildirim basarisiz olsa bile gorev akisini etkilemez.
|
|
1713
|
+
notifyOutcome(task, status, extra = {}) {
|
|
1714
|
+
try {
|
|
1715
|
+
this.emit("notify", {
|
|
1716
|
+
status,
|
|
1717
|
+
id: task.id,
|
|
1718
|
+
prompt: task.prompt,
|
|
1719
|
+
operator: task.operatorCli || null,
|
|
1720
|
+
dir: this._cwd || task.targetDir || null,
|
|
1721
|
+
at: new Date().toISOString(),
|
|
1722
|
+
...extra,
|
|
1723
|
+
});
|
|
1724
|
+
} catch {}
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
module.exports = new Engine();
|
|
1729
|
+
// Testlerin dis davranisla birlikte kritik saf kurallari da dogrudan dogrulayabilmesi icin.
|
|
1730
|
+
module.exports._internals = {
|
|
1731
|
+
normalizeAssignments, ensureBalancedRoleChain, extractVerdict, clipMiddle, snapshotDir, diffSnapshots,
|
|
1732
|
+
captureTextSnapshot, buildLineDiff, describeFileDiff, isSensitiveDiffPath, taskRequiresDelegation,
|
|
1733
|
+
parseJson, conversationalAnswer, classifyCliError, RECOVERABLE_CLI_ERRORS, QUARANTINE_CLI_ERRORS,
|
|
1734
|
+
normalizeCliOutput, addUsage, usageTotal, claudeSandboxSettings,
|
|
1735
|
+
};
|