@omerrgocmen/crewctl 1.3.1 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +193 -193
- package/orchestrator/README.md +473 -473
- package/orchestrator/config.default.json +96 -90
- 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/checkpoints.js +183 -183
- package/orchestrator/src/cli-registry.js +125 -3
- package/orchestrator/src/cli.js +140 -140
- package/orchestrator/src/doctor.js +64 -64
- package/orchestrator/src/engine.js +2112 -1914
- package/orchestrator/src/schedule.js +124 -124
- package/orchestrator/src/server.js +942 -817
- package/orchestrator/src/skill-registry.js +272 -272
- package/orchestrator/src/store.js +583 -478
- 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 +784 -622
- 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,622 +1,784 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="tr">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
-
<title>CrewCtl</title>
|
|
7
|
-
<link rel="icon" href="/favicon.ico" sizes="any">
|
|
8
|
-
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
|
9
|
-
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
|
10
|
-
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
|
11
|
-
<link rel="manifest" href="/site.webmanifest">
|
|
12
|
-
<style>
|
|
13
|
-
*{box-sizing:border-box}
|
|
14
|
-
:root{--bg:#090c12;--panel:#111826;--panel2:#182234;--line:#243149;--tx:#e9eef7;--dim:#8494ab;--acc:#5e8bff;--ok:#2fd08a;--warn:#eab24d;--err:#f2565f;--purple:#a98bff;
|
|
15
|
-
--headerbg:#0a0f1acc;--input:#0b111c;--term:#05080f;--termtx:#c7d3e6;--nodebg:#0b1220;--glow:#5e8bff26;--shadow:0 14px 44px #0009;--appbg:radial-gradient(1200px 560px at 12% -10%,#5e8bff16,transparent),radial-gradient(1000px 520px at 92% -8%,#a98bff10,transparent),var(--bg)}
|
|
16
|
-
[data-theme=light]{--bg:#edf1f8;--panel:#ffffff;--panel2:#f3f6fc;--line:#dce4ef;--tx:#131a27;--dim:#5a6779;--acc:#3a63f0;--ok:#0ca678;--warn:#a76a10;--err:#dc434e;--purple:#7350f0;
|
|
17
|
-
--headerbg:#ffffffe6;--input:#f4f7fd;--term:#0d1420;--termtx:#c7d3e6;--nodebg:#f7f9fd;--glow:#3a63f01c;--shadow:0 14px 40px #7a90b820;--appbg:radial-gradient(1200px 560px at 12% -10%,#3a63f012,transparent),radial-gradient(1000px 520px at 92% -8%,#7350f00e,transparent),var(--bg)}
|
|
18
|
-
[data-theme=light] .team-node.operator
|
|
19
|
-
[data-theme=light] .friendly-error{background:#fdecee;color:#8f1f28;border-color:#d6404b55}[data-theme=light] .friendly-error b{color:#c02e39}[data-theme=light] .friendly-error small{color:#9a5a60}
|
|
20
|
-
[data-theme=light] .friendly-error.success{background:#e9f8f0;color:#0d5c3c;border-color:#0f9d6355}[data-theme=light] .friendly-error.success b{color:var(--ok)}[data-theme=light] .friendly-error.success small{color:#4f7d68}
|
|
21
|
-
[data-theme=light] .fi-failure,[data-theme=light] .fi-blocked{background:#fdecef}
|
|
22
|
-
body{margin:0;background:var(--bg);color:var(--tx);font:14px/1.5 Inter,Segoe UI,system-ui,sans-serif;transition:background .3s,color .3s}
|
|
23
|
-
button,input,textarea,select{font:inherit}
|
|
24
|
-
button{cursor:pointer;color:var(--tx);background:var(--panel2);border:1px solid var(--line);border-radius:
|
|
25
|
-
button:hover{border-color:var(--acc)} button:active{transform:translateY(1px)} button.primary{background:linear-gradient(180deg,var(--acc)
|
|
26
|
-
button.primary:hover{box-shadow:0
|
|
27
|
-
button.danger{color:var(--err)} button.ok{color:var(--ok)} button.sm{padding:4px 8px;font-size:12px} button:disabled{opacity:.45;cursor:default}
|
|
28
|
-
header{min-height:58px;display:flex;align-items:center;gap:9px;padding:8px 18px;background:var(--headerbg);backdrop-filter:blur(8px);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:5}
|
|
29
|
-
h1{font-size:15px;margin:0}.brand{color:var(--acc)}.grow{flex:1}
|
|
30
|
-
.pill{border:1px solid var(--line);background:var(--panel2);border-radius:999px;padding:4px
|
|
31
|
-
.dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--dim);margin-right:5px}.dot.on{background:var(--ok)}
|
|
32
|
-
main{display:grid;grid-template-columns:minmax(
|
|
33
|
-
.stack{display:flex;flex-direction:column;gap:
|
|
34
|
-
.card{background:var(--panel);border:1px solid var(--line);border-radius:
|
|
35
|
-
.card h2{font-size:
|
|
36
|
-
.
|
|
37
|
-
|
|
38
|
-
input
|
|
39
|
-
textarea{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
.
|
|
44
|
-
.
|
|
45
|
-
|
|
46
|
-
.
|
|
47
|
-
.
|
|
48
|
-
.
|
|
49
|
-
.
|
|
50
|
-
.
|
|
51
|
-
.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
.
|
|
58
|
-
|
|
59
|
-
.
|
|
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
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
<div
|
|
323
|
-
<div class="
|
|
324
|
-
<div class="
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
<div class="
|
|
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
|
-
<div id="
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
</div></div>
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
function
|
|
460
|
-
function
|
|
461
|
-
|
|
462
|
-
function
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
const
|
|
479
|
-
const
|
|
480
|
-
codex
|
|
481
|
-
claude
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
function
|
|
501
|
-
function
|
|
502
|
-
function
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
function
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
function
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
function
|
|
525
|
-
function
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
function
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
function
|
|
534
|
-
function
|
|
535
|
-
function
|
|
536
|
-
function
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
function
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
function
|
|
551
|
-
function
|
|
552
|
-
function
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if(
|
|
561
|
-
function
|
|
562
|
-
function
|
|
563
|
-
async function
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
function
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
function
|
|
583
|
-
function
|
|
584
|
-
function
|
|
585
|
-
function
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
function
|
|
599
|
-
const
|
|
600
|
-
function
|
|
601
|
-
function
|
|
602
|
-
function
|
|
603
|
-
function
|
|
604
|
-
function
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
function
|
|
610
|
-
function
|
|
611
|
-
function
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
function
|
|
617
|
-
function
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="tr">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>CrewCtl</title>
|
|
7
|
+
<link rel="icon" href="/favicon.ico" sizes="any">
|
|
8
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
|
9
|
+
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
|
10
|
+
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
|
11
|
+
<link rel="manifest" href="/site.webmanifest">
|
|
12
|
+
<style>
|
|
13
|
+
*{box-sizing:border-box}
|
|
14
|
+
:root{--bg:#090c12;--panel:#111826;--panel2:#182234;--line:#243149;--tx:#e9eef7;--dim:#8494ab;--acc:#5e8bff;--ok:#2fd08a;--warn:#eab24d;--err:#f2565f;--purple:#a98bff;
|
|
15
|
+
--headerbg:#0a0f1acc;--input:#0b111c;--term:#05080f;--termtx:#c7d3e6;--nodebg:#0b1220;--glow:#5e8bff26;--shadow:0 14px 44px #0009;--appbg:radial-gradient(1200px 560px at 12% -10%,#5e8bff16,transparent),radial-gradient(1000px 520px at 92% -8%,#a98bff10,transparent),var(--bg)}
|
|
16
|
+
[data-theme=light]{--bg:#edf1f8;--panel:#ffffff;--panel2:#f3f6fc;--line:#dce4ef;--tx:#131a27;--dim:#5a6779;--acc:#3a63f0;--ok:#0ca678;--warn:#a76a10;--err:#dc434e;--purple:#7350f0;
|
|
17
|
+
--headerbg:#ffffffe6;--input:#f4f7fd;--term:#0d1420;--termtx:#c7d3e6;--nodebg:#f7f9fd;--glow:#3a63f01c;--shadow:0 14px 40px #7a90b820;--appbg:radial-gradient(1200px 560px at 12% -10%,#3a63f012,transparent),radial-gradient(1000px 520px at 92% -8%,#7350f00e,transparent),var(--bg)}
|
|
18
|
+
[data-theme=light] .team-node.operator{border-color:color-mix(in srgb,var(--nacc) 40%,var(--line));background:linear-gradient(150deg,color-mix(in srgb,var(--nacc) 10%,var(--panel2)),var(--nodebg) 60%)}
|
|
19
|
+
[data-theme=light] .friendly-error{background:#fdecee;color:#8f1f28;border-color:#d6404b55}[data-theme=light] .friendly-error b{color:#c02e39}[data-theme=light] .friendly-error small{color:#9a5a60}
|
|
20
|
+
[data-theme=light] .friendly-error.success{background:#e9f8f0;color:#0d5c3c;border-color:#0f9d6355}[data-theme=light] .friendly-error.success b{color:var(--ok)}[data-theme=light] .friendly-error.success small{color:#4f7d68}
|
|
21
|
+
[data-theme=light] .fi-failure,[data-theme=light] .fi-blocked{background:#fdecef}
|
|
22
|
+
body{margin:0;background:var(--bg);color:var(--tx);font:14px/1.5 Inter,Segoe UI,system-ui,sans-serif;transition:background .3s,color .3s}
|
|
23
|
+
button,input,textarea,select{font:inherit}
|
|
24
|
+
button{cursor:pointer;color:var(--tx);background:var(--panel2);border:1px solid color-mix(in srgb,var(--line) 78%,transparent);border-radius:10px;padding:8px 14px;font-weight:500;transition:border-color .16s,background .16s,transform .1s,box-shadow .16s,color .16s}
|
|
25
|
+
button:hover{border-color:color-mix(in srgb,var(--acc) 55%,var(--line));background:color-mix(in srgb,var(--acc) 8%,var(--panel2))} button:active{transform:translateY(1px)} button.primary{background:linear-gradient(180deg,color-mix(in srgb,#fff 14%,var(--acc)),var(--acc));border-color:transparent;color:#071324;font-weight:700;box-shadow:0 2px 8px -2px var(--glow),0 6px 20px -10px var(--glow)}
|
|
26
|
+
button.primary:hover{background:linear-gradient(180deg,color-mix(in srgb,#fff 22%,var(--acc)),var(--acc));box-shadow:0 4px 14px -2px var(--glow),0 10px 26px -8px var(--glow);transform:translateY(-1px)}
|
|
27
|
+
button.danger{color:var(--err)} button.ok{color:var(--ok)} button.sm{padding:4px 8px;font-size:12px} button:disabled{opacity:.45;cursor:default}
|
|
28
|
+
header{min-height:58px;display:flex;align-items:center;gap:9px;padding:8px 18px;background:var(--headerbg);backdrop-filter:blur(8px);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:5}
|
|
29
|
+
h1{font-size:15px;margin:0}.brand{color:var(--acc)}.grow{flex:1}
|
|
30
|
+
.pill{border:1px solid color-mix(in srgb,var(--line) 70%,transparent);background:color-mix(in srgb,var(--panel2) 70%,transparent);border-radius:999px;padding:4px 10px;color:var(--dim);font-size:12px}
|
|
31
|
+
.dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--dim);margin-right:5px}.dot.on{background:var(--ok)}
|
|
32
|
+
main{display:grid;grid-template-columns:minmax(320px,384px) minmax(460px,1fr);gap:20px;padding:24px;align-items:start;max-width:1680px;margin:0 auto}
|
|
33
|
+
.stack{display:flex;flex-direction:column;gap:16px}
|
|
34
|
+
.card{background:var(--panel);border:1px solid color-mix(in srgb,var(--line) 60%,transparent);border-radius:18px;padding:20px;box-shadow:0 1px 2px #0000001a,0 18px 40px -24px #00000070;transition:border-color .18s,box-shadow .18s}
|
|
35
|
+
.card h2{font-size:14.5px;letter-spacing:-.012em;color:var(--tx);margin:0 0 14px;font-weight:650;display:flex;align-items:center;gap:8px}
|
|
36
|
+
.card h2::before{content:'';width:3px;height:15px;border-radius:3px;background:linear-gradient(var(--acc),var(--purple));flex:none}
|
|
37
|
+
.row{display:flex;align-items:center;gap:7px}.muted{font-size:12px;color:var(--dim)}
|
|
38
|
+
input,textarea,select{width:100%;color:var(--tx);background:var(--input);border:1px solid color-mix(in srgb,var(--line) 85%,transparent);border-radius:10px;padding:9px 11px;transition:border-color .16s,box-shadow .16s,background .16s}
|
|
39
|
+
input:hover,textarea:hover,select:hover{border-color:color-mix(in srgb,var(--acc) 40%,var(--line))}
|
|
40
|
+
input:focus,textarea:focus,select:focus{outline:none;border-color:var(--acc);box-shadow:0 0 0 3px var(--glow)}
|
|
41
|
+
textarea{resize:vertical;font:12.5px/1.5 ui-monospace,Consolas,monospace}
|
|
42
|
+
label{display:block;color:var(--dim);font-size:11.5px;font-weight:550;letter-spacing:0;margin:12px 0 5px}
|
|
43
|
+
.hint{font-size:11px;color:var(--dim);background:var(--input);border:1px solid var(--line);border-radius:8px;padding:8px 9px;margin:8px 0}
|
|
44
|
+
.task{position:relative;border:1px solid color-mix(in srgb,var(--line) 70%,transparent);background:var(--panel2);border-radius:13px;padding:13px 14px 13px 17px;margin:9px 0;transition:border-color .16s,box-shadow .16s}
|
|
45
|
+
.task::before{content:'';position:absolute;left:0;top:12px;bottom:12px;width:3px;border-radius:0 3px 3px 0;background:var(--dim);opacity:.55}
|
|
46
|
+
.task--pending::before{background:var(--acc);opacity:.9}
|
|
47
|
+
.task--approval::before{background:var(--warn);opacity:.9}
|
|
48
|
+
.task--done::before{background:var(--ok);opacity:.85}
|
|
49
|
+
.task--failed::before{background:var(--err);opacity:.9}
|
|
50
|
+
.task:hover{border-color:color-mix(in srgb,var(--acc) 34%,var(--line));box-shadow:0 12px 30px -20px #000000b0}
|
|
51
|
+
.task .id{font:10px ui-monospace;color:var(--dim)}.task .prompt{margin:6px 0;line-height:1.5;display:-webkit-box;-webkit-line-clamp:4;line-clamp:4;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}
|
|
52
|
+
/* Uzun promptta kart cok uzamasin: metin 4 satira kirpilir, tiklayinca acilir. Bekleyen liste de
|
|
53
|
+
asiri buyumesin diye ust sinir + kendi kaydirmasi. */
|
|
54
|
+
.task .prompt.expanded{-webkit-line-clamp:unset;line-clamp:unset}
|
|
55
|
+
.task .prompt{cursor:pointer}
|
|
56
|
+
#pending{max-height:min(52vh,560px);overflow:auto;padding-right:2px}
|
|
57
|
+
.badge{font-size:10px;border:1px solid color-mix(in srgb,var(--line) 60%,transparent);background:color-mix(in srgb,var(--panel2) 60%,transparent);border-radius:999px;padding:2px 8px;color:var(--dim);font-weight:500}
|
|
58
|
+
details summary{cursor:pointer;color:var(--dim);font-size:12px} pre{white-space:pre-wrap;word-break:break-word}
|
|
59
|
+
.command-head{display:flex;align-items:center;gap:8px;margin-bottom:10px}.command-head h2{margin:0}
|
|
60
|
+
.empty{color:var(--dim);font-size:12px;padding:18px;text-align:center;border:1px dashed color-mix(in srgb,var(--line) 75%,transparent);border-radius:12px;background:color-mix(in srgb,var(--panel2) 35%,transparent)}
|
|
61
|
+
.header-state{display:flex;align-items:center;gap:7px}.header-tools{display:flex;align-items:center;gap:7px}.header-tools>button{white-space:nowrap}
|
|
62
|
+
.section-title{display:flex;align-items:center;gap:8px;margin-bottom:10px}.section-title h2{margin:0}.section-title .badge{margin-left:auto}
|
|
63
|
+
.composer-sub{font-size:12.5px;color:var(--dim);margin:-2px 0 14px;line-height:1.5}.composer textarea{min-height:134px;font-family:inherit;font-size:14px;line-height:1.55;padding:12px 13px}
|
|
64
|
+
.project-bar{display:flex;align-items:center;gap:9px;margin-bottom:12px}
|
|
65
|
+
.project-bar select{flex:1;min-width:0}
|
|
66
|
+
.pb-ico{flex:none;display:flex;color:var(--acc)}.pb-ico svg{width:17px;height:17px}
|
|
67
|
+
.iconbtn{flex:none;display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;padding:0;color:var(--dim)}
|
|
68
|
+
.iconbtn:hover{color:var(--tx)}.iconbtn svg{width:17px;height:17px}
|
|
69
|
+
.badge--ic{display:inline-flex;align-items:center;gap:4px}
|
|
70
|
+
.badge svg{width:11px;height:11px}
|
|
71
|
+
.qs-ico{flex:none;display:flex;color:var(--acc)}.qs-ico svg{width:14px;height:14px}
|
|
72
|
+
.mention-item .mi-ico{flex:none;display:flex;color:var(--dim)}.mention-item .mi-ico svg{width:16px;height:16px}
|
|
73
|
+
.project-path{font:11px/1.4 ui-monospace,monospace;color:var(--dim);margin:-4px 0 10px;display:flex;align-items:center;gap:6px}
|
|
74
|
+
.project-path span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
75
|
+
.project-path svg{width:12px;height:12px;flex:none;opacity:.7}
|
|
76
|
+
.queue-scope{display:flex;align-items:center;gap:8px;padding:8px 11px;margin-bottom:10px;border:1px solid color-mix(in srgb,var(--acc) 28%,var(--line));border-radius:10px;background:color-mix(in srgb,var(--acc) 9%,var(--panel2));font-size:12px}
|
|
77
|
+
.queue-scope .qs-name{font-weight:600;display:inline-flex;align-items:center;gap:5px;min-width:0}
|
|
78
|
+
.queue-scope .qs-path{color:var(--dim);font:11px/1 ui-monospace,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}
|
|
79
|
+
.queue-scope .qs-clear{flex:none;border:none;background:none;color:var(--acc);cursor:pointer;font-size:12px;padding:0}
|
|
80
|
+
.queue-scope .qs-clear:hover{text-decoration:underline}
|
|
81
|
+
.proj-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid color-mix(in srgb,var(--line) 72%,transparent);border-radius:11px;background:var(--input);margin-bottom:8px;transition:border-color .16s,background .16s}
|
|
82
|
+
.proj-item:hover{border-color:color-mix(in srgb,var(--acc) 38%,var(--line));background:color-mix(in srgb,var(--acc) 6%,var(--input))}
|
|
83
|
+
.proj-item .pi-main{min-width:0;flex:1}
|
|
84
|
+
.proj-item .pi-name{font-weight:600;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
85
|
+
.proj-item .pi-path{font:11px/1.4 ui-monospace,monospace;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
86
|
+
.mention-wrap{position:relative}
|
|
87
|
+
.mention-box{position:absolute;left:0;right:0;top:100%;z-index:35;max-height:260px;overflow:auto;margin-top:5px;background:var(--panel);border:1px solid var(--line);border-radius:10px;box-shadow:var(--shadow);padding:4px}
|
|
88
|
+
.mention-item{display:flex;align-items:center;gap:9px;padding:7px 9px;border-radius:7px;cursor:pointer;font-size:13px}
|
|
89
|
+
.mention-item.active,.mention-item:hover{background:color-mix(in srgb,var(--acc) 12%,var(--panel2))}
|
|
90
|
+
.mention-item .folder-icon{flex:none}
|
|
91
|
+
.mention-item .mi-name{font-weight:500;flex:none}
|
|
92
|
+
.mention-item .mi-rel{margin-left:auto;color:var(--dim);font:11px/1 ui-monospace,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60%}
|
|
93
|
+
.mention-empty{padding:9px;color:var(--dim);font-size:12px}
|
|
94
|
+
.attach-bar{margin-top:8px;display:flex;flex-direction:column;gap:7px}
|
|
95
|
+
.attach-actions{display:flex;gap:7px;flex-wrap:wrap}
|
|
96
|
+
.attach-btn{align-self:flex-start;display:inline-flex;align-items:center;gap:6px;font-size:12px;padding:5px 11px;border:1px dashed var(--line);background:transparent;color:var(--dim);border-radius:8px;cursor:pointer}
|
|
97
|
+
.attach-btn:hover{color:var(--tx);border-color:var(--acc)}
|
|
98
|
+
.attach-btn svg{width:14px;height:14px}
|
|
99
|
+
.attach-chips{display:flex;flex-wrap:wrap;gap:6px}
|
|
100
|
+
.attach-chip{display:inline-flex;align-items:center;gap:7px;max-width:230px;padding:4px 6px 4px 9px;background:var(--panel2);border:1px solid var(--line);border-radius:8px;font-size:12px}
|
|
101
|
+
.attach-chip .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
102
|
+
.attach-chip .ext{font:10px/1 ui-monospace,monospace;color:var(--dim);text-transform:uppercase;flex:none}
|
|
103
|
+
.attach-chip .sz{color:var(--dim);flex:none}
|
|
104
|
+
.attach-chip .rm{cursor:pointer;color:var(--dim);border:none;background:none;font-size:15px;line-height:1;padding:0 2px;flex:none}
|
|
105
|
+
.attach-chip .rm:hover{color:var(--err)}
|
|
106
|
+
.advanced{margin-top:8px;border:1px solid var(--line);border-radius:9px;background:var(--input)}.advanced>summary{padding:8px 10px;list-style:none;display:flex;align-items:center;gap:7px}.advanced>summary::-webkit-details-marker{display:none}.advanced>summary:before{content:'›';font-size:17px;line-height:1;transition:transform .15s}.advanced[open]>summary:before{transform:rotate(90deg)}.advanced-body{padding:0 10px 10px;border-top:1px solid var(--line)}
|
|
107
|
+
.queue-group+.queue-group{border-top:1px solid var(--line);margin-top:11px;padding-top:10px}.queue-label{display:flex;align-items:center;gap:7px;color:var(--dim);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px}.queue-label .badge{margin-left:auto}
|
|
108
|
+
.queue-archive{border-top:1px solid var(--line);margin-top:11px;padding-top:9px}.queue-archive>summary{list-style:none;display:flex;align-items:center;gap:8px}.queue-archive>summary::-webkit-details-marker{display:none}.queue-archive>summary:before{content:'›';font-size:16px}.queue-archive[open]>summary:before{transform:rotate(90deg)}.queue-archive-body{padding-top:8px}
|
|
109
|
+
.live-details>summary{list-style:none;display:flex;align-items:center;gap:8px;font-weight:700}.live-details>summary::-webkit-details-marker{display:none}.live-details>summary:before{content:'›';font-size:18px;color:var(--dim)}.live-details[open]>summary:before{transform:rotate(90deg)}.live-details .live-body{padding-top:12px}
|
|
110
|
+
/* Ekip haritasi — operasyon dugumleri */
|
|
111
|
+
.mission-top{display:flex;align-items:center;gap:10px}
|
|
112
|
+
.mission-title{font-weight:750;font-size:15px;letter-spacing:-.15px}.mission-sub{color:var(--dim);font-size:11px;margin-top:1px}
|
|
113
|
+
/* Butunlesik takim akisi paneli — bolum basliklari */
|
|
114
|
+
.flow-seg{margin-top:16px}
|
|
115
|
+
.flow-seg-head{display:flex;align-items:center;gap:10px;margin-bottom:10px}
|
|
116
|
+
.flow-seg-head .lbl{font-size:9.5px;font-weight:800;letter-spacing:1.3px;text-transform:uppercase;color:var(--dim)}
|
|
117
|
+
.flow-seg-head .ln{flex:1;height:1px;background:linear-gradient(90deg,color-mix(in srgb,var(--line) 85%,transparent),transparent)}
|
|
118
|
+
.flow-seg-head .hint{font-size:10.5px;color:var(--dim)}
|
|
119
|
+
/* Ekip seridi — operator + uzman dugumleri yatay boru hatti cipleri */
|
|
120
|
+
.team-map{display:flex;align-items:center;gap:0;overflow-x:auto;padding:18px 10px 26px;scrollbar-width:thin}
|
|
121
|
+
.team-node{--nacc:var(--acc);position:relative;flex:none;display:flex;align-items:center;gap:13px;padding:12px 20px 12px 12px;border:1px solid color-mix(in srgb,var(--nacc) 26%,var(--line));border-radius:16px;background:linear-gradient(150deg,color-mix(in srgb,var(--nacc) 10%,var(--nodebg)),var(--nodebg) 62%);box-shadow:0 10px 26px #0004;color:var(--tx);transition:border-color .25s,box-shadow .25s,transform .2s}
|
|
122
|
+
.node-orb{position:relative;isolation:isolate;flex:none;width:54px;height:54px;display:grid;place-items:center;border:1px solid transparent;border-radius:50%;background:radial-gradient(circle at 36% 28%,color-mix(in srgb,var(--nacc) 22%,var(--nodebg)),var(--nodebg) 60%) padding-box,conic-gradient(from 210deg,var(--nacc),#38577d 40%,var(--line) 66%,var(--nacc)) border-box;box-shadow:inset 0 -7px 16px #0003,0 7px 16px #0004}
|
|
123
|
+
.node-orb:before{content:'';position:absolute;inset:-5px;border:1px dashed color-mix(in srgb,var(--nacc) 36%,transparent);border-radius:50%;opacity:0}
|
|
124
|
+
.team-node.working .node-orb:before{opacity:1;animation:nodeOrbit 9s linear infinite}
|
|
125
|
+
.node-glyph{position:relative;z-index:1;font:850 15px/1 ui-monospace,Consolas,monospace;letter-spacing:.5px;color:var(--nacc);text-shadow:0 0 11px color-mix(in srgb,var(--nacc) 60%,transparent)}
|
|
126
|
+
.node-status{position:absolute;z-index:2;right:-1px;bottom:1px;width:14px;height:14px;border:3px solid var(--nodebg);border-radius:50%;background:var(--dim)}
|
|
127
|
+
.node-info{min-width:0;text-align:left}
|
|
128
|
+
.team-node .nrole{font-size:8.5px;letter-spacing:1px;text-transform:uppercase;color:var(--dim)}
|
|
129
|
+
.team-node .nname{max-width:170px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:750;font-size:14.5px;margin-top:2px}.team-node .nstate{font-size:11.5px;color:var(--dim);margin-top:2px}
|
|
130
|
+
.team-node.operator .node-orb{background:radial-gradient(circle at 36% 28%,color-mix(in srgb,var(--nacc) 20%,var(--nodebg)),var(--nodebg) 60%) padding-box,conic-gradient(from 210deg,var(--nacc),#64449a 44%,var(--line) 68%,var(--nacc)) border-box}.team-node.operator .node-glyph{color:var(--nacc)}
|
|
131
|
+
.team-node.working{transform:translateY(-2px);border-color:color-mix(in srgb,var(--ok) 46%,var(--line));box-shadow:0 12px 30px color-mix(in srgb,var(--ok) 18%,transparent)}.team-node.working .nstate{color:var(--ok)}.team-node.working .node-status{background:var(--ok);box-shadow:0 0 9px var(--ok)}
|
|
132
|
+
.team-node.done{border-color:color-mix(in srgb,var(--acc) 38%,var(--line))}.team-node.done .node-status{background:var(--acc)}
|
|
133
|
+
.team-node.failed{border-color:color-mix(in srgb,var(--err) 48%,var(--line))}.team-node.failed .node-status{background:var(--err);box-shadow:0 0 8px var(--err)}.team-node.failed .node-orb{filter:saturate(.7)}
|
|
134
|
+
.team-link{position:relative;z-index:2;flex:none;align-self:center;height:2px;width:66px;margin:0 15px 0 -1px;background:repeating-linear-gradient(90deg,color-mix(in srgb,var(--acc) 46%,var(--line)) 0 5px,transparent 5px 12px);background-size:24px 2px;animation:teamRail 1.1s linear infinite;filter:drop-shadow(0 0 3px color-mix(in srgb,var(--acc) 20%,transparent))}.team-link:before{content:'';position:absolute;left:0;top:50%;width:6px;height:6px;border-radius:50%;background:var(--acc);box-shadow:0 0 8px var(--acc);transform:translate(-50%,-50%);opacity:0}.team-link:after{content:'';position:absolute;right:-1px;top:50%;width:6px;height:6px;border-top:2px solid var(--acc);border-right:2px solid var(--acc);transform:translateY(-50%) rotate(45deg);filter:drop-shadow(0 0 3px var(--acc))}.team-link.active{background:linear-gradient(90deg,transparent,var(--acc),transparent);box-shadow:0 0 10px color-mix(in srgb,var(--acc) 60%,transparent)}.team-link.active:before{opacity:1;animation:teamPulse 1.15s ease-in-out infinite}.team-link.active:after{border-color:var(--ok);filter:drop-shadow(0 0 4px var(--ok))}
|
|
135
|
+
@keyframes nodeOrbit{to{transform:rotate(360deg)}}@keyframes teamRail{to{background-position:24px 0}}@keyframes teamPulse{from{left:0}to{left:100%}}
|
|
136
|
+
/* Agent calisma panelleri */
|
|
137
|
+
.agent-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(330px,1fr));gap:14px}
|
|
138
|
+
.agent{--agent-accent:var(--acc);position:relative;min-width:0;border:1px solid color-mix(in srgb,var(--agent-accent) 25%,var(--line));background:linear-gradient(145deg,color-mix(in srgb,var(--agent-accent) 5%,var(--nodebg)),var(--nodebg) 48%);border-radius:15px;overflow:hidden;box-shadow:0 16px 38px #0004;transition:border-color .3s,box-shadow .3s,transform .25s}
|
|
139
|
+
.agent:before{content:'';position:absolute;z-index:2;inset:0 0 auto;height:2px;background:linear-gradient(90deg,transparent,var(--agent-accent),transparent);opacity:.65}.agent:after{content:'';position:absolute;top:-70px;right:-55px;width:150px;height:150px;border-radius:50%;background:var(--agent-accent);filter:blur(68px);opacity:.07;pointer-events:none}
|
|
140
|
+
.agent.operatorcall{--agent-accent:var(--purple)}.agent.active{--agent-accent:var(--ok);transform:translateY(-2px);box-shadow:0 18px 42px #0005,0 0 0 1px color-mix(in srgb,var(--ok) 22%,transparent)}.agent.operatorcall.active{--agent-accent:var(--purple)}.agent.error{--agent-accent:var(--err);box-shadow:0 16px 38px #0005,0 0 0 1px color-mix(in srgb,var(--err) 24%,transparent)}
|
|
141
|
+
.agent-head{position:relative;z-index:1;display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:11px;padding:13px 14px;background:linear-gradient(180deg,color-mix(in srgb,var(--panel2) 72%,transparent),transparent);min-height:68px}
|
|
142
|
+
.agent-avatar{width:40px;height:40px;display:grid;place-items:center;border:1px solid color-mix(in srgb,var(--agent-accent) 45%,var(--line));border-radius:12px;background:linear-gradient(145deg,color-mix(in srgb,var(--agent-accent) 20%,var(--panel2)),var(--input));color:var(--agent-accent);font:800 11px/1 ui-monospace,Consolas,monospace;letter-spacing:.7px;box-shadow:inset 0 1px #ffffff0c,0 7px 16px #0003}
|
|
143
|
+
.agent-identity{min-width:0}.agent-kicker{display:flex;align-items:center;gap:6px;min-width:0}.agent-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:750;font-size:13px;letter-spacing:-.1px;margin-top:4px}.agent-role{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dim);font-size:9px;letter-spacing:.5px;text-transform:uppercase}
|
|
144
|
+
.agent-step{font:800 8px/1 ui-monospace,Consolas,monospace;letter-spacing:.7px;color:var(--agent-accent);border:1px solid color-mix(in srgb,var(--agent-accent) 35%,var(--line));background:color-mix(in srgb,var(--agent-accent) 8%,transparent);border-radius:5px;padding:3px 5px}
|
|
145
|
+
.agent-state{display:flex;align-items:center;gap:6px;max-width:118px;color:var(--dim);font-size:10px;border:1px solid var(--line);border-radius:999px;padding:5px 8px;background:color-mix(in srgb,var(--input) 80%,transparent);white-space:nowrap}.agent-state-text{overflow:hidden;text-overflow:ellipsis}.state-pulse{flex:none;width:6px;height:6px;border-radius:50%;background:var(--dim)}.agent.active .state-pulse{background:var(--ok);box-shadow:0 0 9px var(--ok);animation:agentPulse 1.35s ease-in-out infinite}.agent.error .state-pulse{background:var(--err);box-shadow:0 0 8px var(--err)}
|
|
146
|
+
.agent-progress{height:2px;background:var(--line);overflow:hidden}.agent-progress span{display:block;width:0;height:100%;background:var(--agent-accent)}.agent.active .agent-progress span{width:42%;animation:agentScan 1.8s ease-in-out infinite}.agent.error .agent-progress span,.agent:not(.active) .agent-progress span{width:100%;animation:none}
|
|
147
|
+
.terminal-wrap{border-top:1px solid var(--line)}.terminal-wrap summary{padding:9px 13px;background:color-mix(in srgb,var(--input) 88%,transparent);font-size:10px;letter-spacing:.4px;text-transform:uppercase}.terminal-wrap summary::marker{color:var(--agent-accent)}
|
|
148
|
+
.terminal{height:180px;overflow:auto;margin:0;padding:12px 14px;background:linear-gradient(180deg,#080c12,var(--term));color:var(--termtx);font:11.5px/1.5 ui-monospace,Consolas,monospace;border-top:1px solid #ffffff08;white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere}
|
|
149
|
+
@keyframes agentPulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.72)}}
|
|
150
|
+
@keyframes agentScan{0%{transform:translateX(-120%)}55%,100%{transform:translateX(260%)}}
|
|
151
|
+
@media(prefers-reduced-motion:reduce){.agent,.team-node{transition:none}.agent.active .state-pulse,.agent.active .agent-progress span,.node-orb:before,.team-link,.team-link.active:before{animation:none}}
|
|
152
|
+
.stderr{color:#ff9aa2}
|
|
153
|
+
/* Kutuya basılan metin agent çıktısıdır: boşluksuz uzun parçalar (dosya yolu, URL, hash)
|
|
154
|
+
içerebilir. overflow-wrap olmadan bunlar kutunun kenarını taşıyordu. */
|
|
155
|
+
.friendly-error{margin:9px;border:1px solid #ff6b7560;border-radius:9px;padding:10px;background:#2a141b;color:#ffd8db;min-width:0;max-width:100%;overflow-wrap:anywhere;word-break:break-word;white-space:pre-wrap;max-height:260px;overflow-y:auto}
|
|
156
|
+
.friendly-error b{display:block;color:#ff8f97;margin-bottom:3px;overflow-wrap:anywhere}.friendly-error small{color:#d5aeb2}
|
|
157
|
+
.friendly-error.success{border-color:#42d39255;background:#10261e;color:#d7ffec}.friendly-error.success b{color:var(--ok)}.friendly-error.success small{color:#a7cfbd}
|
|
158
|
+
.plan-pills{display:flex;gap:5px;flex-wrap:wrap;margin-top:7px}.plan-pills span{font-size:10px;border:1px solid #42d39255;border-radius:999px;padding:2px 7px}
|
|
159
|
+
/* Birlesik akis — sistem satirlari ince tek satir, mesaj/teslimat renk-kodlu belirgin kart */
|
|
160
|
+
.feed{max-height:460px;overflow:auto;display:flex;flex-direction:column;gap:6px;padding-right:2px}
|
|
161
|
+
.feed-item{position:relative;display:flex;gap:11px;padding:10px 13px;border-radius:12px;transition:background .14s,border-color .18s}
|
|
162
|
+
.fi-log{gap:9px;padding:5px 9px;border-radius:8px}
|
|
163
|
+
.fi-log:hover{background:color-mix(in srgb,var(--panel2) 50%,transparent)}
|
|
164
|
+
.fi-delegation,.fi-result,.fi-failure,.fi-blocked{border:1px solid color-mix(in srgb,var(--evc) 24%,var(--line));border-left:3px solid var(--evc);background:color-mix(in srgb,var(--evc) 6%,var(--panel2));box-shadow:0 6px 18px #00000026}
|
|
165
|
+
.fi-delegation:hover,.fi-result:hover,.fi-failure:hover,.fi-blocked:hover{border-color:color-mix(in srgb,var(--evc) 42%,var(--line));border-left-color:var(--evc)}
|
|
166
|
+
.fi-dot{position:relative;z-index:1;flex:none;margin-top:1px;width:23px;height:23px;border-radius:50%;display:grid;place-items:center;font:800 11px/1 ui-monospace,Consolas,monospace;color:var(--evc,var(--dim));border:1px solid color-mix(in srgb,var(--evc,var(--line)) 45%,var(--line));background:color-mix(in srgb,var(--evc,var(--dim)) 15%,var(--panel))}
|
|
167
|
+
.fi-log .fi-dot{width:19px;height:19px;font-size:10px}
|
|
168
|
+
.fi-main{min-width:0;flex:1}
|
|
169
|
+
.fi-head{display:flex;align-items:center;gap:7px;flex-wrap:wrap;font-size:12px;line-height:1.5}
|
|
170
|
+
.fi-from{font-weight:750}.fi-arrow{color:var(--dim);opacity:.55}.fi-to{font-weight:700;color:var(--tx)}
|
|
171
|
+
.fi-tag{font:10px ui-monospace,Consolas,monospace;color:var(--dim);border:1px solid var(--line);border-radius:6px;padding:1px 6px;background:color-mix(in srgb,var(--panel) 55%,transparent)}
|
|
172
|
+
.fi-head time{margin-left:auto;color:var(--dim);font:10px ui-monospace,Consolas,monospace;flex:none}
|
|
173
|
+
.fi-msg{color:var(--tx);font-size:12px}
|
|
174
|
+
.fi-body{margin-top:6px;font-size:11.5px;line-height:1.6;color:color-mix(in srgb,var(--tx) 78%,var(--dim));white-space:pre-wrap;word-break:break-word}
|
|
175
|
+
.fi-body.clamp{max-height:106px;overflow:hidden;-webkit-mask-image:linear-gradient(180deg,#000 66%,transparent);mask-image:linear-gradient(180deg,#000 66%,transparent)}
|
|
176
|
+
.fi-more{margin-top:6px;font-size:11px;font-weight:600;color:var(--acc);background:color-mix(in srgb,var(--acc) 10%,transparent);border:1px solid color-mix(in srgb,var(--acc) 28%,var(--line));border-radius:7px;padding:3px 10px}.fi-more:hover{background:color-mix(in srgb,var(--acc) 17%,transparent)}
|
|
177
|
+
.fi-delegation{--evc:var(--purple)}.fi-result{--evc:var(--ok)}.fi-failure,.fi-blocked{--evc:var(--err)}
|
|
178
|
+
.fi-log{--evc:var(--dim)}.fi-log.task{--evc:var(--acc)}.fi-log.ok{--evc:var(--ok)}.fi-log.error{--evc:var(--err)}.fi-log.warn{--evc:var(--warn)}.fi-log.stage{--evc:var(--purple)}.fi-log.info{--evc:var(--acc)}
|
|
179
|
+
.fi-log.error .fi-msg,.fi-log.warn .fi-msg{color:var(--evc)}.fi-log.ok .fi-msg,.fi-log.task .fi-msg{font-weight:600}
|
|
180
|
+
/* Teslimat */
|
|
181
|
+
.delivery{margin-top:8px;border-top:1px solid var(--line);padding-top:7px}
|
|
182
|
+
.usage-box{margin-top:8px;border:1px solid var(--line);border-radius:9px;background:color-mix(in srgb,var(--acc) 5%,transparent);padding:8px 10px}
|
|
183
|
+
.usage-head{display:flex;align-items:baseline;gap:8px;font-weight:700;font-size:12.5px}.usage-head .usage-cost{margin-left:auto;font-family:ui-monospace,monospace;color:var(--acc)}
|
|
184
|
+
.usage-detail{margin-top:3px;font-size:11px;color:var(--dim);overflow-wrap:anywhere}
|
|
185
|
+
.usage-agents{margin-top:6px;border-top:1px solid var(--line);padding-top:5px;display:grid;gap:2px}
|
|
186
|
+
.usage-agents>div{display:flex;gap:8px;font-size:11px}.usage-agents>div>span:first-child{color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.usage-agents>div>span:last-child{margin-left:auto;font-family:ui-monospace,monospace;white-space:nowrap}
|
|
187
|
+
.usage-note{margin-top:5px;font-size:10.5px;color:var(--warn)}
|
|
188
|
+
.failure-box{margin-top:8px;border:1px solid color-mix(in srgb,var(--err) 45%,var(--line));border-radius:9px;background:color-mix(in srgb,var(--err) 8%,transparent);padding:9px 11px}
|
|
189
|
+
.failure-box .fb-title{font-weight:700;font-size:12.5px;display:flex;gap:7px;align-items:baseline;flex-wrap:wrap}
|
|
190
|
+
.failure-box .fb-code{font-family:ui-monospace,monospace;font-size:10px;letter-spacing:.4px;color:var(--err);border:1px solid color-mix(in srgb,var(--err) 40%,transparent);border-radius:5px;padding:1px 5px}
|
|
191
|
+
.failure-box .fb-action{margin-top:5px;font-size:12px;color:var(--dim);line-height:1.5}
|
|
192
|
+
.failure-box details{margin-top:6px}.failure-box pre{max-height:220px;overflow:auto;font-size:11px;margin-top:5px}
|
|
193
|
+
.file-change{display:flex;gap:6px;align-items:center;font:11px ui-monospace;margin:3px 0}
|
|
194
|
+
.file-change .created{color:var(--ok)}.file-change .modified{color:var(--warn)}.file-change .deleted{color:var(--err)}
|
|
195
|
+
.file-change span:first-child{flex:none;min-width:52px;font-weight:700}.file-change code{color:var(--tx);word-break:break-all}
|
|
196
|
+
/* Modal */
|
|
197
|
+
.modal{display:none;position:fixed;inset:0;background:#000b;z-index:20;align-items:center;justify-content:center}.modal.open{display:flex}
|
|
198
|
+
.dialog{width:min(940px,95vw);height:min(760px,92vh);display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);border-radius:14px}
|
|
199
|
+
.dialog-head,.dialog-foot{padding:13px 16px;display:flex;align-items:center;gap:8px}
|
|
200
|
+
.dialog-head{border-bottom:1px solid var(--line)}.dialog-foot{border-top:1px solid var(--line)}
|
|
201
|
+
.dialog-body{padding:12px 16px;overflow:auto}
|
|
202
|
+
.tabs{display:flex;gap:6px;margin-bottom:12px}.tabs button.active{background:var(--acc);color:#071324}
|
|
203
|
+
.item{border:1px solid var(--line);background:var(--panel2);border-radius:10px;padding:11px;margin:9px 0}
|
|
204
|
+
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:8px}.grid3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px}
|
|
205
|
+
.role-layout{display:grid;grid-template-columns:200px 1fr;gap:10px}.role-list button{width:100%;text-align:left;margin-bottom:5px}.role-list button.active{border-color:var(--acc);color:var(--acc)}
|
|
206
|
+
#roleText,#operatorRoleText{min-height:280px}
|
|
207
|
+
/* Klasör seçici */
|
|
208
|
+
.pickrow{display:flex;gap:7px;align-items:stretch}.pickrow input{flex:1}.pickrow button{white-space:nowrap}
|
|
209
|
+
#fsModal{--fsbar:color-mix(in srgb,var(--panel2) 72%,var(--panel));backdrop-filter:blur(8px)}
|
|
210
|
+
#fsModal .dialog{z-index:1;width:min(960px,96vw)!important;height:min(690px,92vh)!important;border-radius:10px;overflow:hidden;box-shadow:0 28px 80px #0009}
|
|
211
|
+
.fs-titlebar{padding:11px 14px;background:var(--fsbar)}.fs-title{display:flex;align-items:center;gap:9px;font-size:13px}.fs-appicon{width:26px;height:26px;display:grid;place-items:center;border-radius:7px;background:color-mix(in srgb,var(--acc) 16%,transparent);color:var(--acc)}.fs-appicon svg{width:16px;height:16px}
|
|
212
|
+
.fs-toolbar{display:flex;align-items:center;gap:6px;padding:8px 10px;border-bottom:1px solid var(--line);background:var(--fsbar)}
|
|
213
|
+
.fs-nav{width:32px;height:30px;padding:0;display:grid;place-items:center;border-color:transparent;background:transparent;font-size:15px}.fs-nav:hover{background:var(--panel2)}
|
|
214
|
+
.fs-address{height:32px;flex:1;display:flex;align-items:center;gap:1px;background:var(--input);border:1px solid var(--line);border-radius:5px;padding:2px 7px;overflow:auto;white-space:nowrap}
|
|
215
|
+
.fs-address:focus-within{border-color:var(--acc);box-shadow:0 0 0 1px var(--acc)}.fs-address input{display:none;border:0;padding:4px;background:transparent;border-radius:0}
|
|
216
|
+
.fs-address.editing input{display:block}.fs-address.editing .fs-breadcrumbs{display:none}.fs-breadcrumbs{display:flex;align-items:center;gap:1px;min-width:max-content}
|
|
217
|
+
.fs-crumb{border:0;background:transparent;border-radius:4px;padding:4px 6px;font-size:11px}.fs-crumb:hover{background:var(--panel2);box-shadow:none}.fs-sep{color:var(--dim);font-size:11px}
|
|
218
|
+
.fs-layout{display:grid;grid-template-columns:190px minmax(0,1fr);flex:1;min-height:0;background:var(--input)}
|
|
219
|
+
.fs-sidebar{padding:8px;border-right:1px solid var(--line);overflow:auto;background:var(--fsbar)}.fs-side-title{font-size:9px;text-transform:uppercase;letter-spacing:.7px;color:var(--dim);padding:8px 9px 4px}
|
|
220
|
+
.fs-place{width:100%;border:0;background:transparent;box-shadow:none;text-align:left;border-radius:5px;padding:7px 9px;display:flex;align-items:center;gap:9px;font-size:11.5px}.fs-place:hover,.fs-place.active{background:var(--panel2);box-shadow:none}.fs-place .ico{width:18px;display:flex;align-items:center;justify-content:center;color:var(--acc);font-size:14px}.fs-place .ico svg{width:15px;height:15px}.fs-place.drive .ico{color:var(--dim)}
|
|
221
|
+
.fs-content{display:flex;flex-direction:column;min-width:0;min-height:0;background:var(--panel)}.fs-colhead,.fs-item{display:grid;grid-template-columns:minmax(210px,1fr) 145px 90px;align-items:center;text-align:left}
|
|
222
|
+
.fs-colhead{height:29px;padding:0 10px;border-bottom:1px solid var(--line);color:var(--dim);font-size:10px;background:var(--panel)}.fs-colhead span+span{border-left:1px solid var(--line);padding-left:10px}
|
|
223
|
+
.fs-list{flex:1;overflow:auto;padding:4px 6px;background:var(--panel)}
|
|
224
|
+
.fs-item{width:100%;border:1px solid transparent;border-radius:4px;padding:5px 5px;background:transparent;color:var(--tx);box-shadow:none;font-size:11.5px;min-height:34px}
|
|
225
|
+
.fs-item:hover{background:color-mix(in srgb,var(--acc) 9%,transparent);box-shadow:none}.fs-item.selected{background:color-mix(in srgb,var(--acc) 18%,transparent);border-color:color-mix(in srgb,var(--acc) 48%,transparent)}
|
|
226
|
+
.fs-item .fname{display:flex;align-items:center;gap:9px;min-width:0}.fs-item .folder-icon{flex:none;display:flex}.fs-item .folder-icon svg{width:17px;height:17px}.fs-item .folder-icon.fi-dir{color:#e0a83a}.fs-item .folder-icon.fi-file{color:var(--dim)}.fs-item .fname b{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500}.fs-item .date,.fs-item .type{color:var(--dim);font-size:10.5px;padding-left:10px}
|
|
227
|
+
.fs-empty{color:var(--dim);font-size:12px;padding:40px 14px;text-align:center}
|
|
228
|
+
.fs-statusbar{display:flex;align-items:center;gap:8px;padding:7px 12px;border-top:1px solid var(--line);background:var(--fsbar);font-size:10.5px;color:var(--dim)}.fs-statusbar>span{flex:none}.fs-statusbar .selected-path{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--tx);font-family:ui-monospace,Consolas,monospace}#fsState{max-width:34%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
229
|
+
#fsModal .dialog-foot{padding:10px 13px;background:var(--fsbar);justify-content:flex-end}.fs-pick{min-width:110px}
|
|
230
|
+
@media(max-width:680px){.fs-layout{grid-template-columns:1fr}.fs-sidebar{display:none}.fs-colhead,.fs-item{grid-template-columns:1fr}.fs-colhead span:not(:first-child),.fs-item .date,.fs-item .type{display:none}}
|
|
231
|
+
.toggle{display:flex;gap:4px;padding:4px;background:var(--input);border:1px solid color-mix(in srgb,var(--line) 70%,transparent);border-radius:12px}
|
|
232
|
+
.toggle button{border-radius:9px;border:1px solid transparent;background:transparent;color:var(--dim);font-weight:550}
|
|
233
|
+
.toggle button:hover{background:color-mix(in srgb,var(--acc) 8%,transparent);color:var(--tx);border-color:transparent}
|
|
234
|
+
.toggle button.active{background:var(--panel);color:var(--tx);border-color:color-mix(in srgb,var(--line) 65%,transparent);box-shadow:0 1px 3px #00000030}
|
|
235
|
+
.toggle button svg{width:14px;height:14px}
|
|
236
|
+
/* Motor başlat/durdur — durum farkında */
|
|
237
|
+
.enginebtn{display:inline-flex;align-items:center;gap:7px;font-weight:700}
|
|
238
|
+
.enginebtn .ic{font-size:11px;line-height:1}
|
|
239
|
+
.enginebtn.ready{background:linear-gradient(180deg,var(--ok),#2fb87c);border-color:var(--ok);color:#04140d;box-shadow:0 4px 16px #42d39233}
|
|
240
|
+
.enginebtn.ready:hover{box-shadow:0 7px 22px #42d39247;border-color:var(--ok)}
|
|
241
|
+
.enginebtn.live{background:linear-gradient(180deg,var(--err),#e0555f);border-color:var(--err);color:#fff;box-shadow:0 4px 16px #ff6b7533}
|
|
242
|
+
.enginebtn.live:hover{box-shadow:0 7px 22px #ff6b7547;border-color:var(--err)}
|
|
243
|
+
.enginebtn.on{background:#12351f;border-color:var(--ok);color:var(--ok);cursor:default}
|
|
244
|
+
[data-theme=light] .enginebtn.on{background:#e4f7ee}
|
|
245
|
+
.enginebtn.idle{background:var(--panel2);border-color:var(--line);color:var(--dim);cursor:default}
|
|
246
|
+
.enginebtn:disabled{opacity:1}
|
|
247
|
+
#stopBtn{display:none}#stopBtn.live{display:inline-flex}#startBtn.on{display:none}
|
|
248
|
+
.rdot{width:8px;height:8px;border-radius:50%;background:currentColor;box-shadow:0 0 7px currentColor;animation:rpulse 1.3s ease-in-out infinite}
|
|
249
|
+
@keyframes rpulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.7)}}
|
|
250
|
+
.chat-history{display:flex;flex-direction:column;gap:9px;min-height:180px;max-height:52vh;overflow:auto;padding:4px}
|
|
251
|
+
.bubble{max-width:88%;padding:9px 11px;border-radius:11px;white-space:pre-wrap}.bubble.user{align-self:flex-end;background:#244b78}.bubble.operator{align-self:flex-start;background:var(--panel2);border:1px solid var(--line)}
|
|
252
|
+
.chat-compose{display:flex;gap:7px;margin-top:10px}.chat-compose textarea{min-height:65px}
|
|
253
|
+
@media(max-width:960px){main{grid-template-columns:1fr}.grid2,.grid3,.role-layout{grid-template-columns:1fr}.header-state #current,.header-tools #budget{display:none}}
|
|
254
|
+
@media(max-width:680px){header{padding:8px 10px;flex-wrap:wrap}header h1{flex:1}.header-state{order:2}.header-tools{order:3;width:100%;overflow-x:auto}main{padding:10px}.mission-sub{max-width:70vw}}
|
|
255
|
+
/* Görev durum rozeti + tamamlanma vurgusu */
|
|
256
|
+
.mission-card{position:relative;transition:border-color .3s,box-shadow .3s}
|
|
257
|
+
.mission-status{display:inline-flex;align-items:center;gap:7px;font-size:10.5px;font-weight:800;letter-spacing:.5px;text-transform:uppercase;border-radius:999px;padding:6px 12px;border:1px solid var(--line);color:var(--dim);background:color-mix(in srgb,var(--input) 70%,transparent)}
|
|
258
|
+
.mission-status .sdot{width:8px;height:8px;border-radius:50%;background:currentColor;box-shadow:0 0 8px currentColor}
|
|
259
|
+
.mission-status.running{color:var(--acc);border-color:color-mix(in srgb,var(--acc) 45%,var(--line));background:color-mix(in srgb,var(--acc) 12%,transparent)}
|
|
260
|
+
.mission-status.running .sdot{animation:rpulse 1.3s ease-in-out infinite}
|
|
261
|
+
.mission-status.completed{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 55%,var(--line));background:color-mix(in srgb,var(--ok) 15%,transparent)}
|
|
262
|
+
.mission-status.failed{color:var(--err);border-color:color-mix(in srgb,var(--err) 55%,var(--line));background:color-mix(in srgb,var(--err) 15%,transparent)}
|
|
263
|
+
.mission-card.completed{border-color:color-mix(in srgb,var(--ok) 48%,var(--line));box-shadow:0 0 0 1px color-mix(in srgb,var(--ok) 28%,transparent),0 18px 46px color-mix(in srgb,var(--ok) 13%,transparent)}
|
|
264
|
+
.mission-card.failed{border-color:color-mix(in srgb,var(--err) 44%,var(--line));box-shadow:0 0 0 1px color-mix(in srgb,var(--err) 24%,transparent)}
|
|
265
|
+
.mission-banner{display:none;align-items:center;gap:12px;margin-top:13px;padding:13px 15px;border-radius:12px;border:1px solid var(--line);background:var(--panel2);animation:bannerIn .4s cubic-bezier(.2,.8,.2,1)}
|
|
266
|
+
.mission-card.completed .mission-banner,.mission-card.failed .mission-banner{display:flex}
|
|
267
|
+
.mission-card.completed .mission-banner{border-color:color-mix(in srgb,var(--ok) 40%,var(--line));background:linear-gradient(120deg,color-mix(in srgb,var(--ok) 12%,var(--nodebg)),var(--nodebg))}
|
|
268
|
+
.mission-card.failed .mission-banner{border-color:color-mix(in srgb,var(--err) 40%,var(--line));background:linear-gradient(120deg,color-mix(in srgb,var(--err) 12%,var(--nodebg)),var(--nodebg))}
|
|
269
|
+
.mission-banner .mb-ico{flex:none;width:36px;height:36px;display:grid;place-items:center;border-radius:10px;font-size:18px;font-weight:800}
|
|
270
|
+
.mission-card.completed .mb-ico{background:color-mix(in srgb,var(--ok) 20%,transparent);color:var(--ok);border:1px solid color-mix(in srgb,var(--ok) 45%,transparent)}
|
|
271
|
+
.mission-card.failed .mb-ico{background:color-mix(in srgb,var(--err) 20%,transparent);color:var(--err);border:1px solid color-mix(in srgb,var(--err) 45%,transparent)}
|
|
272
|
+
.mission-banner .mb-title{font-weight:800;font-size:13.5px;letter-spacing:-.1px}.mission-banner .mb-sub{color:var(--dim);font-size:12px;margin-top:2px}
|
|
273
|
+
.mission-banner .mb-answer{margin-top:6px;font-size:13px;line-height:1.5;white-space:pre-wrap;word-break:break-word;max-height:220px;overflow:auto}
|
|
274
|
+
@keyframes bannerIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
|
|
275
|
+
/* Bildirim (toast) */
|
|
276
|
+
#toasts{position:fixed;right:18px;bottom:18px;z-index:80;display:flex;flex-direction:column;gap:9px}
|
|
277
|
+
.toast{display:flex;align-items:center;gap:11px;min-width:256px;max-width:370px;padding:12px 14px;border-radius:12px;border:1px solid var(--line);background:var(--panel);box-shadow:var(--shadow);animation:toastIn .35s cubic-bezier(.2,.8,.2,1)}
|
|
278
|
+
.toast .t-ico{flex:none;width:28px;height:28px;display:grid;place-items:center;border-radius:8px;font-weight:800}
|
|
279
|
+
.toast.ok{border-color:color-mix(in srgb,var(--ok) 45%,var(--line))}.toast.ok .t-ico{background:color-mix(in srgb,var(--ok) 18%,transparent);color:var(--ok)}
|
|
280
|
+
.toast.err{border-color:color-mix(in srgb,var(--err) 45%,var(--line))}.toast.err .t-ico{background:color-mix(in srgb,var(--err) 18%,transparent);color:var(--err)}
|
|
281
|
+
.toast .t-title{font-weight:750;font-size:13px}.toast .t-sub{color:var(--dim);font-size:11.5px;margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:300px}
|
|
282
|
+
.toast.hide{animation:toastOut .3s forwards}
|
|
283
|
+
@keyframes toastIn{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}
|
|
284
|
+
@keyframes toastOut{to{opacity:0;transform:translateX(24px)}}
|
|
285
|
+
/* Yardım (?) modalı */
|
|
286
|
+
.help-overlay{position:fixed;inset:0;z-index:90;background:#050912cc;backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;padding:20px}
|
|
287
|
+
.help-overlay[hidden]{display:none}
|
|
288
|
+
.help-card{background:var(--panel);border:1px solid var(--line);border-radius:18px;box-shadow:0 30px 80px #000a;max-width:580px;width:100%;max-height:86vh;overflow:auto}
|
|
289
|
+
.help-card .hc-head{display:flex;align-items:center;gap:10px;padding:16px 18px;border-bottom:1px solid var(--line);position:sticky;top:0;background:var(--panel)}
|
|
290
|
+
.help-card .hc-head .ic{font-size:20px}.help-card .hc-head h2{margin:0;font-size:16px}.help-card .hc-head .brand{color:var(--acc)}
|
|
291
|
+
.help-card .hc-close{margin-left:auto}
|
|
292
|
+
.help-card .hc-body{padding:16px 18px;font-size:13px;line-height:1.65}
|
|
293
|
+
.help-card .hc-body p{margin:0 0 12px}
|
|
294
|
+
.hc-row{display:flex;gap:11px;align-items:flex-start;padding:10px 0;border-top:1px solid var(--line)}
|
|
295
|
+
.hc-row .hc-ic{flex:none;width:30px;height:30px;border-radius:9px;display:grid;place-items:center;font-size:15px;background:var(--panel2,#182234);border:1px solid var(--line)}
|
|
296
|
+
.hc-row .hc-t{font-weight:750;font-size:12.5px}.hc-row .hc-d{color:var(--muted,#8494ab);font-size:12px;margin-top:2px}
|
|
297
|
+
.hc-num{flex:none;width:20px;color:var(--acc);font-weight:800;font-size:12.5px}
|
|
298
|
+
.help-card .hc-h{margin:16px 0 6px;font-weight:800;font-size:13.5px;color:var(--acc)}
|
|
299
|
+
#helpBtn{width:32px;height:32px;padding:0;border-radius:50%;display:inline-grid;place-items:center;font-weight:800;font-size:15px;line-height:1;color:var(--muted,#8494ab)}
|
|
300
|
+
#helpBtn:hover{color:var(--acc);border-color:var(--acc)}
|
|
301
|
+
/* Sayfa geçiş yükleyicisi (Ekip Akışı) */
|
|
302
|
+
.nav-loader{position:fixed;inset:0;z-index:100;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;background:radial-gradient(120% 90% at 50% 40%,#1a2745,#0b1224);animation:navFade .25s ease}
|
|
303
|
+
.nav-loader[hidden]{display:none}
|
|
304
|
+
.nav-loader .lt{font-size:12.5px;letter-spacing:.4px;color:#8fa0bb}
|
|
305
|
+
@keyframes navFade{from{opacity:0}to{opacity:1}}
|
|
306
|
+
.orbit-spinner{position:relative;width:64px;height:64px}
|
|
307
|
+
.orbit-spinner .sun{position:absolute;inset:0;margin:auto;width:16px;height:16px;border-radius:50%;background:radial-gradient(circle at 40% 35%,#e6dcff,#a98bff);box-shadow:0 0 16px #a98bff}
|
|
308
|
+
.orbit-spinner .ring{position:absolute;inset:0;border-radius:50%;border:1.5px solid #2a3d63}
|
|
309
|
+
.orbit-spinner .ring:nth-child(2){inset:8px}.orbit-spinner .ring:nth-child(3){inset:18px}
|
|
310
|
+
.orbit-spinner .planet{position:absolute;top:-4px;left:calc(50% - 4px);width:8px;height:8px;border-radius:50%;background:#5e8bff;box-shadow:0 0 8px #5e8bff}
|
|
311
|
+
.orbit-spinner .o1{animation:orbitspin 2.4s linear infinite}.orbit-spinner .o2{animation:orbitspin 3.6s linear infinite reverse}.orbit-spinner .o3{animation:orbitspin 5s linear infinite}
|
|
312
|
+
.orbit-spinner .o2 .planet{background:#2fd08a;box-shadow:0 0 8px #2fd08a}.orbit-spinner .o3 .planet{background:#d97757;box-shadow:0 0 8px #d97757}
|
|
313
|
+
@keyframes orbitspin{to{transform:rotate(360deg)}}
|
|
314
|
+
@media(prefers-reduced-motion:reduce){.orbit-spinner .o1,.orbit-spinner .o2,.orbit-spinner .o3{animation:none}}
|
|
315
|
+
</style>
|
|
316
|
+
<link rel="stylesheet" href="app.css">
|
|
317
|
+
<script src="app.js" defer></script>
|
|
318
|
+
</head>
|
|
319
|
+
<body>
|
|
320
|
+
<header>
|
|
321
|
+
<h1><span class="brand">CrewCtl</span> Command Center</h1>
|
|
322
|
+
<div class="header-state"><span class="pill"><span id="dot" class="dot"></span><span id="engineState">durdu</span></span><span id="current" class="pill">boşta</span></div>
|
|
323
|
+
<div class="grow"></div>
|
|
324
|
+
<div class="header-tools"><span id="usagePill" class="pill" hidden></span><span id="budget" class="pill">0 çağrı</span><button id="startBtn" class="enginebtn ready" onclick="engine('start')"><span class="ic"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 5v14l11-7z" fill="currentColor"/></svg></span> Başlat</button><button id="stopBtn" class="enginebtn idle" onclick="engine('stop')"><span class="ic"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="1.6" fill="currentColor"/></svg></span> Durdur</button><button onclick="location.href='board.html'">Pano</button><button onclick="gotoFlow()">Ekip Akışı</button><button onclick="location.href='code.html'">Canlı Kod</button><button onclick="openSettings()">Ayarlar</button><button id="helpBtn" title="Bu sistem nedir?"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="9.2"/><path d="M12 11v5M12 7.6h.01"/></svg></button><button id="themeBtn" onclick="toggleTheme()" title="Açık / koyu tema"><svg class="i i-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg><svg class="i i-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg></button></div>
|
|
325
|
+
</header>
|
|
326
|
+
<main>
|
|
327
|
+
<aside class="stack">
|
|
328
|
+
<section class="card composer"><h2>Yeni görev</h2><div class="composer-sub">Hedefi yazın; operatör uygun ekibi ve çalışma planını oluştursun.</div>
|
|
329
|
+
<div class="project-bar"><span class="pb-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l8.5 4.5L12 12 3.5 7.5 12 3z"/><path d="M3.5 12L12 16.5 20.5 12"/><path d="M3.5 16.5L12 21l8.5-4.5"/></svg></span><select id="projectSelect" onchange="onProjectChange()" aria-label="Proje seç"></select><button type="button" class="iconbtn" onclick="openProjectModal()" title="Projeleri yönet" aria-label="Projeleri yönet"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M4 21v-7M4 10V3M12 21v-8M12 9V3M20 21v-5M20 11V3M1 14h6M9 9h6M17 16h6"/></svg></button></div>
|
|
330
|
+
<div id="projectPath" class="project-path" hidden></div>
|
|
331
|
+
<div class="mention-wrap"><textarea id="prompt" rows="5" placeholder="Ekibin çözmesini istediğiniz hedefi ve başarı ölçütlerini yazın... (dosya eklemek için @ yazın)"></textarea><div id="mentionBox" class="mention-box" hidden></div></div>
|
|
332
|
+
<div class="attach-bar"><div class="attach-actions"><button type="button" class="attach-btn" onclick="openAttachPicker()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>Dosya ekle</button><button type="button" class="attach-btn" onclick="openProjectFiles()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M3 7h5l2 2h11v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>Dosyaları görüntüle</button></div><div id="attachChips" class="attach-chips"></div></div>
|
|
333
|
+
<details class="advanced"><summary>Görev seçenekleri <span class="muted">Otomatik ayarlar kullanılacak</span></summary><div class="advanced-body"><label>Operatör CLI</label><select id="taskOperator"></select><label>Çalışma modu</label><select id="executionMode"><option value="auto">Otomatik — göreve göre seç</option><option value="fast">Hızlı — tek uzman, kısa iş</option><option value="balanced">Dengeli — standart ekip</option><option value="deep">Derin — kritik iş, kapsamlı denetim</option></select><div id="targetDirRow"><label>Çalışma klasörü</label><div class="pickrow"><input id="targetDir" placeholder="Varsayılan proje klasörü"><button type="button" onclick="openFs(v=>$('#targetDir').value=v,$('#targetDir').value)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7h5l2 2h11v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>Gözat</button></div></div></div></details>
|
|
334
|
+
<div class="toggle" style="margin-top:14px"><button id="cmNow" type="button" class="active" style="flex:1;justify-content:center" onclick="setComposerMode('now')"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg>Hemen çalıştır</button><button id="cmSched" type="button" style="flex:1;justify-content:center" onclick="setComposerMode('sched')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7.5V12l3 2"/></svg>Zamanla</button></div>
|
|
335
|
+
<div id="schedFields" hidden><div class="hint">Görev, seçtiğin zaman gelene kadar <b>çalışmaz</b>; saati geldiğinde otomatik kuyruğa eklenir. Bunun için motorun açık olması gerekir.</div><label>Tetikleyici</label><select id="cs-type" onchange="csTypeChanged()"><option value="interval">Belirli aralıkla</option><option value="daily">Her gün belirli saatte</option><option value="weekly">Haftanın günleri</option></select><div id="cs-fields" style="margin-top:4px"></div></div>
|
|
336
|
+
<button class="primary" id="composerSubmit" style="width:100%;margin-top:10px" onclick="composerSubmit()">Görevi Ekle</button>
|
|
337
|
+
</section>
|
|
338
|
+
<section class="card" id="scheduleCard"><div class="section-title"><h2>Zamanlanmış görevler</h2><span class="badge" id="scheduleCount">0</span></div><div id="scheduleList" class="muted">Henüz zamanlanmış görev yok.</div></section>
|
|
339
|
+
<section class="card" id="approvalCard" hidden><h2>Onay bekleyen</h2><div id="approval"></div></section>
|
|
340
|
+
<section class="card"><div class="section-title"><h2>Görevler</h2><span class="badge" id="pendingCount">0 bekleyen</span></div><div id="queueScope" class="queue-scope" hidden></div><div id="pending" class="muted">kuyruk boş</div><details class="queue-archive"><summary>Geçmiş <span class="muted" id="historyCount">0 görev</span></summary><div class="queue-archive-body"><div class="queue-group"><div class="queue-label">Tamamlanan <span class="badge" id="doneCount">0</span></div><div id="done">—</div></div><div class="queue-group"><div class="queue-label">Başarısız <span class="badge" id="failedCount">0</span></div><div id="failed">—</div></div></div></details></section>
|
|
341
|
+
</aside>
|
|
342
|
+
|
|
343
|
+
<section class="stack">
|
|
344
|
+
<div class="card mission-card" id="missionCard">
|
|
345
|
+
<div class="mission-top"><div><div class="mission-title" id="missionTitle">Takım görevi bekleniyor</div><div class="mission-sub" id="missionSub">Operatör bir hedef aldığında ekibini burada kuracak.</div></div><div class="grow"></div><span class="mission-status" id="missionStatus" hidden><span class="sdot"></span><span id="missionStatusText">Hazır</span></span><span class="badge" id="missionStats">0 mesaj · 0 çağrı</span></div>
|
|
346
|
+
<div class="mission-banner" id="missionBanner"><div class="mb-ico" id="mbIco">✓</div><div style="min-width:0"><div class="mb-title" id="mbTitle"></div><div class="mb-answer" id="mbAnswer" hidden></div><div class="mb-sub" id="mbSub"></div></div></div>
|
|
347
|
+
<div class="flow-seg">
|
|
348
|
+
<div class="flow-seg-head"><span class="lbl">Ekip</span><span class="ln"></span><span class="hint">operatör + uzman ajanlar</span></div>
|
|
349
|
+
<div id="teamMap" class="team-map"><div class="empty" style="width:100%">Henüz aktif ekip yok.</div></div>
|
|
350
|
+
</div>
|
|
351
|
+
<div class="flow-seg">
|
|
352
|
+
<div class="flow-seg-head"><span class="lbl">Canlı akış</span><span class="ln"></span><span class="hint">canlı görev günlüğü</span></div>
|
|
353
|
+
<div id="feed" class="feed"><div class="empty">Henüz etkinlik yok.</div></div>
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
<details class="card live-details"><summary>Teknik ayrıntılar <span class="muted">Canlı agent terminalleri</span></summary><div class="live-body"><div class="command-head"><span class="muted">Operatör ve uzmanların ham çıktıları</span><div class="grow"></div><button class="sm" onclick="clearLive()">Temizle</button></div><div id="agentGrid" class="agent-grid"><div class="empty">Bir görev başladığında terminal çıktıları burada görünür.</div></div></div></details>
|
|
357
|
+
</section>
|
|
358
|
+
</main>
|
|
359
|
+
<div id="toasts"></div>
|
|
360
|
+
|
|
361
|
+
<div class="nav-loader" id="navLoader" hidden>
|
|
362
|
+
<div class="orbit-spinner"><span class="sun"></span><span class="ring o1"><span class="planet"></span></span><span class="ring o2"><span class="planet"></span></span><span class="ring o3"><span class="planet"></span></span></div>
|
|
363
|
+
<div class="lt">Ekip Akışı açılıyor…</div>
|
|
364
|
+
</div>
|
|
365
|
+
|
|
366
|
+
<div class="help-overlay" id="helpModal" hidden>
|
|
367
|
+
<div class="help-card">
|
|
368
|
+
<div class="hc-head"><span class="ic">🛰️</span><h2><span class="brand">CrewCtl</span> nedir?</h2><button class="hc-close" id="helpClose" title="Kapat">✕</button></div>
|
|
369
|
+
<div class="hc-body">
|
|
370
|
+
<p>CrewCtl, kurulu CLI kodlama ajanlarınızı (Codex, Claude, Gemini, OpenCode) <b>tek bir operatör‑liderliğindeki takım</b> gibi çalıştıran yerel bir çok‑ajan orkestratörüdür. Ekstra API anahtarı gerekmez; her şey makinenizde çalışır.</p>
|
|
371
|
+
<div class="hc-row"><div class="hc-num">1</div><div><div class="hc-t">Hedefi yaz</div><div class="hc-d">“Yeni görev” kutusuna ne istediğinizi ve başarı ölçütlerini yazıp <b>Görevi Ekle</b>'ye basın.</div></div></div>
|
|
372
|
+
<div class="hc-row"><div class="hc-num">2</div><div><div class="hc-t">Başlat</div><div class="hc-d">Üstteki <b>Başlat</b> ile motoru çalıştırın. Operatör hedefi analiz eder, işi böler ve uygun uzmanlara delege eder.</div></div></div>
|
|
373
|
+
<div class="hc-row"><div class="hc-num">3</div><div><div class="hc-t">İzle</div><div class="hc-d">Bir CLI operatör (takım lideri) rolünü üstlenir; diğerleri rollerine göre uzman olarak çalışır. <b>Ekip Akışı</b> butonu takımın çalışmasını canlı bir 3B sahnede gösterir.</div></div></div>
|
|
374
|
+
|
|
375
|
+
<div class="hc-h">Uzman ajanlar nasıl oluşturulur?</div>
|
|
376
|
+
<p>Ajanlar, makinenizde kurulu <b>CLI kodlama araçlarıdır</b> — her ajan bir CLI'ye (Codex, Claude Code, Gemini veya OpenCode) bağlanır. Kendi başına bir “model” yaratmazsınız; var olan CLI'larınızı rollere göre bir takıma dizersiniz.</p>
|
|
377
|
+
<div class="hc-row"><div><div class="hc-t">Otomatik keşif</div><div class="hc-d">CrewCtl ilk açılışta kurulu CLI'ları tespit edip her birini <b>güvenli varsayılanlarla</b> birer uzman olarak ekler; yani hazır bir ekiple başlarsınız. <b>Ayarlar → CLI'lar</b>'da <b>CLI Tara</b> ile sonradan kurulanları da algılatabilirsiniz.</div></div></div>
|
|
378
|
+
<div class="hc-row"><div><div class="hc-t">Elle ekleme</div><div class="hc-d"><b>Ayarlar → Ajanlar</b>'dan yeni uzman ekleyebilirsiniz: bir CLI şablonu seçin, adını verin; komut, argüman ve zaman aşımı gibi ayarlar otomatik dolar. İstediğinizi etkinleştirip devre dışı bırakabilirsiniz.</div></div></div>
|
|
379
|
+
<div class="hc-row"><div><div class="hc-t">Rol = yetenek</div><div class="hc-d">Her uzmanın bir rolü vardır ve bu rol görev türünü <b>bağlayıcı biçimde</b> sınırlar: <b>executor</b> uygulama yapar/kod yazar, <b>reviewer</b> inceler, <b>planner</b> planlar. Operatör işi türüne göre doğru role yönlendirir.</div></div></div>
|
|
380
|
+
<div class="hc-row"><div><div class="hc-t">Önerilen kurulum</div><div class="hc-d">En az bir <b>executor</b> (uygulama) uzmanı gerekir. Kaliteli sonuç için dengeli modda <b>planner → executor → reviewer</b> üçlüsü aynı turda çalışır. Farklı CLI'ları farklı rollere dağıtmak (ör. güçlü bir modeli reviewer'a vermek) hem çeşitliliği hem denetimi güçlendirir. Not: <b>operatör ayrı bir CLI'dır</b>, uzmanlardan biri değildir ve <b>Ayarlar → Operatör</b>'den seçilir.</div></div></div>
|
|
381
|
+
<p style="margin:12px 0 0;color:var(--muted,#8494ab);font-size:12px">Sıfır bağımlılık, tamamen yerel çalışır; ekstra API anahtarı gerekmez. Konfigürasyon ilk çalıştırmada makinenize göre otomatik üretilir.</p>
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
|
|
386
|
+
<div id="settings" class="modal"><div class="dialog">
|
|
387
|
+
<div class="dialog-head"><b>Takım ve CLI Ayarları</b><div class="grow"></div><button onclick="closeSettings()">Kapat</button></div>
|
|
388
|
+
<div class="dialog-body"><div class="tabs"><button data-tab="agents" onclick="showTab('agents')">Agent'lar & Roller</button><button data-tab="clis" onclick="showTab('clis')">CLI'lar ve Modeller</button><button data-tab="operator" onclick="showTab('operator')">Operatör</button><button data-tab="skills" onclick="showTab('skills')">Beceriler</button><button data-tab="general" onclick="showTab('general')">Genel</button></div><div id="settingsContent"></div></div>
|
|
389
|
+
<div class="dialog-foot"><button class="primary" onclick="saveSettings()">Kaydet</button><span id="saveState" class="muted"></span></div>
|
|
390
|
+
</div></div>
|
|
391
|
+
|
|
392
|
+
<div id="consentModal" class="modal" style="z-index:60" data-testid="autonomous-consent"><div class="dialog" style="width:min(620px,94vw);height:auto">
|
|
393
|
+
<div class="dialog-head"><b>Otonom CLI çalışma izni</b></div>
|
|
394
|
+
<div class="dialog-body">
|
|
395
|
+
<div class="hint" style="font-size:14px;line-height:1.55">Bu uygulama Codex, Claude, Gemini ve OpenCode gibi yerel CLI'ları izin sorularında beklemeden çalıştırır. CLI'lar seçtiğiniz çalışma klasöründe komut çalıştırabilir, dosya oluşturabilir veya değiştirebilir ve kendi hesaplarıyla ağ erişimi kullanabilir.</div>
|
|
396
|
+
<p class="muted">Bu uyarı yalnızca ilk kullanımda gösterilir. Kabul kaydı bu kuruluma ait yerel <code>config.json</code> dosyasında saklanır.</p>
|
|
397
|
+
</div>
|
|
398
|
+
<div class="dialog-foot"><button onclick="dismissConsent()">Şimdilik kullanma</button><div class="grow"></div><button class="primary" data-testid="accept-autonomous-consent" onclick="acceptConsent()">Anladım, otonom çalışmayı etkinleştir</button></div>
|
|
399
|
+
</div></div>
|
|
400
|
+
|
|
401
|
+
<div id="editModal" class="modal"><div class="dialog" style="width:min(640px,95vw);height:auto;max-height:90vh">
|
|
402
|
+
<div class="dialog-head"><b>Bekleyen görevi düzenle</b><div class="grow"></div><button onclick="closeEdit()">Kapat</button></div>
|
|
403
|
+
<div class="dialog-body">
|
|
404
|
+
<label>Hedef ve başarı ölçütleri</label><textarea id="editPrompt" rows="6"></textarea>
|
|
405
|
+
<div class="grid2"><div><label>Operatör modeli</label><select id="editOperator"></select></div><div><label>Çalışma modu</label><select id="editMode"><option value="auto">Otomatik</option><option value="fast">Hızlı</option><option value="balanced">Dengeli</option><option value="deep">Derin</option></select></div></div>
|
|
406
|
+
<label>Çalışma klasörü</label><div class="pickrow"><input id="editDir" placeholder="Varsayılan proje klasörü"><button type="button" onclick="openFs(v=>$('#editDir').value=v,$('#editDir').value)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7h5l2 2h11v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>Gözat</button></div>
|
|
407
|
+
<div class="hint">Hedefi değiştirirseniz operatör planı sıfırlanır ve görev bir sonraki turda yeniden planlanır.</div>
|
|
408
|
+
</div>
|
|
409
|
+
<div class="dialog-foot"><button class="primary" onclick="saveEdit()">Kaydet</button><span id="editState" class="muted"></span></div>
|
|
410
|
+
</div></div>
|
|
411
|
+
|
|
412
|
+
<div id="projectModal" class="modal"><div class="dialog" style="width:min(660px,95vw);height:auto;max-height:90vh">
|
|
413
|
+
<div class="dialog-head"><b>Projeler</b><div class="grow"></div><button onclick="closeProjectModal()">Kapat</button></div>
|
|
414
|
+
<div class="dialog-body">
|
|
415
|
+
<div class="composer-sub" style="margin-top:0">Proje = kaydedilmiş, isimli çalışma klasörü. Bir kez tanımla; görevlerde her seferinde yol seçme.</div>
|
|
416
|
+
<div id="projectList"></div>
|
|
417
|
+
<details id="projectForm" class="advanced" style="margin-top:12px" open><summary>Yeni proje</summary><div class="advanced-body">
|
|
418
|
+
<label>Klasör seç</label><div class="pickrow"><input id="pfPath" placeholder="Projenin klasörünü seçin" oninput="onPfPathInput()"><button type="button" onclick="pickProjectFolder()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7h5l2 2h11v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>Gözat</button></div>
|
|
419
|
+
<label>Proje adı <span class="muted">— klasörden otomatik, düzenlenebilir</span></label><input id="pfName" placeholder="Klasör adı" oninput="this.dataset.touched='1'">
|
|
420
|
+
<div class="dialog-foot" style="padding:12px 0 0"><button class="primary" onclick="createProject()">Proje oluştur</button><span id="pfState" class="muted"></span></div>
|
|
421
|
+
</div></details>
|
|
422
|
+
</div>
|
|
423
|
+
</div></div>
|
|
424
|
+
|
|
425
|
+
<div id="fsModal" class="modal" style="z-index:40"><div class="dialog">
|
|
426
|
+
<div class="dialog-head fs-titlebar"><div class="fs-title"><span class="fs-appicon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7h5l2 2h11v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg></span><b id="fsTitle">Çalışma klasörü seç</b></div><div class="grow"></div><button class="fs-nav" onclick="closeFs()" title="Kapat">✕</button></div>
|
|
427
|
+
<div class="fs-toolbar">
|
|
428
|
+
<button class="fs-nav" onclick="fsBack()" id="fsBackBtn" title="Geri">←</button>
|
|
429
|
+
<button class="fs-nav" onclick="fsUp()" id="fsUpBtn" title="Üst klasör">↑</button>
|
|
430
|
+
<button class="fs-nav" onclick="fsGo('')" title="Bu bilgisayar">⌂</button>
|
|
431
|
+
<div class="fs-address" id="fsAddress" ondblclick="fsEditAddress()">
|
|
432
|
+
<div class="fs-breadcrumbs" id="fsBreadcrumbs"></div>
|
|
433
|
+
<input id="fsPath" aria-label="Klasör yolu" onkeydown="fsAddressKey(event)" onblur="fsEndAddress(false)">
|
|
434
|
+
</div>
|
|
435
|
+
<button class="fs-nav" onclick="fsGo(fsCurrent,false)" title="Yenile">↻</button>
|
|
436
|
+
</div>
|
|
437
|
+
<div class="fs-layout">
|
|
438
|
+
<aside class="fs-sidebar"><div class="fs-side-title">Hızlı erişim</div><div id="fsPlaces"></div><div class="fs-side-title">Bu bilgisayar</div><div id="fsDrives"></div></aside>
|
|
439
|
+
<section class="fs-content"><div class="fs-colhead"><span>Ad</span><span>Değiştirme tarihi</span><span>Tür</span></div><div id="fsList" class="fs-list"></div></section>
|
|
440
|
+
</div>
|
|
441
|
+
<div class="fs-statusbar"><span id="fsCount">0 öğe</span><span>·</span><span class="selected-path" id="fsSelection">Klasör seçilmedi</span><span id="fsState"></span></div>
|
|
442
|
+
<div class="dialog-foot"><button onclick="closeFs()">İptal</button><button class="primary fs-pick" id="fsPickBtn" onclick="fsPick()">Klasör seç</button></div>
|
|
443
|
+
</div></div>
|
|
444
|
+
|
|
445
|
+
<div id="chatModal" class="modal"><div class="dialog" style="width:min(760px,95vw);height:auto;max-height:90vh">
|
|
446
|
+
<div class="dialog-head"><div><b id="chatTitle">Operatörle konuş</b><div class="muted" id="chatSubtitle"></div></div><div class="grow"></div><button onclick="closeChat()">Kapat</button></div>
|
|
447
|
+
<div class="dialog-body"><div id="chatHistory" class="chat-history"></div><div class="chat-compose"><textarea id="chatQuestion" placeholder="Bu görev, eklenen dosyalar veya alınan kararlar hakkında sor..."></textarea><button class="primary" onclick="sendChat()">Sor</button></div><div id="chatState" class="muted" style="margin-top:5px"></div></div>
|
|
448
|
+
</div></div>
|
|
449
|
+
|
|
450
|
+
<script>
|
|
451
|
+
const $=s=>document.querySelector(s); let STATE={config:{agents:{}},roles:[],skills:[],schedules:[],status:{},queue:{}}, draftAgents=[],draftCliSettings={},draftIgnoredAdapters=[],tab='agents',currentRole=null,callCards=new Map(),teamNodes=new Map(),missionCalls=0,missionMessages=0,liveStep=0,chatParent=null,replaying=false,bootstrapping=true,bufferedEvents=[],es=null;
|
|
452
|
+
const OPERATOR_ROLES=['operator.md','operator-chat.md'];
|
|
453
|
+
const workerRoles=()=>STATE.roles.filter(r=>!OPERATOR_ROLES.includes(r));
|
|
454
|
+
const esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
455
|
+
function applyTheme(t){document.documentElement.setAttribute('data-theme',t);try{localStorage.setItem('cliTheme',t)}catch{}}
|
|
456
|
+
function toggleTheme(){applyTheme(document.documentElement.getAttribute('data-theme')==='light'?'dark':'light')}
|
|
457
|
+
(function(){let t='dark';try{t=localStorage.getItem('cliTheme')||'dark'}catch{}applyTheme(t)})();
|
|
458
|
+
const time=at=>new Date(at||Date.now()).toLocaleTimeString();
|
|
459
|
+
async function api(path,method='GET',body){const r=await fetch(path,{method,headers:{'Content-Type':'application/json'},body:body?JSON.stringify(body):undefined});const data=await r.json();if(!r.ok){const e=new Error(data.error||`HTTP ${r.status}`);e.code=data.code;e.status=r.status;throw e}return data}
|
|
460
|
+
function notify(text,error=false){const el=$('#saveState');if(el){el.textContent=text;el.style.color=error?'var(--err)':''}showToast(error?'err':'ok',text)}
|
|
461
|
+
/* Yardım (?) modalı */
|
|
462
|
+
(function(){const m=$('#helpModal'),b=$('#helpBtn'),c=$('#helpClose');if(!m||!b)return;const open=()=>{m.hidden=false},close=()=>{m.hidden=true};
|
|
463
|
+
b.addEventListener('click',open);if(c)c.addEventListener('click',close);
|
|
464
|
+
m.addEventListener('click',e=>{if(e.target===m)close()});
|
|
465
|
+
document.addEventListener('keydown',e=>{if(e.key==='Escape'&&!m.hidden)close()});})();
|
|
466
|
+
/* Ekip Akışı'na geçerken anında yükleme efekti göster, sonra git */
|
|
467
|
+
function gotoFlow(){const n=$('#navLoader');if(n)n.hidden=false;setTimeout(()=>{location.href='flow.html'},40)}
|
|
468
|
+
const CLI_LABEL={codex:'Codex',claude:'Claude Code',gemini:'Gemini',opencode:'OpenCode'};
|
|
469
|
+
// Proje genelinde tek renk standardi: her CLI kendi marka rengiyle gosterilir (flow.html
|
|
470
|
+
// CLI_META ile birebir ayni). Durum (calisiyor/hata) ayri ipuclarıyla (nabiz, ilerleme, halka)
|
|
471
|
+
// belirtilir; kimlik rengi degismez. custom = notr gri.
|
|
472
|
+
const CLI_COLOR={codex:'#10a37f',claude:'#d97757',gemini:'#4285f4',opencode:'#0ea5e9',custom:'#8b98b0'};
|
|
473
|
+
function cliColor(id){return CLI_COLOR[id]||CLI_COLOR.custom}
|
|
474
|
+
// Bir katilimcinin (operator CLI id'si ya da uzman agent adi) CLI'sini coz. Operator mesaj/
|
|
475
|
+
// aktivite olaylarinda dogrudan CLI id'siyle gelir; uzman ise config.agents anahtaridir.
|
|
476
|
+
function agentCli(name){if(!name)return 'custom';const key=String(name).toLowerCase();if(CLI_COLOR[key]&&key!=='custom')return key;const a=STATE.config.agents&&STATE.config.agents[name];return a?adapterOf(a):'custom'}
|
|
477
|
+
function agentColor(name){return cliColor(agentCli(name))}
|
|
478
|
+
const CLI_SETTINGS_DEFAULTS={codex:{model:'',reasoningEffort:'medium',serviceTier:'fast'},claude:{model:''},opencode:{model:''}};
|
|
479
|
+
function cloneCliSettings(value){const source=value&&typeof value==='object'?value:{};return Object.fromEntries(Object.entries(CLI_SETTINGS_DEFAULTS).map(([id,defaults])=>[id,{...defaults,...(source[id]||{})}]))}
|
|
480
|
+
function adapterOf(a){const b=String(a?.adapter||a?.cmd||'').toLowerCase();if(b.includes('codex'))return 'codex';if(b.includes('claude'))return 'claude';if(b.includes('gemini'))return 'gemini';if(b.includes('opencode'))return 'opencode';return b||'custom'}
|
|
481
|
+
function commandAdapter(command){const b=String(command||'').replace(/\\/g,'/').split('/').pop().toLowerCase().replace(/\.(cmd|bat|exe|ps1)$/,'');if(b.includes('codex'))return 'codex';if(b.includes('claude'))return 'claude';if(b.includes('gemini'))return 'gemini';if(b.includes('opencode'))return 'opencode';return 'custom'}
|
|
482
|
+
function normalizeDraftAgentAdapters(){for(const agent of draftAgents){const inferred=commandAdapter(agent.cmd),configured=String(agent.adapter||'').toLowerCase(),next=inferred!=='custom'?inferred:(CLI_LABEL[configured]?configured:'custom');if(inferred!=='custom'&&configured&&configured!==inferred){const template=(STATE.cliStatus||[]).find(x=>x.id===inferred);agent.args=[...(template?.defaultArgs||[])];delete agent.model;delete agent.modelOverride}agent.adapter=next}}
|
|
483
|
+
function setAgentCommand(index,value){const agent=draftAgents[index];if(!agent)return;agent.cmd=String(value||'').trim();const inferred=commandAdapter(agent.cmd),previous=String(agent.adapter||'').toLowerCase();agent.adapter=inferred;if(previous!==inferred){const template=(STATE.cliStatus||[]).find(x=>x.id===inferred);agent.args=[...(template?.defaultArgs||[])];delete agent.model;delete agent.modelOverride}renderAgents($('#settingsContent'))}
|
|
484
|
+
function cliLabel(id){return CLI_LABEL[id]||(String(id||'').charAt(0).toUpperCase()+String(id||'').slice(1))}
|
|
485
|
+
// Operatör, uzman agent'lardan BAĞIMSIZ bir CLI'dır. Seçenekler kurulu CLI'lardan üretilir;
|
|
486
|
+
// bir uzman agent silinse bile operatör listesi etkilenmez.
|
|
487
|
+
function installedClis(includeUnready=false){const selected=STATE.config.cliSettings?.opencode?.model||draftCliSettings.opencode?.model;const ids=(STATE.cliStatus||[]).filter(x=>x.installed&&(includeUnready||x.id!=='opencode'||x.ready!==false||Boolean(selected))).map(x=>x.id);return ids.length?ids:(STATE.cliStatus?.length?[]:Object.keys(CLI_LABEL))}
|
|
488
|
+
function operatorCliOptions(includeUnready=false){return installedClis(includeUnready).map(id=>({cli:id,label:cliLabel(id)}))}
|
|
489
|
+
function fillOperatorSelect(el,selectedCli){if(!el)return;const opts=operatorCliOptions();const cur=opts.some(o=>o.cli===selectedCli)?selectedCli:opts[0]?.cli;el.innerHTML=opts.map(o=>`<option value="${o.cli}" ${o.cli===cur?'selected':''}>${esc(o.label)}</option>`).join('')}
|
|
490
|
+
// sync=true → görev formunu ayarlardaki varsayılan operatör CLI'sına eşitle
|
|
491
|
+
function refreshOperatorSelect(sync){const el=$('#taskOperator');if(!el)return;fillOperatorSelect(el,sync?STATE.config.operator?.cli:(el.value||STATE.config.operator?.cli))}
|
|
492
|
+
/* Token/maliyet biçimleyicileri. Yalnızca JSON modunda çalışan CLI'lar (bugün OpenCode) veri
|
|
493
|
+
üretir; veri yoksa sayı uydurmak yerine bölüm hiç gösterilmez. */
|
|
494
|
+
function fmtTokens(n){const v=Number(n)||0;if(v>=1e6)return (v/1e6).toFixed(v>=1e7?0:1)+'M';if(v>=1e3)return (v/1e3).toFixed(v>=1e4?0:1)+'B';return String(v)}
|
|
495
|
+
function fmtCost(n){const v=Number(n)||0;if(!v)return '$0';return v<0.01?'<$0.01':'$'+v.toFixed(v<1?3:2)}
|
|
496
|
+
function usageLine(u){if(!u)return '';const t=u.total||u;return `${fmtTokens((t.input||0)+(t.output||0))} token · ${fmtCost(t.cost)}`}
|
|
497
|
+
function applyStatus(s){STATE.status=s;$('#dot').className='dot'+(s.running?' on':'');$('#engineState').textContent=s.running?'çalışıyor':'durdu';$('#current').textContent=s.current?`${s.current.stage} · ${s.current.agent}`:(s.busy?'meşgul':'boşta');$('#budget').textContent=`${s.callsToday}/${s.budget} çağrı`;const ut=s.usageToday,tok=ut?(ut.input||0)+(ut.output||0):0,pill=$('#usagePill');if(pill){pill.hidden=!tok;pill.textContent=`${fmtTokens(tok)} token · ${fmtCost(ut&&ut.cost)} bugün`;pill.title=ut?`Bugün: ${(ut.input||0).toLocaleString('tr')} girdi + ${(ut.output||0).toLocaleString('tr')} çıktı token, ${(ut.cacheRead||0).toLocaleString('tr')} önbellekten okundu`:''}renderEngineButtons(s.running)}
|
|
498
|
+
function renderEngineButtons(running){const start=$('#startBtn'),stop=$('#stopBtn');if(!start||!stop)return;if(running){start.className='enginebtn on';start.disabled=true;start.innerHTML='<span class="rdot"></span> Çalışıyor';stop.className='enginebtn live';stop.disabled=false;stop.innerHTML='<span class="ic"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="1.6" fill="currentColor"/></svg></span> Durdur'}else{start.className='enginebtn ready';start.disabled=false;start.innerHTML='<span class="ic"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 5v14l11-7z" fill="currentColor"/></svg></span> Başlat';stop.className='enginebtn idle';stop.disabled=true;stop.innerHTML='<span class="ic"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="1.6" fill="currentColor"/></svg></span> Durdur'}}
|
|
499
|
+
function showConsent(){const modal=$('#consentModal');if(modal)modal.classList.add('open')}
|
|
500
|
+
function dismissConsent(){$('#consentModal').classList.remove('open')}
|
|
501
|
+
async function acceptConsent(){try{const r=await api('/api/security/autonomous-consent','POST',{accepted:true});STATE.config.autonomousConsentAcceptedAt=r.acceptedAt;dismissConsent();notify('Otonom CLI çalışma izni kaydedildi. Bu uyarı tekrar gösterilmeyecek.')}catch(e){alert(e.message)}}
|
|
502
|
+
async function engine(action){if(action==='start'&&!STATE.config.autonomousConsentAcceptedAt){showConsent();return}try{applyStatus(await api('/api/engine','POST',{action}))}catch(e){if(e.code==='AUTONOMOUS_CONSENT_REQUIRED')showConsent();else alert(e.message)}}
|
|
503
|
+
/* ---- Görev durumu (belirgin tamamlanma göstergesi) ---- */
|
|
504
|
+
function setMissionStatus(state,text){const el=$('#missionStatus'),tx=$('#missionStatusText'),card=$('#missionCard');if(card)card.classList.remove('completed','failed');if(!state){if(el)el.hidden=true;return}if(el){el.hidden=false;el.className='mission-status '+state}if(tx)tx.textContent=text||'';if(card&&(state==='completed'||state==='failed'))card.classList.add(state)}
|
|
505
|
+
/* Operatörün nihai cevabı, banner'ın ana içeriğidir: "merhaba" gibi sohbet görevlerinde
|
|
506
|
+
kullanıcının görmesi gereken tek şey odur, istatistik satırı değil. */
|
|
507
|
+
function missionCompleted(info,answer){setMissionStatus('completed','Tamamlandı');const i=$('#mbIco'),t=$('#mbTitle'),s=$('#mbSub'),a=$('#mbAnswer');if(i)i.textContent='✓';if(t)t.textContent='Görev başarıyla tamamlandı';if(a){const text=String(answer||'').trim();a.textContent=text;a.hidden=!text}if(s)s.textContent=info||''}
|
|
508
|
+
/* Başarısızlıkta ham hata metnini kırpmak yerine sınıflandırılmış teşhisi gösteririz:
|
|
509
|
+
başlıkta NE OLDU, altında NE YAPMALI. failure yoksa eski davranış korunur. */
|
|
510
|
+
function missionFailed(info,failure){setMissionStatus('failed','Başarısız');const i=$('#mbIco'),t=$('#mbTitle'),s=$('#mbSub'),a=$('#mbAnswer');if(i)i.textContent='✕';if(t)t.textContent=failure&&failure.summary?failure.summary:'Görev başarısız oldu';if(a){const act=failure&&failure.action?failure.action:'';a.textContent=act?`→ ${act}`:'';a.hidden=!act}if(s)s.textContent=info||''}
|
|
511
|
+
function showToast(kind,title,sub){let box=$('#toasts');if(!box){box=document.createElement('div');box.id='toasts';document.body.appendChild(box)}const t=document.createElement('div');t.className='toast '+(kind||'');t.innerHTML=`<div class="t-ico">${kind==='err'?'✕':'✓'}</div><div><div class="t-title">${esc(title)}</div>${sub?`<div class="t-sub">${esc(sub)}</div>`:''}</div>`;box.appendChild(t);setTimeout(()=>{t.classList.add('hide');setTimeout(()=>t.remove(),300)},5200)}
|
|
512
|
+
// Temiz, tutarli inline SVG ikon seti (emoji yerine — stroke-tabanli, currentColor, temaya uyumlu).
|
|
513
|
+
const ICN={
|
|
514
|
+
project:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l8.5 4.5L12 12 3.5 7.5 12 3z"/><path d="M3.5 12L12 16.5 20.5 12"/><path d="M3.5 16.5L12 21l8.5-4.5"/></svg>',
|
|
515
|
+
file:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/></svg>',
|
|
516
|
+
clip:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>',
|
|
517
|
+
clock:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7.5V12l3 2"/></svg>',
|
|
518
|
+
folderOpen:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7h5l2 2h11v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>',
|
|
519
|
+
folder:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7.5A1.5 1.5 0 0 1 4.5 6h4l2 2.2h7A1.5 1.5 0 0 1 19 9.7v7.8A1.5 1.5 0 0 1 17.5 19h-13A1.5 1.5 0 0 1 3 17.5z"/></svg>',
|
|
520
|
+
drive:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="7" width="19" height="10" rx="2"/><path d="M6.5 12h.01M10 12h7.5"/></svg>'
|
|
521
|
+
};
|
|
522
|
+
// ---- Projeler (kaydedilmiş çalışma klasörleri) ----
|
|
523
|
+
let selectedProjectId='';
|
|
524
|
+
function projectList(){return (STATE.projects||[]).slice().sort((a,b)=>(Date.parse(b.lastUsedAt||b.createdAt||0))-(Date.parse(a.lastUsedAt||a.createdAt||0)))}
|
|
525
|
+
function renderProjectSelect(){const sel=$('#projectSelect');if(!sel)return;const list=projectList();if(list.every(p=>p.id!==selectedProjectId))selectedProjectId='';sel.innerHTML=`<option value="">Genel — proje yok</option>`+list.map(p=>`<option value="${esc(p.id)}" ${p.id===selectedProjectId?'selected':''}>${esc(p.name)}</option>`).join('');applyProjectSelection();if(STATE.queue)renderQueueScoped()}
|
|
526
|
+
function applyProjectSelection(){const p=(STATE.projects||[]).find(x=>x.id===selectedProjectId);const pathBox=$('#projectPath'),dirRow=$('#targetDirRow');if(p){$('#targetDir').value=p.path;if(pathBox){pathBox.hidden=false;pathBox.innerHTML=`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 7h5l2 2h11v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg><span>${esc(p.path)}</span>`}if(dirRow)dirRow.hidden=true}else{if(pathBox)pathBox.hidden=true;if(dirRow)dirRow.hidden=false}}
|
|
527
|
+
function persistActiveProject(){try{localStorage.setItem('crewctl.activeProject',selectedProjectId||'')}catch{}}
|
|
528
|
+
function onProjectChange(){selectedProjectId=$('#projectSelect').value;persistActiveProject();applyProjectSelection();renderQueueScoped()}
|
|
529
|
+
function openProjectModal(){renderProjectList();$('#pfName').value='';$('#pfName').dataset.touched='';$('#pfPath').value='';$('#pfState').textContent='';$('#projectModal').classList.add('open')}
|
|
530
|
+
function baseName(p){return String(p||'').replace(/[\\/]+$/,'').split(/[\\/]/).pop()||''}
|
|
531
|
+
function onPfPathInput(){const nameEl=$('#pfName');if(nameEl&&!nameEl.dataset.touched)nameEl.value=baseName($('#pfPath').value)}
|
|
532
|
+
function pickProjectFolder(){openFs(v=>{$('#pfPath').value=v;onPfPathInput()},$('#pfPath').value)}
|
|
533
|
+
function closeProjectModal(){$('#projectModal').classList.remove('open')}
|
|
534
|
+
function renderProjectList(){const box=$('#projectList');if(!box)return;const list=projectList();box.innerHTML=list.length?list.map(p=>`<div class="proj-item"><div class="pi-main"><div class="pi-name">${esc(p.name)}</div><div class="pi-path" title="${esc(p.path)}">${esc(p.path)}</div></div>${p.operatorCli?`<span class="badge">${esc(cliLabel(p.operatorCli))}</span>`:''}<button class="sm" onclick="selectProjectFromModal('${esc(p.id)}')">Seç</button><button class="danger sm" onclick="deleteProjectById('${esc(p.id)}')">Sil</button></div>`).join(''):'<div class="empty">Henüz proje yok. Aşağıdan bir tane oluşturun.</div>'}
|
|
535
|
+
async function refreshProjects(){try{const r=await api('/api/projects');STATE.projects=r.projects||[]}catch{}}
|
|
536
|
+
async function createProject(){const path=$('#pfPath').value.trim();if(!path){$('#pfState').textContent='Önce bir klasör seçin.';$('#pfState').style.color='var(--err)';return}const name=($('#pfName').value.trim())||baseName(path);try{const p=await api('/api/projects','POST',{name,path});await refreshProjects();selectedProjectId=p.id;persistActiveProject();renderProjectSelect();renderQueueScoped();renderProjectList();$('#pfName').value='';$('#pfName').dataset.touched='';$('#pfPath').value='';$('#pfState').textContent='';showToast('ok','Proje oluşturuldu',p.name)}catch(e){$('#pfState').textContent=e.message;$('#pfState').style.color='var(--err)'}}
|
|
537
|
+
function selectProjectFromModal(id){selectedProjectId=id;persistActiveProject();renderProjectSelect();renderQueueScoped();closeProjectModal()}
|
|
538
|
+
async function deleteProjectById(id){const p=(STATE.projects||[]).find(x=>x.id===id);if(!await appConfirm({title:'Proje silinsin mi?',body:`"${p?p.name:id}" projesi kaldırılacak. Klasör ve dosyalar silinmez; yalnızca kayıt kaldırılır.`,confirmLabel:'Sil',cancelLabel:'Vazgeç',danger:true}))return;try{await api('/api/projects/'+encodeURIComponent(id),'DELETE');if(selectedProjectId===id){selectedProjectId='';persistActiveProject()}await refreshProjects();renderProjectSelect();renderQueueScoped();renderProjectList();showToast('ok','Proje silindi')}catch(e){showToast('err','Silinemedi',e.message)}}
|
|
539
|
+
let pendingAttachments=[];
|
|
540
|
+
const MAX_ATTACH=12, MAX_ATTACH_BYTES=24*1024*1024;
|
|
541
|
+
function fmtSize(n){return n<1024?n+' B':n<1048576?(n/1024).toFixed(0)+' KB':(n/1048576).toFixed(1)+' MB'}
|
|
542
|
+
function renderAttachChips(){const box=$('#attachChips');if(!box)return;box.innerHTML=pendingAttachments.map((a,i)=>`<span class="attach-chip"><span class="ext">${esc((a.name.split('.').pop()||'?').slice(0,5))}</span><span class="nm" title="${esc(a.srcPath||a.name)}">${esc(a.name)}</span>${a.size!=null?`<span class="sz">${fmtSize(a.size)}</span>`:''}<button type="button" class="rm" title="Kaldır" onclick="removeAttach(${i})">×</button></span>`).join('')}
|
|
543
|
+
function removeAttach(i){pendingAttachments.splice(i,1);renderAttachChips()}
|
|
544
|
+
function clearAttachments(){pendingAttachments=[];renderAttachChips()}
|
|
545
|
+
// Aktif çalışma yolu: seçili proje → onun path'i; yoksa görev klasörü/varsayılan.
|
|
546
|
+
function activeWorkingPath(){const p=(STATE.projects||[]).find(x=>x.id===selectedProjectId);if(p)return p.path;const td=$('#targetDir')&&$('#targetDir').value.trim();return td||(STATE.workingDirAbs||'')}
|
|
547
|
+
// Attach = sunucu tarafı dosya seçici, çalışma path'inde açılır (cihazı açmaz). Seçilen dosya
|
|
548
|
+
// yol olarak tutulur; motor çalışırken path cwd içindeyse KOPYA YOK (sadece göreli yol), dışındaysa
|
|
549
|
+
// .crewctl'e kopyalanır. Okuma modele kalır.
|
|
550
|
+
function openAttachPicker(){openFs(f=>addServerAttachment(f),activeWorkingPath(),{files:true,title:'Dosya ekle',pickLabel:'Dosyayı ekle'})}
|
|
551
|
+
function openProjectFiles(){const start=activeWorkingPath();if(!start){showToast('err','Klasör yok','Önce bir proje seçin veya çalışma klasörü belirtin.');return}openFs(null,start,{files:true,view:true,title:'Proje dosyaları'})}
|
|
552
|
+
function addServerAttachment(f){if(!f||!f.path)return;if(pendingAttachments.length>=MAX_ATTACH){showToast('err','Ek sınırı',`En fazla ${MAX_ATTACH} dosya eklenebilir.`);return}if(pendingAttachments.some(a=>a.srcPath===f.path))return;pendingAttachments.push({name:f.name,srcPath:f.path});renderAttachChips()}
|
|
553
|
+
// @-bahsi: görev metninde @ yazınca proje/çalışma klasöründeki dosyaları listeler (Cursor/Claude tarzı).
|
|
554
|
+
let mtOpen=false,mtStart=-1,mtItems=[],mtIndex=0,mtTimer=null,mtSeq=0;
|
|
555
|
+
function setupMentions(){const ta=$('#prompt');if(!ta)return;ta.addEventListener('input',onPromptInput);ta.addEventListener('keydown',onPromptKeydown);ta.addEventListener('blur',()=>setTimeout(closeMentions,150))}
|
|
556
|
+
function currentMentionQuery(ta){const pos=ta.selectionStart;const m=ta.value.slice(0,pos).match(/(?:^|\s)@([^\s@]*)$/);return m?{q:m[1],start:pos-m[1].length-1}:null}
|
|
557
|
+
function onPromptInput(e){const ta=e.target;const ctx=currentMentionQuery(ta);if(!ctx){closeMentions();return}mtStart=ctx.start;clearTimeout(mtTimer);mtTimer=setTimeout(()=>fetchMentions(ctx.q),140)}
|
|
558
|
+
async function fetchMentions(q){const dir=activeWorkingPath();if(!dir){closeMentions();return}const seq=++mtSeq;try{const r=await api('/api/files?dir='+encodeURIComponent(dir)+'&q='+encodeURIComponent(q)+'&limit=30');if(seq!==mtSeq)return;mtItems=r.files||[];mtIndex=0;mtOpen=true;$('#mentionBox').hidden=false;renderMentions(q)}catch{closeMentions()}}
|
|
559
|
+
function renderMentions(q){const box=$('#mentionBox');if(!box)return;if(!mtItems.length){box.innerHTML=`<div class="mention-empty">Eşleşen dosya yok${q?` ("${esc(q)}")`:''} · ${esc(activeWorkingPath()||'klasör yok')}</div>`;return}box.innerHTML=mtItems.map((f,i)=>`<div class="mention-item${i===mtIndex?' active':''}" data-i="${i}" onmousedown="event.preventDefault();pickMention(${i})"><span class="mi-ico">${ICN.file}</span><span class="mi-name">${esc(f.name)}</span><span class="mi-rel" title="${esc(f.rel)}">${esc(f.rel)}</span></div>`).join('')}
|
|
560
|
+
function onPromptKeydown(e){if(!mtOpen)return;if(e.key==='ArrowDown'){e.preventDefault();mtIndex=Math.min(mtItems.length-1,mtIndex+1);renderMentions()}else if(e.key==='ArrowUp'){e.preventDefault();mtIndex=Math.max(0,mtIndex-1);renderMentions()}else if((e.key==='Enter'||e.key==='Tab')&&mtItems.length){e.preventDefault();pickMention(mtIndex)}else if(e.key==='Escape'){e.preventDefault();closeMentions()}}
|
|
561
|
+
function pickMention(i){const f=mtItems[i];if(!f){closeMentions();return}const ta=$('#prompt');const pos=ta.selectionStart;const before=ta.value.slice(0,mtStart),after=ta.value.slice(pos),token='@'+f.rel+' ';ta.value=before+token+after;const caret=(before+token).length;ta.setSelectionRange(caret,caret);ta.focus();addServerAttachment({path:f.path,name:f.name});closeMentions()}
|
|
562
|
+
function closeMentions(){mtOpen=false;mtItems=[];const box=$('#mentionBox');if(box){box.hidden=true;box.innerHTML=''}}
|
|
563
|
+
async function addTask(){const prompt=$('#prompt').value.trim(),operatorCli=$('#taskOperator').value,targetDir=$('#targetDir').value.trim(),executionMode=$('#executionMode').value;if(!prompt)return;ensureNotifyPermission();const attachments=pendingAttachments.map(a=>a.srcPath?({name:a.name,srcPath:a.srcPath}):({name:a.name,dataBase64:a.dataBase64}));try{const t=await api('/api/tasks','POST',{prompt,operatorCli,executionMode,targetDir:targetDir||undefined,attachments:attachments.length?attachments:undefined,projectId:selectedProjectId||undefined});$('#prompt').value='';clearAttachments();showToast('ok','Görev kuyruğa eklendi',(t&&t.attachmentWarning)?t.attachmentWarning:'Operatör sıraya alındığında çalışmaya başlayacak.')}catch(e){showToast('err','Görev eklenemedi',e.message)}}
|
|
564
|
+
|
|
565
|
+
/* ---- Kuyruk ---- */
|
|
566
|
+
function deliveryHtml(t){const d=t.delivery;if(!d)return '';const labels={created:'eklendi',modified:'değişti',deleted:'silindi'};return `<div class="delivery"><div class="muted">📁 ${esc(d.location||'')}</div><div class="muted">⚡ ${(d.mode||'').toUpperCase()} · ${d.rounds||0} tur · ${(d.agents||[]).join(', ')}</div>${(d.files||[]).map(f=>`<div class="file-change"><span class="${esc(f.action)}">${esc(labels[f.action]||f.action)}</span><code>${esc(f.path)}</code></div>`).join('')||'<div class="muted">Dosya değişikliği yok.</div>'}${d.verification?`<div class="muted" style="margin-top:5px">✓ ${esc(d.verification)}</div>`:''}${(d.warnings||[]).map(w=>`<div style="margin-top:5px;color:var(--warn)">⚠ ${esc(w)}</div>`).join('')}${usageHtml(t)}</div>`}
|
|
567
|
+
/* Maliyet dökümü. reportingCalls < calls ise bazı CLI'lar telemetri vermemiştir; bunu
|
|
568
|
+
gizlemek yerine söyleriz, yoksa kullanıcı eksik toplamı gerçek sanır. */
|
|
569
|
+
function usageHtml(t){const u=t.usage;if(!u||!u.total)return '';const tt=u.total,rows=Object.entries(u.byAgent||{});const missing=(u.calls||0)-(u.reportingCalls||0);return `<div class="usage-box"><div class="usage-head"><span>${fmtTokens((tt.input||0)+(tt.output||0))} token</span><span class="usage-cost">${fmtCost(tt.cost)}</span></div><div class="usage-detail">${(tt.input||0).toLocaleString('tr')} girdi · ${(tt.output||0).toLocaleString('tr')} çıktı${tt.reasoning?` · ${tt.reasoning.toLocaleString('tr')} akıl yürütme`:''}${tt.cacheRead?` · ${tt.cacheRead.toLocaleString('tr')} önbellek`:''}</div>${rows.length>1?`<div class="usage-agents">${rows.map(([name,a])=>`<div><span>${esc(name)}</span><span>${fmtTokens((a.input||0)+(a.output||0))} · ${fmtCost(a.cost)}</span></div>`).join('')}</div>`:''}${missing>0?`<div class="usage-note">${missing} çağrı telemetri bildirmedi; toplam eksik olabilir.</div>`:''}</div>`}
|
|
570
|
+
/* Başarısız görev kartı: kod + ne olduğu + ne yapılacağı görünür, ham çıktı katlanmış durur. */
|
|
571
|
+
function failureHtml(t){const f=t.failure||{},title=f.summary||String(t.error||'Görev tamamlanamadı');return `<div class="failure-box"><div class="fb-title">${f.code?`<span class="fb-code">${esc(f.code)}</span>`:''}<span>${esc(title)}</span></div>${f.action?`<div class="fb-action">→ ${esc(f.action)}</div>`:''}${f.raw?`<details><summary>Ham CLI çıktısı</summary><pre>${esc(f.raw)}</pre></details>`:''}</div>`}
|
|
572
|
+
function taskCard(t,state){const actions=state==='approval',done=state==='done',pending=state==='pending',editable=pending&&t.kind!=='operator-chat';const opLabel=t.operatorCli?cliLabel(t.operatorCli):'';return `<div class="task task--${esc(state)}"><div class="row"><span class="id">${esc(t.id)}</span><div class="grow"></div><span class="badge">${esc(t.executionMode||'auto')}</span><span class="badge">${esc(opLabel)}</span>${(()=>{const p=t.projectId&&(STATE.projects||[]).find(x=>x.id===t.projectId);return p?`<span class="badge badge--ic" title="Proje: ${esc(p.path)}">${ICN.project}${esc(p.name)}</span>`:''})()}${t.attachments&&t.attachments.length?`<span class="badge badge--ic" title="${esc(t.attachments.map(a=>a.name).join(', '))}">${ICN.clip}${t.attachments.length}</span>`:''}</div><div class="prompt" onclick="this.classList.toggle('expanded')" title="Tümünü göster/gizle">${esc(t.prompt)}</div>${t.summary?`<details ${(t.delivery&&(t.delivery.files||[]).length)?'':'open'}><summary>Kısa teslimat özeti</summary><pre>${esc(t.summary)}</pre>${deliveryHtml(t)}</details>`:''}${state==='failed'?failureHtml(t):''}${t.planPreview?`<details><summary>Takım planı</summary><pre>${esc(t.planPreview)}</pre></details>`:''}<div class="row" style="margin-top:6px">${actions?`<button class="ok sm" onclick="approve('${t.id}')">Onayla</button><button class="danger sm" onclick="rejectTask('${t.id}')">Reddet</button>`:`<button class="sm" onclick="inspectRun('${t.id}')">Akışı incele</button>${(done||state==='failed')&&(t.delivery&&(t.delivery.files||[]).length)?`<button class="sm" onclick="location.href='code.html?task=${t.id}'" title="Bu görevin kod değişikliklerini satır satır gör">Kodu gör</button>`:''}${(done||state==='failed')&&t.checkpointId?`<button class="danger sm" onclick="restoreVersion('${t.id}')" title="Bu görev öncesindeki kod sürümüne dön"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>Bu sürüme dön</button>`:''}${editable?`<button class="sm" onclick="openEdit('${t.id}')">Düzenle</button><button class="danger sm" onclick="deleteTask('${t.id}')">Sil</button>`:''}${done?`<button class="primary sm" onclick="openChat('${t.id}')">Operatöre Sor</button>`:''}`}</div></div>`}
|
|
573
|
+
function renderQueue(q){STATE.queue=q;renderQueueScoped()}
|
|
574
|
+
// Aktif proje seçiliyse tüm görev listeleri o projeye kapsanır (proje detayı = o projenin görevleri).
|
|
575
|
+
function renderQueueScoped(){const q=STATE.queue||{pending:[],approval:[],done:[],failed:[]};const f=l=>selectedProjectId?(l||[]).filter(t=>t.projectId===selectedProjectId):(l||[]);const pending=f(q.pending),approval=f(q.approval),done=f(q.done),failed=f(q.failed);
|
|
576
|
+
$('#pending').innerHTML=pending.length?pending.map(t=>taskCard(t,'pending')).join(''):`<div class="empty">${selectedProjectId?'Bu projede bekleyen görev yok.':'Bekleyen görev yok.'}</div>`;
|
|
577
|
+
$('#approvalCard').hidden=!approval.length;$('#approval').innerHTML=approval.map(t=>taskCard(t,'approval')).join('');
|
|
578
|
+
$('#done').innerHTML=done.length?done.map(t=>taskCard(t,'done')).join(''):'<span class="muted">Henüz tamamlanan görev yok.</span>';
|
|
579
|
+
$('#failed').innerHTML=failed.length?failed.map(t=>taskCard(t,'failed')).join(''):'<span class="muted">Başarısız görev yok.</span>';
|
|
580
|
+
$('#pendingCount').textContent=`${pending.length} bekleyen`;$('#doneCount').textContent=done.length;$('#failedCount').textContent=failed.length;$('#historyCount').textContent=`${done.length+failed.length} görev`;
|
|
581
|
+
renderQueueScope()}
|
|
582
|
+
function renderQueueScope(){const box=$('#queueScope');if(!box)return;const p=(STATE.projects||[]).find(x=>x.id===selectedProjectId);if(!p){box.hidden=true;return}box.hidden=false;box.innerHTML=`<span class="qs-ico">${ICN.project}</span><span class="qs-name">${esc(p.name)}</span><span class="qs-path" title="${esc(p.path)}">${esc(p.path)}</span><button class="qs-clear" onclick="clearProjectScope()">Tüm görevler</button>`}
|
|
583
|
+
function clearProjectScope(){selectedProjectId='';persistActiveProject();renderProjectSelect();renderQueueScoped()}
|
|
584
|
+
async function approve(id){try{await api(`/api/tasks/${id}/approve`,'POST')}catch(e){alert(e.message)}}
|
|
585
|
+
async function rejectTask(id){try{await api(`/api/tasks/${id}/reject`,'POST')}catch(e){alert(e.message)}}
|
|
586
|
+
async function deleteTask(id){if(!await appConfirm({title:'Görev silinsin mi?',body:'Bu bekleyen görev kuyruktan kaldırılacak.',confirmLabel:'Sil',cancelLabel:'Vazgeç',danger:true}))return;try{await api(`/api/tasks/${id}`,'DELETE')}catch(e){alert(e.message)}}
|
|
587
|
+
async function restoreVersion(id){if(!await appConfirm({title:'Bu sürüme dönülsün mü?',body:'Bu görev çalışmadan önceki koda dönülür:\n• Görev sonrası oluşan dosyalar silinir\n• Değiştirilen/silinen dosyalar eski haline döner\n\nGeri alma işleminin kendisi de bir sürüm oluşturur, yani geri alınabilir.',confirmLabel:'Sürüme dön',cancelLabel:'Vazgeç',danger:true}))return;try{const r=await api(`/api/tasks/${id}/restore`,'POST');showToast('ok','Sürüm geri yüklendi',`${r.restored} dosya geri yazıldı · ${r.deleted} sonradan oluşan dosya silindi`)}catch(e){showToast('err','Geri yüklenemedi',e.message)}}
|
|
588
|
+
/* ---- Klasör seçici ---- */
|
|
589
|
+
let fsTarget=null,fsCurrent='',fsParent=null,fsEntries=[],fsSelected='',fsSelectedType='',fsHistory=[],fsHistoryIndex=-1,fsMode='dir',fsView=false;
|
|
590
|
+
async function openFs(setter,start,opts){opts=opts||{};fsTarget=setter;fsMode=opts.files?'file':'dir';fsView=!!opts.view;fsHistory=[];fsHistoryIndex=-1;fsSelected='';fsSelectedType='';$('#fsState').textContent='';$('#fsState').style.color='';
|
|
591
|
+
if($('#fsTitle'))$('#fsTitle').textContent=opts.title||(fsMode==='file'?(fsView?'Proje dosyaları':'Dosya seç'):'Çalışma klasörü seç');
|
|
592
|
+
const pickBtn=$('#fsPickBtn');if(pickBtn){pickBtn.hidden=fsView;pickBtn.textContent=opts.pickLabel||(fsMode==='file'?'Dosyayı ekle':'Klasör seç')}
|
|
593
|
+
$('#fsModal').classList.add('open');await fsGo(start&&start.trim()?start.trim():'')}
|
|
594
|
+
function closeFs(){$('#fsModal').classList.remove('open');fsTarget=null;fsEndAddress(false)}
|
|
595
|
+
function fsDate(value){if(!value)return '—';try{return new Date(value).toLocaleString('tr-TR',{dateStyle:'short',timeStyle:'short'})}catch{return '—'}}
|
|
596
|
+
function fsCrumbs(){const box=$('#fsBreadcrumbs'),items=[{label:'Bu bilgisayar',path:''}];if(fsCurrent){const win=/^[A-Za-z]:[\\/]/.test(fsCurrent),sep=win?'\\':'/',parts=fsCurrent.replace(/[\\/]+/g,sep).split(sep).filter(Boolean);let acc=win?(parts.shift()+sep):sep;if(win)items.push({label:acc.slice(0,2),path:acc});for(const part of parts){acc=(acc.endsWith(sep)?acc:acc+sep)+part;items.push({label:part,path:acc})}}
|
|
597
|
+
box.innerHTML=items.map((item,i)=>`${i?'<span class="fs-sep">›</span>':''}<button class="fs-crumb" data-path="${esc(item.path)}">${esc(item.label)}</button>`).join('');box.querySelectorAll('.fs-crumb').forEach(b=>b.onclick=()=>fsGo(b.dataset.path));$('#fsPath').value=fsCurrent||''}
|
|
598
|
+
function fsSidebar(places,drives){const placeBox=$('#fsPlaces'),driveBox=$('#fsDrives');placeBox.innerHTML=(places||[]).map((p,i)=>`<button class="fs-place" data-i="${i}"><span class="ico">${esc(p.icon||'◇')}</span><span>${esc(p.label)}</span></button>`).join('');placeBox.querySelectorAll('.fs-place').forEach(b=>{const p=places[+b.dataset.i];b.classList.toggle('active',p.path===fsCurrent);b.onclick=()=>fsGo(p.path)});driveBox.innerHTML=(drives||[]).map((d,i)=>`<button class="fs-place drive" data-i="${i}"><span class="ico">${ICN.drive}</span><span>Yerel Disk (${esc(String(d).slice(0,2))})</span></button>`).join('');driveBox.querySelectorAll('.fs-place').forEach(b=>b.onclick=()=>fsGo(drives[+b.dataset.i]))}
|
|
599
|
+
function fsSelect(index){const entry=fsEntries[index];if(!entry)return;fsSelected=entry.path;fsSelectedType=entry.type;document.querySelectorAll('#fsList .fs-item').forEach((row,i)=>row.classList.toggle('selected',i===index));$('#fsSelection').textContent=entry.path;const btn=$('#fsPickBtn');if(btn&&fsMode==='file')btn.textContent=entry.type==='file'?'Dosyayı ekle':'Klasör seç'}
|
|
600
|
+
function fsRender(r){fsEntries=r.entries||[];fsCrumbs();fsSidebar(r.places||[],r.drives||[]);$('#fsCount').textContent=`${fsEntries.length} öğe`;$('#fsSelection').textContent=fsCurrent||'Bu bilgisayar';$('#fsBackBtn').disabled=fsHistoryIndex<=0;$('#fsUpBtn').disabled=(fsParent===null&&!fsCurrent);const list=$('#fsList');if(!fsEntries.length)list.innerHTML=`<div class="fs-empty">${fsMode==='file'?'Bu klasörde öğe yok.':'Bu klasörde alt klasör yok.<br>Mevcut klasörü seçebilirsiniz.'}</div>`;else{list.innerHTML=fsEntries.map((entry,i)=>{const isFile=entry.type==='file';const icon=entry.type==='drive'?ICN.drive:(isFile?ICN.file:ICN.folder);const typ=entry.type==='drive'?'Yerel Disk':(isFile?((entry.name.split('.').pop()||'').toUpperCase()+' dosyası'):'Dosya klasörü');const meta=isFile&&entry.size!=null?' · '+fmtSize(entry.size):'';return `<button class="fs-item${isFile?' isfile':''}" data-i="${i}" title="${isFile?'Seçmek için tıklayın':'Açmak için çift tıklayın'}"><span class="fname"><span class="folder-icon ${isFile?'fi-file':'fi-dir'}">${icon}</span><b>${esc(entry.name)}</b></span><span class="date">${esc(fsDate(entry.modifiedAt))}</span><span class="type">${esc(typ)}${meta}</span></button>`}).join('');list.querySelectorAll('.fs-item').forEach(row=>{const i=+row.dataset.i;const e=fsEntries[i];row.onclick=()=>fsSelect(i);row.ondblclick=()=>{if(e.type==='file'){if(fsMode==='file'&&!fsView){fsSelect(i);fsPick()}}else fsGo(e.path)}})}}
|
|
601
|
+
async function fsGo(pathArg,push=true){try{$('#fsState').style.color='';$('#fsState').textContent='Yükleniyor…';fsSelected='';fsSelectedType='';const p=pathArg?String(pathArg):'';let q='';if(p){q='?path='+encodeURIComponent(p);if(fsMode==='file')q+='&files=1'}const r=await api('/api/fs'+q);if(r.error&&!r.entries)throw new Error(r.error);fsCurrent=r.path||'';fsParent=r.parent;if(push){fsHistory=fsHistory.slice(0,fsHistoryIndex+1);if(fsHistory[fsHistory.length-1]!==fsCurrent)fsHistory.push(fsCurrent);fsHistoryIndex=fsHistory.length-1}fsRender(r);$('#fsState').textContent=r.warning||'';$('#fsState').style.color=r.warning?'var(--warn)':''}catch(e){$('#fsState').textContent=e.message;$('#fsState').style.color='var(--err)'}}
|
|
602
|
+
function fsBack(){if(fsHistoryIndex>0){fsHistoryIndex--;fsGo(fsHistory[fsHistoryIndex],false)}}
|
|
603
|
+
function fsUp(){if(fsParent!==null)fsGo(fsParent||'')}
|
|
604
|
+
function fsEditAddress(){$('#fsAddress').classList.add('editing');$('#fsPath').value=fsCurrent;$('#fsPath').focus();$('#fsPath').select()}
|
|
605
|
+
function fsEndAddress(navigate){const address=$('#fsAddress');if(!address)return;if(navigate){const value=$('#fsPath').value.trim();address.classList.remove('editing');fsGo(value)}else{address.classList.remove('editing');$('#fsPath').value=fsCurrent}}
|
|
606
|
+
function fsAddressKey(event){if(event.key==='Enter')fsEndAddress(true);else if(event.key==='Escape')fsEndAddress(false)}
|
|
607
|
+
function fsPick(){if(!fsTarget)return;if(fsMode==='file'){if(fsSelectedType!=='file'){$('#fsState').textContent='Önce bir dosya seçin.';$('#fsState').style.color='var(--warn)';return}fsTarget({path:fsSelected,name:(fsSelected.split(/[\\/]/).pop()||'dosya')});closeFs();return}const picked=fsSelected||fsCurrent;if(!picked){$('#fsState').textContent='Önce bir sürücü veya klasör seçin.';return}fsTarget(picked);closeFs()}
|
|
608
|
+
let editId=null;
|
|
609
|
+
function openEdit(id){const t=(STATE.queue.pending||[]).find(x=>x.id===id);if(!t){alert('Görev artık bekleyende değil (çalışmaya başlamış olabilir).');return}editId=id;$('#editPrompt').value=t.prompt||'';fillOperatorSelect($('#editOperator'),t.operatorCli);$('#editMode').value=t.executionMode||'auto';$('#editDir').value=t.targetDir||'';$('#editState').textContent='';$('#editModal').classList.add('open')}
|
|
610
|
+
function closeEdit(){$('#editModal').classList.remove('open');editId=null}
|
|
611
|
+
async function saveEdit(){if(!editId)return;const prompt=$('#editPrompt').value.trim();if(!prompt){$('#editState').textContent='Hedef boş olamaz.';return}try{await api(`/api/tasks/${editId}`,'PUT',{prompt,operatorCli:$('#editOperator').value,executionMode:$('#editMode').value,targetDir:$('#editDir').value.trim()});showToast('ok','Görev güncellendi');closeEdit()}catch(e){$('#editState').textContent=e.message;$('#editState').style.color='var(--err)'}}
|
|
612
|
+
|
|
613
|
+
/* ---- Ekip haritasi + terminaller ---- */
|
|
614
|
+
function updateMissionStats(){$('#missionStats').textContent=`${missionMessages} mesaj · ${missionCalls} çağrı`}
|
|
615
|
+
function startMission(e){callCards.clear();teamNodes.clear();missionCalls=0;missionMessages=0;liveStep=0;$('#agentGrid').innerHTML='<div class="empty">Operatör ekip planını hazırlıyor…</div>';$('#feed').innerHTML='<div class="empty">Operatörün ilk adımı bekleniyor.</div>';$('#teamMap').innerHTML='';$('#missionTitle').textContent=String(e.msg||'').replace(/^GOREV\s+[^:]+:\s*/, '').slice(0,120)||'Yeni takım görevi';$('#missionSub').textContent='Operatör hedefi analiz ediyor ve uygun uzmanları seçiyor.';setMissionStatus('running','Çalışıyor');updateMissionStats()}
|
|
616
|
+
function ensureTeamNode(name,role){if(!name)return null;if(teamNodes.has(name))return teamNodes.get(name);if($('#teamMap .empty'))$('#teamMap').innerHTML='';if(teamNodes.size){const link=document.createElement('div');link.className='team-link';$('#teamMap').appendChild(link)}const node=document.createElement('div'),glyph=role==='operator'?'OP':String(name).replace(/[^a-zA-Z0-9]/g,'').slice(0,2).toUpperCase()||'AI';node.className='team-node '+(role==='operator'?'operator':'');node.style.setProperty('--nacc',agentColor(name));node.innerHTML=`<div class="node-orb"><span class="node-glyph">${esc(glyph)}</span><span class="node-status"></span></div><div class="node-info"><div class="nrole">${role==='operator'?'Takım Operatörü':'Uzman Agent'}</div><div class="nname">${esc(name)}</div><div class="nstate">hazır</div></div>`;$('#teamMap').appendChild(node);const item={node,state:node.querySelector('.nstate')};teamNodes.set(name,item);return item}
|
|
617
|
+
function setTeamState(name,state,text,role){const n=ensureTeamNode(name,role);if(!n)return;n.node.classList.remove('working','done','failed');if(state)n.node.classList.add(state);n.state.textContent=text}
|
|
618
|
+
function clearLive(){callCards.clear();teamNodes.clear();missionCalls=0;missionMessages=0;liveStep=0;$('#agentGrid').innerHTML='<div class="empty">Canlı akış temizlendi.</div>';$('#feed').innerHTML='<div class="empty">Henüz etkinlik yok.</div>';$('#teamMap').innerHTML='<div class="empty" style="width:100%">Henüz aktif ekip yok.</div>';$('#missionTitle').textContent='Takım görevi bekleniyor';$('#missionSub').textContent='Operatör bir hedef aldığında ekibini burada kuracak.';setMissionStatus(null);updateMissionStats()}
|
|
619
|
+
function ensureCall(e){if(callCards.has(e.callId))return callCards.get(e.callId);if($('#agentGrid .empty'))$('#agentGrid').innerHTML='';const step=++liveStep,isOp=String(e.stage||'').startsWith('operator'),glyph=isOp?'OP':String(e.agent||'AI').replace(/[^a-zA-Z0-9]/g,'').slice(0,2).toUpperCase()||'AI';const box=document.createElement('div');box.className='agent active'+(isOp?' operatorcall':'');box.style.setProperty('--agent-accent',agentColor(e.agent));box.dataset.step=step;box.innerHTML=`<div class="agent-head"><div class="agent-avatar">${esc(glyph)}</div><div class="agent-identity"><div class="agent-kicker"><span class="agent-step">RUN ${String(step).padStart(2,'0')}</span><span class="agent-role">${isOp?'operatör':'uzman'} · ${esc(e.stage||'')}</span></div><div class="agent-name">${esc(e.agent)}</div></div><div class="agent-state"><span class="state-pulse"></span><span class="agent-state-text">başlıyor</span></div></div><div class="agent-progress"><span></span></div><div class="friendly-error" hidden></div><details class="terminal-wrap" open><summary>CLI çalışma kaydı</summary><pre class="terminal"></pre></details>`;$('#agentGrid').appendChild(box);box.scrollIntoView({block:'nearest',behavior:'smooth'});const item={box,term:box.querySelector('.terminal'),state:box.querySelector('.agent-state-text'),friendly:box.querySelector('.friendly-error'),details:box.querySelector('.terminal-wrap'),step,raw:'',stdout:'',stderr:'',cmd:''};callCards.set(e.callId,item);return item}
|
|
620
|
+
/* Ham CLI çıktısını teşhise çeviren tablo. engine.js'teki classifyCliError ile AYNI kodları ve
|
|
621
|
+
aynı sırayı kullanır: kota bitmesi ile hız sınırı, oturum sorunu ile kurulum sorunu ayrı
|
|
622
|
+
teşhislerdir — kullanıcının "limitim mi bitti, CLI mı açılmadı" sorusu tam olarak budur.
|
|
623
|
+
Sıra önemli: /not found/ gibi geniş desenler kimlik kontrollerinin ARKASINDA kalmalı. */
|
|
624
|
+
/* Oturum/kurulum tavsiyesi CLI'ya gore degisir. Sabit tek metin (Gemini'ninki) opencode
|
|
625
|
+
hatasinda yanlis komut oneriyordu. Adapter, process.started olayindaki cmd'den turetilir;
|
|
626
|
+
src/cli-registry.js adapterId + AUTH_HINTS ile AYNI mantik. */
|
|
627
|
+
function adapterOf(cmd){const b=String(cmd||'').split(/[\\/]/).pop().toLowerCase().replace(/\.(cmd|bat|exe|ps1)$/,'');if(b.includes('codex'))return 'codex';if(b.includes('claude'))return 'claude';if(b.includes('gemini'))return 'gemini';if(b.includes('opencode'))return 'opencode';return 'custom'}
|
|
628
|
+
const AUTH_HINTS={
|
|
629
|
+
codex:'Terminalde `codex login` çalıştırın (veya OPENAI_API_KEY tanımlayın).',
|
|
630
|
+
claude:'Terminalde `claude` çalıştırıp oturum açın (veya ANTHROPIC_API_KEY tanımlayın).',
|
|
631
|
+
gemini:'Terminalde `gemini` çalıştırıp "Login with Google" seçin (veya GEMINI_API_KEY tanımlayın).',
|
|
632
|
+
opencode:'Terminalde `opencode auth login` çalıştırıp sağlayıcıyı seçin. "401 No provider available" = seçili modelin sağlayıcısında geçerli kimlik yok.',
|
|
633
|
+
};
|
|
634
|
+
const authHint=a=>AUTH_HINTS[a]||'Bu CLI\'nin kendi oturum açma komutunu terminalde çalıştırın.';
|
|
635
|
+
const INSTALL_HINTS={
|
|
636
|
+
codex:'`npm install -g @openai/codex`',
|
|
637
|
+
claude:'`npm install -g @anthropic-ai/claude-code`',
|
|
638
|
+
gemini:'`npm install -g @google/gemini-cli`',
|
|
639
|
+
opencode:'`npm install -g opencode-ai`',
|
|
640
|
+
};
|
|
641
|
+
const FAILURE_RULES=[
|
|
642
|
+
[/CLI_STALLED|çıktı üretmedi|cikti uretmedi/i,'uzun süre sessiz kaldı','CLI bir süre ilerleme ürettikten sonra uzun süre yeni çıktı vermediği için otomatik durduruldu.','Önceki çalışma kayıtları korundu; operatör bu oturumda alternatif bir agente geçecek.'],
|
|
643
|
+
/* "No provider available" 401 ile gelir ama oturum sorunu DEĞİLDİR: seçili model hiçbir
|
|
644
|
+
sağlayıcıya çözülemiyordur. Genel 401/403 kuralından ÖNCE gelmeli. */
|
|
645
|
+
[/no provider available/i,'modeli çözümleyemedi','Seçili model hiçbir yapılandırılmış sağlayıcıya çözümlenemedi — bu bir oturum sorunu değil.','Ayarlar → Agent\'lar bölümünde bu agent için açık bir model seçin. Model boşken OpenCode kendi son kullandığı modele düşer; o model erişilemezse görev bu hatayla durur.'],
|
|
646
|
+
[/API key not valid|API_KEY_INVALID|invalid[_ ]api[_ ]key|incorrect api key/i,'API anahtarını reddetti','Anahtar geçersiz ya da bu model için yetkili değil.',a=>`Ayarlar → Agent'lar bölümünden anahtarı yenileyin. ${authHint(a)}`],
|
|
647
|
+
[/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,'oturum açmamış','CLI kimlik doğrulaması yapılmadan çalıştırıldı; bu bir kurulum değil oturum sorunudur.',a=>authHint(a)],
|
|
648
|
+
[/RESOURCE_EXHAUSTED|quota exceeded|exceeded your current quota|daily limit|günlük limit|out of credits|insufficient (credit|balance|quota)|billing/i,'kotası doldu','Günlük/aylık kullanım hakkınız veya bakiyeniz bitti — beklemek tek başına çözmez.','Kota yenilenene kadar başka bir agent seçin, planı yükseltin veya kotası olan bir hesabın anahtarını kullanın.'],
|
|
649
|
+
[/rate.?limit|too many requests|\b429\b/i,'hız sınırına takıldı','Kotanız bitmedi; çok kısa sürede çok fazla istek gönderildi.','Birkaç dakika bekleyip tekrar deneyin; operatör bu turda alternatif bir uzman kullanabilir.'],
|
|
650
|
+
[/overloaded|\bUNAVAILABLE\b|\b(503|500)\b|internal error|try again later/i,'sağlayıcı hatası aldı','Model sağlayıcısı geçici olarak aşırı yüklü ya da hata döndürdü.','Sizin tarafınızda bir sorun yok; birkaç dakika sonra tekrar deneyin.'],
|
|
651
|
+
[/location is not supported|not available in your country|region.*not supported/i,'bölge kısıtına takıldı','Seçilen model bulunduğunuz bölgede kullanıma kapalı.','Desteklenen bir bölge/hesap kullanın veya başka sağlayıcının agent\'ını seçin.'],
|
|
652
|
+
[/ENOTFOUND|EAI_AGAIN|ECONNRESET|ETIMEDOUT|getaddrinfo|fetch failed|socket hang up|network (error|unreachable)|ECONNREFUSED|proxy/i,'ağa bağlanamadı','DNS, proxy veya internet bağlantısı nedeniyle sağlayıcıya ulaşılamadı.','Bağlantınızı ve varsa kurumsal proxy ayarlarınızı kontrol edin.'],
|
|
653
|
+
[/not recognized as an internal|is not recognized|command not found|spawn .*ENOENT|\bENOENT\b|no such file or directory/i,'başlatılamadı','CLI komutu bulunamadı; kurulu değil veya PATH üzerinde görünmüyor.',a=>`${INSTALL_HINTS[a]?`Kurulum: ${INSTALL_HINTS[a]}. `:''}Ayarlar → Agent'lar bölümünde komut adını doğrulayın.`],
|
|
654
|
+
[/timeout|timed out|zaman asim|zaman aşım/i,'zaman aşımına uğradı','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.'],
|
|
655
|
+
];
|
|
656
|
+
function friendlyFailure(text,agent,cmd){const adapter=adapterOf(cmd);for(const [re,title,body,action] of FAILURE_RULES){if(re.test(text))return {title:`${agent} ${title}`,body,action:typeof action==='function'?action(adapter):action}}return {title:`${agent} görevi tamamlayamadı`,body:'CLI sıfırdan farklı çıkış koduyla kapandı.',action:'Ham çıktı aşağıdaki “CLI çalışma kaydı” bölümünde saklandı; nedeni orada görebilirsiniz.'}}
|
|
657
|
+
function parseOperatorView(raw){try{let text=raw.trim(),m=text.match(/```(?:json)?\s*([\s\S]*?)```/i);if(m)text=m[1];const data=JSON.parse(text);if(Array.isArray(data.assignments)){const names=[...new Set(data.assignments.map(x=>x.agent))];return {title:data.status==='continue'?'Operatör yeni ekip turu oluşturdu':'Takım planı hazır',body:`${data.assignments.length} delegasyon · ${(data.completionCriteria||[]).length} kabul kriteri`,pills:names}}if(data.status==='complete')return {title:'Operatör görevi tamamladı',body:String(data.final||'Nihai sonuç hazır.'),pills:[]}}catch{}return null}
|
|
658
|
+
function activity(e){const c=ensureCall(e);const role=String(e.stage||'').startsWith('operator')?'operator':'agent';if(e.kind==='stdout'||e.kind==='stderr'){const span=document.createElement('span');if(e.kind==='stderr')span.className='stderr';span.textContent=e.text;c.term.appendChild(span);c.raw+=e.text;c[e.kind]+=e.text;c.term.scrollTop=c.term.scrollHeight;c.state.textContent='çalışıyor';setTeamState(e.agent,'working','çalışıyor',role)}else if(e.kind==='process.progress'){const sec=Math.max(1,Math.round((e.elapsedMs||0)/1000));c.state.textContent=`yanıt bekleniyor · ${sec} sn`;setTeamState(e.agent,'working',`${sec} sn · yanıt bekleniyor`,role)}else if(e.kind==='process.finished'){c.box.classList.remove('active');const failed=Number(e.exitCode)!==0,stalled=e.reason==='silence-timeout';const dur=(e.durationMs/1000).toFixed(1);c.state.textContent=stalled?`yanıt vermedi · ${dur}s`:failed?`başarısız · ${dur}s`:`tamamlandı · ${dur}s${e.usage?` · ${usageLine(e.usage)}`:''}`;setTeamState(e.agent,failed?'failed':'done',stalled?'otomatik durduruldu':failed?'erişilemiyor':'teslim etti',role);if(failed){c.box.classList.add('error');const f=friendlyFailure(stalled?'CLI_STALLED':c.raw,e.agent,c.cmd);c.friendly.hidden=false;c.friendly.innerHTML=`<b>${esc(f.title)}</b>${esc(f.body)}<br><small>${esc(f.action)}</small>`;c.details.open=false}else if(role==='operator'){const view=parseOperatorView(c.stdout||c.raw);if(view){c.friendly.hidden=false;c.friendly.classList.add('success');c.friendly.innerHTML=`<b>${esc(view.title)}</b>${esc(view.body)}${view.pills.length?`<div class="plan-pills">${view.pills.map(x=>`<span>${esc(x)}</span>`).join('')}</div>`:''}`;c.details.open=false}}else{const clean=String(c.stdout||'').replace(/tokens used[\s\S]*$/i,'').trim();c.friendly.hidden=false;c.friendly.classList.add('success');c.friendly.innerHTML=`<b>Teslimat operatöre gönderildi</b>${esc(clean.slice(0,420)||'Agent görevini başarıyla tamamladı.')}<br><small>Komutlar ve ayrıntılı çıktı “Canlı CLI çıktısı” bölümünde saklandı.</small>`;c.details.open=false}}else if(e.kind==='process.failed'||e.kind==='process.timeout'||e.kind==='process.silence-timeout'){c.box.classList.remove('active');c.box.classList.add('error');c.state.textContent=e.kind==='process.silence-timeout'?'yanıt vermedi · durduruldu':'hata';setTeamState(e.agent,'failed',e.kind==='process.silence-timeout'?'otomatik durduruldu':'hata',role)}else if(e.kind==='process.started'){missionCalls++;updateMissionStats();c.cmd=e.cmd||'';c.state.textContent='başlatıldı';setTeamState(e.agent,'working',role==='operator'?'ekibi yönetiyor':'görevi yürütüyor',role)}}
|
|
659
|
+
|
|
660
|
+
/* ---- Birlesik akis: mesajlar + sistem olaylari ---- */
|
|
661
|
+
function feedReady(){const feed=$('#feed');if(feed.querySelector('.empty'))feed.innerHTML='';return feed}
|
|
662
|
+
function message(e){missionMessages++;updateMissionStats();const feed=feedReady();ensureTeamNode(e.from,e.from===STATE.status.defaultOperator?'operator':'agent');ensureTeamNode(e.to,e.to===STATE.status.defaultOperator?'operator':'agent');document.querySelectorAll('.team-link').forEach(x=>{x.classList.add('active');setTimeout(()=>x.classList.remove('active'),1500)});const mt=e.messageType||'',plain=mt!=='failure'&&mt!=='blocked';const typeClass=mt==='failure'?'fi-failure':mt==='blocked'?'fi-blocked':mt==='result'?'fi-result':'fi-delegation';const ico=mt==='failure'?'✕':mt==='blocked'?'⊘':mt==='result'?'✓':'⇢';const col=plain?agentColor(e.from):'';const body=String(e.body||'');const long=body.length>300||(body.match(/\n/g)||[]).length>4;const div=document.createElement('div');div.className='feed-item '+typeClass;div.innerHTML=`<span class="fi-dot">${ico}</span><div class="fi-main"><div class="fi-head"><span class="fi-from"${plain&&col?` style="color:${col}"`:''}>${esc(e.from)}</span><span class="fi-arrow">→</span><span class="fi-to">${esc(e.to)}</span>${e.assignmentId?`<span class="fi-tag">${esc(e.assignmentId)}</span>`:''}<time>${time(e.at)}</time></div><div class="fi-body${long?' clamp':''}">${esc(body)}</div>${long?`<button class="fi-more" onclick="toggleFeedBody(this)">Devamını göster</button>`:''}</div>`;feed.prepend(div)}
|
|
663
|
+
function toggleFeedBody(btn){const body=btn.previousElementSibling;if(!body)return;const clamped=body.classList.toggle('clamp');btn.textContent=clamped?'Devamını göster':'Gizle'}
|
|
664
|
+
function logEvent(e){if(e.level==='task')startMission(e);if(!replaying&&e.level==='error'&&/^HATA:/.test(String(e.msg||''))){const reason=String(e.msg).replace(/^HATA:\s*/,'').slice(0,180);missionFailed(reason);showToast('err','Görev başarısız',reason.slice(0,120))}const feed=feedReady();const lvl=e.level||'info';const ico={task:'▶',ok:'✓',error:'✕',warn:'!',stage:'→',info:'·'}[lvl]||'·';const d=document.createElement('div');d.className='feed-item fi-log '+lvl;d.innerHTML=`<span class="fi-dot">${ico}</span><div class="fi-main"><div class="fi-head"><span class="fi-msg">${esc(e.msg)}</span><time>${time(e.at)}</time></div></div>`;feed.prepend(d)}
|
|
665
|
+
function applyReplayOutcome(id){const done=(STATE.queue.done||[]).find(t=>t.id===id),failed=(STATE.queue.failed||[]).find(t=>t.id===id);if(done){const d=done.delivery||{},files=(d.files||[]).length;missionCompleted(`${done.operatorCli||'Operatör'} · ${d.rounds||0} tur · ${files} dosya değişikliği${d.verification?` · ✓ ${d.verification}`:''}${(d.warnings||[]).length?` · ${d.warnings.length} uyarı`:''}`,done.summary)}else if(failed)missionFailed(failed.failure?`${failed.failure.code} · ${failed.operatorCli||'Operatör'}`:String(failed.error||'Görev tamamlanamadı').slice(0,180),failed.failure)}
|
|
666
|
+
async function replayRun(id,{scroll=false}={}){const previousReplay=replaying;replaying=true;try{clearLive();const events=await api(`/api/tasks/${id}/events?limit=1500`);for(const e of events){if(e.type==='activity')activity(e);else if(e.type==='message')message(e);else if(e.type==='log')logEvent(e)}applyReplayOutcome(id);if(scroll){const mc=$('#missionCard');if(mc)mc.scrollIntoView({behavior:'smooth',block:'start'})}return events}finally{replaying=previousReplay}}
|
|
667
|
+
function latestDashboardRun(){if(STATE.status?.current?.id)return STATE.status.current;return [...(STATE.queue.done||[]),...(STATE.queue.failed||[])].sort((a,b)=>new Date(b.finishedAt||b.createdAt||0)-new Date(a.finishedAt||a.createdAt||0))[0]||null}
|
|
668
|
+
async function restoreDashboardRun(){const run=latestDashboardRun();if(!run?.id)return [];return replayRun(run.id)}
|
|
669
|
+
async function inspectRun(id){const nav=$('#navLoader'),lt=nav&&nav.querySelector('.lt'),shownAt=performance.now();
|
|
670
|
+
if(nav){if(lt)lt.textContent='Görev akışı yükleniyor…';nav.hidden=false}
|
|
671
|
+
const hideNav=()=>{if(nav){nav.hidden=true;if(lt)lt.textContent='Ekip Akışı açılıyor…'}};
|
|
672
|
+
// Emniyet: fetch herhangi bir nedenle asılırsa yükleyici en geç 15 sn'de kapansın.
|
|
673
|
+
const guard=setTimeout(()=>{hideNav();showToast('err','Akış yüklenemedi','Sunucu yanıt vermedi. Sayfayı yenileyip tekrar deneyin.')},15000);
|
|
674
|
+
try{await replayRun(id,{scroll:true})}
|
|
675
|
+
catch(e){showToast('err','Akış yüklenemedi',e.message)}
|
|
676
|
+
finally{clearTimeout(guard);const wait=Math.max(0,650-(performance.now()-shownAt));setTimeout(hideNav,wait)}}
|
|
677
|
+
|
|
678
|
+
/* ---- Sohbet ---- */
|
|
679
|
+
async function openChat(id){try{const found=await api(`/api/tasks/${id}`);chatParent=found.task;$('#chatState').textContent='';$('#chatModal').classList.add('open');renderChat()}catch(e){alert(e.message)}}
|
|
680
|
+
function closeChat(){$('#chatModal').classList.remove('open');chatParent=null}
|
|
681
|
+
function renderChat(){if(!chatParent)return;$('#chatTitle').textContent=`${chatParent.operatorAgent||'Operatör'} ile konuş`;$('#chatSubtitle').textContent=chatParent.prompt;const history=$('#chatHistory');history.innerHTML=(chatParent.conversation||[]).flatMap(x=>[`<div class="bubble user">${esc(x.question)}</div>`,`<div class="bubble operator">${esc(x.answer)}</div>`]).join('')||'<div class="empty">Bu görev hakkında ilk sorunu sorabilirsin.</div>';history.scrollTop=history.scrollHeight}
|
|
682
|
+
async function sendChat(){const question=$('#chatQuestion').value.trim();if(!question||!chatParent)return;try{const history=$('#chatHistory');if(history.querySelector('.empty'))history.innerHTML='';history.insertAdjacentHTML('beforeend',`<div class="bubble user">${esc(question)}</div><div class="bubble operator" data-pending="true">Operatör görev kayıtlarını inceliyor…</div>`);history.scrollTop=history.scrollHeight;$('#chatState').textContent='Sohbet motoru başlatılıyor…';await api(`/api/tasks/${chatParent.id}/chat`,'POST',{question});$('#chatQuestion').value='';$('#chatState').textContent='Soru operatöre iletildi.'}catch(e){$('#chatState').textContent=e.message;const pending=$('#chatHistory [data-pending]');if(pending)pending.textContent='Soru gönderilemedi: '+e.message}}
|
|
683
|
+
|
|
684
|
+
/* ---- SSE ---- */
|
|
685
|
+
async function resultEvent(r){if(r.kind==='operator-chat'){if(chatParent&&chatParent.id===r.parentTaskId){if(r.status==='failed'){const pending=$('#chatHistory [data-pending]');if(pending){pending.removeAttribute('data-pending');pending.textContent='Operatör yanıt veremedi: '+(r.error||'Bilinmeyen hata')}$('#chatState').textContent='Sohbet başarısız.'}else{const found=await api(`/api/tasks/${r.parentTaskId}`);chatParent=found.task;renderChat();$('#chatState').textContent='Yanıt hazır.'}}return}const fileCount=(r.changes?.created||[]).length+(r.changes?.modified||[]).length+(r.changes?.deleted||[]).length,warnings=(r.delivery?.warnings||[]).length;$('#missionTitle').textContent='Görev tamamlandı';$('#missionSub').textContent=`${r.operator||'Operatör'} · ${r.rounds||0} takım turu · ${fileCount} dosya değişikliği${r.usage?` · ${usageLine(r.usage)}`:''}`;teamNodes.forEach(n=>{n.node.classList.remove('working');n.node.classList.add('done');n.state.textContent='tamamlandı'});missionCompleted(`${r.operator||'Operatör'} · ${r.rounds||0} tur · ${fileCount} dosya değişikliği${r.delivery?.verification?` · ✓ ${r.delivery.verification}`:''}${warnings?` · ${warnings} uyarı`:''}`,r.summary);showToast('ok','Görev tamamlandı',r.summary||r.prompt||'Teslimat hazır.');logEvent({...r,level:'ok',msg:'Operatör görevi tamamladı'})}
|
|
686
|
+
function dispatchDashboardEvent(type,data){if(type==='status')applyStatus(data);else if(type==='queue')renderQueue(data);else if(type==='activity')activity(data);else if(type==='message')message(data);else if(type==='log')logEvent(data);else if(type==='result')resultEvent(data);else if(type==='notify')desktopNotify(data);else if(type==='schedules'){STATE.schedules=data;renderScheduleList()}}
|
|
687
|
+
// Görev bitince/başarısız olunca işletim sistemi bildirimi göster (panel arka planda olsa da görünür).
|
|
688
|
+
// İzin bir kullanıcı hareketinde istenir (ensureNotifyPermission). Webhook'tan bağımsızdır ve yapılandırma gerektirmez.
|
|
689
|
+
function ensureNotifyPermission(){try{if('Notification'in window&&Notification.permission==='default')Notification.requestPermission()}catch{}}
|
|
690
|
+
function desktopNotify(o){try{if(!('Notification'in window)||Notification.permission!=='granted'||replaying)return;const done=o.status!=='failed';const title=done?'✅ CrewCtl — Görev tamamlandı':'❌ CrewCtl — Görev başarısız';const extra=done?(o.verification?`\n✓ ${String(o.verification).slice(0,140)}`:''):(o.error?`\n${String(o.error).slice(0,180)}`:'');const n=new Notification(title,{body:String(o.prompt||o.id||'').slice(0,180)+extra,tag:String(o.id||'')});n.onclick=()=>{try{window.focus()}catch{}n.close()}}catch{}}
|
|
691
|
+
function receiveDashboardEvent(type,event){const data=JSON.parse(event.data);if(bootstrapping)bufferedEvents.push({type,data});else dispatchDashboardEvent(type,data)}
|
|
692
|
+
function connectDashboardEvents(){es=new EventSource('/api/events');for(const type of ['status','queue','activity','message','log','result','notify','schedules'])es.addEventListener(type,event=>receiveDashboardEvent(type,event))}
|
|
693
|
+
async function bootstrapDashboard(){connectDashboardEvents();let replayed=[];try{const s=await api('/api/state');STATE=s;try{selectedProjectId=localStorage.getItem('crewctl.activeProject')||''}catch{}applyStatus(s.status);renderQueue(s.queue);$('#targetDir').placeholder=s.workingDirAbs||'';refreshOperatorSelect();renderProjectSelect();setupMentions();csTypeChanged();renderScheduleList();if(!s.config.autonomousConsentAcceptedAt)showConsent();try{replayed=await restoreDashboardRun()}catch(error){showToast('err','Görev akışı geri yüklenemedi',error.message)}}catch(error){alert(error.message)}finally{bootstrapping=false;const seen=new Set(replayed.filter(e=>e.type==='activity'||e.type==='message'||e.type==='log').map(e=>JSON.stringify(e))),pending=bufferedEvents;bufferedEvents=[];for(const event of pending){if(seen.has(JSON.stringify(event.data)))continue;dispatchDashboardEvent(event.type,event.data)}}}
|
|
694
|
+
// Sayfa (ör. Ekip Akışı'na geçiş) bfcache'e girerken açık SSE bağlantısını kapat — aksi halde
|
|
695
|
+
// donmuş bağlantılar birikip tarayıcının host başına bağlantı limitini doldurur ve sonraki
|
|
696
|
+
// fetch'ler (Akışı incele) sonsuz bekler. Geri/bfcache ile dönüşte sayfayı tazele.
|
|
697
|
+
window.addEventListener('pagehide',()=>{try{es?.close()}catch{}});
|
|
698
|
+
window.addEventListener('pageshow',e=>{if(e.persisted)location.reload()});
|
|
699
|
+
|
|
700
|
+
/* ---- Ayarlar ---- */
|
|
701
|
+
function normalizeDraftAgentModels(){for(const agent of draftAgents){const adapter=adapterOf(agent);if(adapter==='gemini'){delete agent.model;delete agent.modelOverride}else if(adapter==='opencode'&&agent.autoDiscovered&&agent.model&&agent.modelOverride!==true)delete agent.model}}
|
|
702
|
+
function setAgentModel(index,value){const agent=draftAgents[index];if(!agent)return;const model=String(value||'').trim();if(model)agent.model=model;else delete agent.model;if(agent.autoDiscovered){if(model)agent.modelOverride=true;else delete agent.modelOverride}}
|
|
703
|
+
function openSettings(){draftAgents=Object.entries(STATE.config.agents||{}).map(([name,a])=>({name,...JSON.parse(JSON.stringify(a))}));draftCliSettings=cloneCliSettings(STATE.config.cliSettings);normalizeDraftAgentAdapters();normalizeDraftAgentModels();draftIgnoredAdapters=[...(STATE.config.discoveryIgnoredAdapters||[])];$('#settings').classList.add('open');showTab('agents');Promise.allSettled([ensureCodexModelCatalog(),ensureClaudeModelCatalog()]).then(()=>{if(tab==='agents'){refreshAgentCodexModels();refreshAgentClaudeModels()}})}
|
|
704
|
+
function closeSettings(){$('#settings').classList.remove('open')}
|
|
705
|
+
function showTab(name){tab=name;document.querySelectorAll('.tabs button').forEach(b=>b.classList.toggle('active',b.dataset.tab===name));const c=$('#settingsContent');if(name==='agents')renderAgents(c);else if(name==='clis')renderClis(c);else if(name==='operator')renderOperator(c);else if(name==='skills')renderSkills(c);else renderGeneral(c)}
|
|
706
|
+
function openCodeGroups(models){const groups=new Map();for(const model of [...new Set(models||[])]){const provider=String(model).split('/')[0];if(!groups.has(provider))groups.set(provider,[]);groups.get(provider).push(model)}const preferred=['opencode','opencode-go','minimax-coding-plan'];const providers=[...groups.keys()].sort((a,b)=>{const rank=x=>x==='ollama'?999:(preferred.includes(x)?preferred.indexOf(x):100);return rank(a)-rank(b)||a.localeCompare(b)});return providers.map(provider=>({provider,models:groups.get(provider).sort()}))}
|
|
707
|
+
function openCodeModelOptions(selected,scope='global'){const status=(STATE.cliStatus||[]).find(x=>x.id==='opencode'),models=[...(status?.models||[])];if(selected&&!models.includes(selected))models.push(selected);const inherited=draftCliSettings.opencode?.model?`CLI ayarını kullan (${draftCliSettings.opencode.model})`:`CLI ayarını kullan (otomatik${status?.recommendedModel?`: ${status.recommendedModel}`:''})`,first=scope==='agent'?inherited:`Otomatik önerilen${status?.recommendedModel?` (${status.recommendedModel})`:''}`;return `<option value="">${esc(first)}</option>`+openCodeGroups(models).map(group=>`<optgroup label="${esc(group.provider)}">${group.models.map(model=>`<option value="${esc(model)}" ${model===selected?'selected':''}>${esc(model)}</option>`).join('')}</optgroup>`).join('')}
|
|
708
|
+
let claudeModelCatalog=[],claudeModelsLoaded=false,claudeModelsPromise=null;
|
|
709
|
+
async function ensureClaudeModelCatalog(){if(claudeModelsLoaded)return claudeModelCatalog;if(!claudeModelsPromise)claudeModelsPromise=api('/api/claude/models').then(response=>{claudeModelCatalog=response.models||[];claudeModelsLoaded=true;return claudeModelCatalog}).finally(()=>{claudeModelsPromise=null});return claudeModelsPromise}
|
|
710
|
+
function claudeModelLabel(model){const name=model.displayName||model.id,detail=model.description||model.resolvedModel||'',id=model.id||'';return [name,detail,id&&id!==name?`kimlik: ${id}`:''].filter(Boolean).join(' · ')}
|
|
711
|
+
function claudeModelOptions(selected,scope='global'){const defaultModel=claudeModelCatalog.find(model=>model.id==='default'),models=claudeModelCatalog.filter(model=>model.id!=='default');if(selected&&!models.some(model=>model.id===selected))models.push({id:selected,displayName:selected+' (kayıtlı)'});const inheritedId=draftCliSettings.claude?.model||'',inheritedModel=claudeModelCatalog.find(model=>model.id===inheritedId),inherited=inheritedId?(inheritedModel?claudeModelLabel(inheritedModel):inheritedId):(defaultModel?claudeModelLabel(defaultModel):'Claude Code varsayılanı'),first=scope==='agent'?`CLI ayarını kullan (${inherited})`:`Otomatik — ${defaultModel?claudeModelLabel(defaultModel):'hesap/sağlayıcı/kurum varsayılanı'}`;return `<option value="">${esc(first)}</option>`+models.map(model=>`<option value="${esc(model.id)}" ${model.id===selected?'selected':''}>${esc(claudeModelLabel(model))}</option>`).join('')}
|
|
712
|
+
function agentModelControl(a,i){const adapter=adapterOf(a),selected=String(a.model||'');if(adapter==='opencode')return `<label>OpenCode modeli</label><select onchange="setAgentModel(${i},this.value)">${openCodeModelOptions(selected,'agent')}</select>`;if(adapter==='claude')return `<label>Claude Code modeli</label><select class="agentClaudeModel" data-agent-index="${i}" onchange="setAgentModel(${i},this.value)">${claudeModelOptions(selected,'agent')}</select>`;if(adapter==='codex'){const models=[...codexModelCatalog],inherited=draftCliSettings.codex?.model||'Codex varsayılanı';if(selected&&!models.some(x=>x.id===selected))models.push({id:selected,displayName:selected+' (kayıtlı)'});return `<label>Model</label><select class="agentCodexModel" data-agent-index="${i}" onchange="setAgentModel(${i},this.value)"><option value="">CLI ayarını kullan (${esc(inherited)})</option>${models.map(m=>`<option value="${esc(m.id)}" ${m.id===selected?'selected':''}>${esc(m.displayName||m.id)}</option>`).join('')}</select>`}return ''}
|
|
713
|
+
function refreshAgentCodexModels(){document.querySelectorAll('.agentCodexModel').forEach(select=>{const index=+select.dataset.agentIndex,selected=String(draftAgents[index]?.model||''),inherited=draftCliSettings.codex?.model||'Codex varsayılanı',models=[...codexModelCatalog];if(selected&&!models.some(x=>x.id===selected))models.push({id:selected,displayName:selected+' (kayıtlı)'});select.innerHTML=`<option value="">CLI ayarını kullan (${esc(inherited)})</option>`+models.map(m=>`<option value="${esc(m.id)}">${esc(m.displayName||m.id)}</option>`).join('');select.value=selected})}
|
|
714
|
+
function refreshAgentClaudeModels(){document.querySelectorAll('.agentClaudeModel').forEach(select=>{const index=+select.dataset.agentIndex,selected=String(draftAgents[index]?.model||'');select.innerHTML=claudeModelOptions(selected,'agent');select.value=selected})}
|
|
715
|
+
function renderAgents(c){const found=(STATE.cliStatus||[]).map(x=>`<span class="badge" style="color:${x.installed&&(x.id!=='opencode'||x.ready!==false)?'var(--ok)':x.installed?'var(--warn)':'var(--dim)'}">${x.installed?'●':'○'} ${esc(x.id)} ${esc(x.id==='opencode'&&x.installed?(x.ready===false?'model seçilmeli':`${x.version||'kurulu'} · model hazır`):(x.version||'kurulu değil'))}</span>`).join(' '),open=(STATE.cliStatus||[]).find(x=>x.id==='opencode'),platform=STATE.platform==='win32'?'windows':STATE.platform==='darwin'?'macos':'linux',install=open?.install?.[platform];
|
|
716
|
+
c.innerHTML=`<div class="item"><div class="row" style="flex-wrap:wrap"><b>Kurulu CLI taraması</b>${found}<div class="grow"></div>${draftIgnoredAdapters.length?`<button class="sm" onclick="restoreDiscovered()">Gizlenenleri geri getir (${draftIgnoredAdapters.length})</button>`:''}<button class="sm" onclick="scanClis()">Yeniden Tara</button></div><p class="muted">Kurulu CLI'lar otomatik tanınır. Otomatik oluşturulmuş bir agent'ı silerseniz tercihiniz korunur ve sonraki taramada geri eklenmez.</p>${open&&!open.installed?`<div style="border:1px solid var(--warn);border-radius:8px;padding:8px"><b style="color:var(--warn)">OpenCode kurulu değil</b><div class="muted">Bu cihaz için kurulum:</div><code>${esc(install||'npm install -g opencode-ai')}</code><div class="muted" style="margin-top:4px">Sağlayıcı bağlantısı: <code>${esc(open.install?.auth||'opencode auth login')}</code></div></div>`:''}</div>`
|
|
717
|
+
+`<div class="hint">Buradaki agent'lar <b>operatörün altında çalışan uzmanlardır</b>. Operatör bunlardan biri değildir — ayrı "Operatör" sekmesinde bir CLI olarak seçilir. Bir uzmanı silmek operatörü etkilemez. Agent'ın <b>rolü görev türünü bağlayıcı biçimde sınırlar</b>: executor uygulama, reviewer inceleme, planner planlama yapar. Dengeli uygulama görevlerinde hazır üç rol <b>planner → executor → reviewer</b> sırasıyla aynı turda çalıştırılır.</div>`
|
|
718
|
+
+draftAgents.map((a,i)=>`<div class="item"><div class="row"><b>${esc(a.name)}</b><span class="badge">uzman</span><div class="grow"></div><span class="badge">${esc(a.adapter||'otomatik')}</span><label style="margin:0"><input type="checkbox" style="width:auto" ${a.enabled!==false?'checked':''} onchange="draftAgents[${i}].enabled=this.checked"> Aktif</label></div><div class="grid3"><div><label>Agent adı</label><input value="${esc(a.name)}" onchange="draftAgents[${i}].name=this.value"></div><div><label>CLI komutu</label><input value="${esc(a.cmd||'')}" onchange="setAgentCommand(${i},this.value)"><div class="muted">Bilinen CLI komutlarında adapter ve güvenli varsayılan argümanlar otomatik eşitlenir.</div></div><div><label>Uzman rolü</label><select onchange="draftAgents[${i}].roleFile='roles/'+this.value">${workerRoles().map(r=>`<option ${('roles/'+r)===(a.roleFile||'')?'selected':''}>${esc(r)}</option>`).join('')}</select></div></div><label>Açıklama</label><input value="${esc(a.description||'')}" onchange="draftAgents[${i}].description=this.value">${agentModelControl(a,i)}<div class="grid3"><div><label>Yetenekler (virgül)</label><input value="${esc((a.capabilities||[]).join(', '))}" onchange="draftAgents[${i}].capabilities=this.value.split(',').map(x=>x.trim()).filter(Boolean)"></div><div><label>Maliyet sınıfı</label><select onchange="draftAgents[${i}].costTier=this.value"><option ${a.costTier==='low'?'selected':''}>low</option><option ${!a.costTier||a.costTier==='standard'?'selected':''}>standard</option><option ${a.costTier==='high'?'selected':''}>high</option></select></div><div><label>Zaman aşımı (sn)</label><input type="number" value="${a.timeoutSeconds||900}" onchange="draftAgents[${i}].timeoutSeconds=+this.value"></div></div><label>Argümanlar (her satıra bir tane)</label><textarea rows="4" onchange="draftAgents[${i}].args=this.value.split('\\n').filter(Boolean)">${esc((a.args||[]).join('\n'))}</textarea><button class="danger sm" onclick="removeDraftAgent(${i})">Agent'ı sil</button></div>`).join('')
|
|
719
|
+
+`<div class="item"><div class="row"><div class="grow"><b>Yeni CLI agent</b><div class="muted">CLI'ı seçin; komut ve önerilen argümanlar otomatik doldurulsun.</div></div></div><div class="grid3"><div><label>CLI şablonu</label><select id="newAgentCli">${[...(STATE.cliStatus||[])].sort((a,b)=>Number(b.installed)-Number(a.installed)).map(x=>`<option value="${esc(x.id)}">${esc(cliLabel(x.id))}${x.installed?' · kurulu':' · kurulu değil'}</option>`).join('')}<option value="custom">Özel CLI</option></select></div><div><label>Agent adı (isteğe bağlı)</label><input id="newAgentName" placeholder="otomatik oluşturulur"></div><div style="display:flex;align-items:flex-end"><button class="primary" style="width:100%" onclick="addAgentFromCli()">+ Agent oluştur</button></div></div></div>`
|
|
720
|
+
+`<details style="margin-top:12px"><summary>Uzman rollerini düzenle (Markdown)</summary><div class="role-layout" style="margin-top:10px"><div class="role-list">${workerRoles().map(r=>`<button class="${r===currentRole?'active':''}" onclick="selectRole('${esc(r)}')">${esc(r)}</button>`).join('')}<button onclick="newRole()">+ Yeni rol</button></div><div id="roleEditor" class="muted">Düzenlemek için bir rol seçin.</div></div></details>`;
|
|
721
|
+
if(currentRole&&workerRoles().includes(currentRole))loadRole()}
|
|
722
|
+
function removeDraftAgent(index){const agent=draftAgents[index];if(agent?.autoDiscovered){const adapter=adapterOf(agent);if(CLI_LABEL[adapter]&&!draftIgnoredAdapters.includes(adapter))draftIgnoredAdapters.push(adapter)}draftAgents.splice(index,1);renderAgents($('#settingsContent'))}
|
|
723
|
+
function addAgentFromCli(){const adapter=$('#newAgentCli')?.value||'custom',template=(STATE.cliStatus||[]).find(x=>x.id===adapter),requested=$('#newAgentName')?.value.trim();let base=requested||(adapter==='custom'?'yeni-agent':`${adapter}-agent`),name=base,n=2;while(draftAgents.some(a=>a.name===name))name=`${base}-${n++}`;draftAgents.push({name,adapter,cmd:template?.command||(adapter==='custom'?'':adapter),args:[...(template?.defaultArgs||[])],enabled:true,autoDiscovered:false,description:template?.description||'',capabilities:[...(template?.capabilities||['implementation'])],roleFile:template?.roleFile||'roles/executor.md',costTier:'standard',timeoutSeconds:template?.timeoutSeconds||STATE.config.agentTimeoutSeconds||900,silenceTimeoutSeconds:template?.silenceTimeoutSeconds||STATE.config.cliSilenceTimeoutSeconds||300});renderAgents($('#settingsContent'));notify(`${cliLabel(adapter)} varsayılanlarıyla ${name} oluşturuldu.`)}
|
|
724
|
+
async function scanClis(){try{const localDrafts=draftAgents.map(a=>JSON.parse(JSON.stringify(a))),localCliSettings=cloneCliSettings(draftCliSettings);const r=await api('/api/cli/discover','POST',{ignoredAdapters:draftIgnoredAdapters});STATE.cliStatus=r.cliStatus;STATE.config={...r.config,cliSettings:localCliSettings,discoveryIgnoredAdapters:[...draftIgnoredAdapters]};draftCliSettings=localCliSettings;claudeModelCatalog=[];claudeModelsLoaded=false;const serverAgents=Object.entries(r.config.agents||{}).map(([name,a])=>({name,...JSON.parse(JSON.stringify(a))}));for(const agent of serverAgents){if(!localDrafts.some(a=>a.name===agent.name||adapterOf(a)===adapterOf(agent)))localDrafts.push(agent)}draftAgents=localDrafts;showTab(tab);refreshOperatorSelect();notify(r.changed?'CLI taraması güncellendi; yerel taslaklar korundu.':'Tarama tamamlandı; yerel taslaklar korundu.')}catch(e){notify(e.message,true)}}
|
|
725
|
+
async function restoreDiscovered(){draftIgnoredAdapters=[];await scanClis()}
|
|
726
|
+
async function selectRole(name){currentRole=name;await loadRole()}
|
|
727
|
+
async function loadRole(){const r=await api(`/api/roles/${encodeURIComponent(currentRole)}`);const ed=$('#roleEditor');if(!ed)return;ed.innerHTML=`<label>roles/${esc(currentRole)}</label><textarea id="roleText">${esc(r.content)}</textarea><button class="primary sm" onclick="saveRole()">Rolü Kaydet</button> <button class="danger sm" onclick="deleteRoleFile()">Sil</button>`}
|
|
728
|
+
async function saveRole(){await api(`/api/roles/${encodeURIComponent(currentRole)}`,'PUT',{content:$('#roleText').value});notify('Rol kaydedildi')}
|
|
729
|
+
async function newRole(){let n=prompt('Rol dosyası adı','uzman.md');if(!n)return;if(!n.endsWith('.md'))n+='.md';await api(`/api/roles/${encodeURIComponent(n)}`,'PUT',{content:'# Rol: Uzman\n\nBu agentın sorumluluklarını, sınırlarını ve teslimat formatını yazın.\n'});STATE.roles=(await api('/api/state')).roles;currentRole=n;renderAgents($('#settingsContent'))}
|
|
730
|
+
async function deleteRoleFile(){if(!await appConfirm({title:'Rol silinsin mi?',body:`"${currentRole}" rol dosyası kalıcı olarak silinecek.`,confirmLabel:'Sil',cancelLabel:'Vazgeç',danger:true}))return;await api(`/api/roles/${encodeURIComponent(currentRole)}`,'DELETE');STATE.roles=(await api('/api/state')).roles;currentRole=null;renderAgents($('#settingsContent'))}
|
|
731
|
+
// Model ayarları operatöre değil CLI'ya aittir; aynı CLI'yı kullanan tüm çağrılara uygulanır.
|
|
732
|
+
let codexModelCatalog=[],codexModelsPromise=null;
|
|
733
|
+
async function ensureCodexModelCatalog(){if(codexModelCatalog.length)return codexModelCatalog;if(!codexModelsPromise)codexModelsPromise=api('/api/codex/models').then(response=>{codexModelCatalog=response.models||[];return codexModelCatalog}).finally(()=>{codexModelsPromise=null});return codexModelsPromise}
|
|
734
|
+
async function loadCodexModels(selected){const select=$('#codexModel');if(!select)return;try{await ensureCodexModelCatalog();if($('#codexModel')!==select)return;select.innerHTML='<option value="">Codex varsayılanı</option>'+codexModelCatalog.map(m=>`<option value="${esc(m.id)}">${esc(m.displayName||m.id)}</option>`).join('');if(selected&&!codexModelCatalog.some(m=>m.id===selected))select.insertAdjacentHTML('beforeend',`<option value="${esc(selected)}">${esc(selected)} (kayıtlı)</option>`);select.value=selected||'';const hint=$('#codexModelHint');if(hint)hint.textContent=`Codex hesabından ${codexModelCatalog.length} aktif model alındı; boş seçim CLI varsayılanını kullanır.`;updateCodexCapabilities()}catch(error){if($('#codexModel')!==select)return;select.innerHTML='<option value="">Codex varsayılanı</option>';if(selected){select.insertAdjacentHTML('beforeend',`<option value="${esc(selected)}">${esc(selected)} (kayıtlı)</option>`);select.value=selected}const hint=$('#codexModelHint');if(hint)hint.textContent='Aktif model listesi alınamadı: '+error.message}}
|
|
735
|
+
async function loadClaudeModels(selected){const select=$('#claudeModel');if(!select)return;try{await ensureClaudeModelCatalog();if($('#claudeModel')!==select)return;select.innerHTML=claudeModelOptions(selected,'global');select.value=selected||'';const hint=$('#claudeModelHint');if(hint)hint.textContent=`Bu Claude Code hesabı, sağlayıcısı ve kurum politikası için ${claudeModelCatalog.filter(model=>model.id!=='default').length} seçilebilir model bildirildi. Liste bir model isteği çalıştırılmadan Claude Code'dan alınır.`;refreshAgentClaudeModels()}catch(error){if($('#claudeModel')!==select)return;claudeModelCatalog=[];select.innerHTML=claudeModelOptions(selected,'global');select.value=selected||'';const hint=$('#claudeModelHint');if(hint)hint.textContent='Claude Code model kataloğu alınamadı; yalnızca varsayılan veya önceden kayıtlı seçim kullanılabilir: '+error.message}}
|
|
736
|
+
function updateCodexCapabilities(){const settings=draftCliSettings.codex||(draftCliSettings.codex={...CLI_SETTINGS_DEFAULTS.codex}),chosen=$('#codexModel')?.value||'',model=codexModelCatalog.find(x=>x.id===chosen)||(chosen?'':codexModelCatalog.find(x=>x.isDefault)),effort=$('#codexEffort'),tier=$('#codexServiceTier');settings.model=chosen;if(effort){const values=(model?.reasoningEfforts||[]).map(x=>x.id);if(!values.length)values.push('low','medium','high','xhigh','max','ultra');const current=settings.reasoningEffort||'medium';effort.innerHTML=values.map(x=>`<option value="${esc(x)}">${esc(x)}</option>`).join('');effort.value=values.includes(current)?current:(model?.defaultReasoningEffort||values[0]);settings.reasoningEffort=effort.value}if(tier){const values=(model?.serviceTiers||[]).map(x=>({id:x.id,label:x.name||x.id}));if(!values.length)values.push({id:'fast',label:'fast'},{id:'priority',label:'priority'});const current=settings.serviceTier||'fast';tier.innerHTML=values.map(x=>`<option value="${esc(x.id)}">${esc(x.label)}${x.label!==x.id?' ('+esc(x.id)+')':''}</option>`).join('');tier.value=values.some(x=>x.id===current)?current:(model?.defaultServiceTier||values[0].id);settings.serviceTier=tier.value}}
|
|
737
|
+
function renderClis(c){const statuses=STATE.cliStatus||[],available=statuses.filter(x=>x.installed&&CLI_LABEL[x.id]);const ids=available.length?available.map(x=>x.id):Object.keys(CLI_LABEL),cards=[];for(const id of ids){const status=statuses.find(x=>x.id===id),defaults=CLI_SETTINGS_DEFAULTS[id],settings=defaults?(draftCliSettings[id]||(draftCliSettings[id]={...defaults})):null;if(id==='codex')cards.push(`<div class="item"><div class="row"><b>Codex</b><div class="grow"></div><span class="badge">${esc(status?.version||'CLI')}</span></div><div class="grid2"><div><label>Model</label><select id="codexModel" onchange="draftCliSettings.codex.model=this.value;updateCodexCapabilities()"><option value="">Modeller yükleniyor…</option></select><div id="codexModelHint" class="muted">Codex hesabındaki aktif modeller getiriliyor.</div></div><div><label>Reasoning effort</label><select id="codexEffort" onchange="draftCliSettings.codex.reasoningEffort=this.value"><option>${esc(settings.reasoningEffort)}</option></select></div><div><label>Servis hızı</label><select id="codexServiceTier" onchange="draftCliSettings.codex.serviceTier=this.value"><option>${esc(settings.serviceTier)}</option></select></div></div></div>`);else if(id==='claude')cards.push(`<div class="item"><div class="row"><b>Claude Code</b><div class="grow"></div><span class="badge">${esc(status?.version||'CLI')}</span></div><label>Model</label><select id="claudeModel" onchange="draftCliSettings.claude.model=this.value"><option value="">Modeller yükleniyor…</option></select><div id="claudeModelHint" class="muted">Bu kullanıcının Claude Code hesabı, sağlayıcısı ve kurum politikası için kullanılabilir modeller getiriliyor.</div></div>`);else if(id==='opencode')cards.push(`<div class="item"><div class="row"><b>OpenCode</b><div class="grow"></div><span class="badge">${esc(status?.version||'CLI')}</span></div><label>Model</label><select id="opencodeModel" onchange="draftCliSettings.opencode.model=this.value">${openCodeModelOptions(settings.model,'global')}</select><div class="muted">Tüm sağlayıcılar listelenir; boş seçim uygun modeli otomatik seçer.</div></div>`);else cards.push(`<div class="item"><div class="row"><b>${esc(cliLabel(id))}</b><div class="grow"></div><span class="badge">${esc(status?.version||'CLI')}</span></div><div class="muted">Bu CLI için doğrulanmış model kataloğu yok; model seçimi sunulmaz ve CLI varsayılanı kullanılır.</div></div>`) }c.innerHTML=`<div class="hint">Model seçimi yalnızca doğrulanmış katalog sağlayan CLI'larda gösterilir. Seçilen model o CLI'nın <b>tüm kullanımlarına</b> uygulanır; agent ekranındaki boş override alanı miras alınan modeli adıyla gösterir.</div>${cards.join('')}`;if(ids.includes('codex'))loadCodexModels(draftCliSettings.codex.model||'');if(ids.includes('claude'))loadClaudeModels(draftCliSettings.claude.model||'')}
|
|
738
|
+
function renderOperator(c){const o=STATE.config.operator||{},opts=operatorCliOptions(true),curCli=opts.some(x=>x.cli===o.cli)?o.cli:opts[0]?.cli;c.innerHTML=`<div class="hint">Operatör, uzman agent'lardan <b>bağımsız bir CLI'dır</b>. Burada yalnızca ekip yönetimi ve tur sınırları ayarlanır. CLI modellerini “CLI'lar ve Modeller” sekmesinden seçebilirsiniz.</div><div class="item"><div class="grid2"><div><label>Operatör CLI'sı — operator.md'yi çalıştıracak CLI</label><select id="defaultOperator">${opts.map(x=>`<option value="${x.cli}" ${x.cli===curCli?'selected':''}>${esc(x.label)}</option>`).join('')||'<option value="">Kurulu CLI yok</option>'}</select></div><div><label>Maksimum takım turu</label><input id="maxRounds" type="number" value="${o.maxRounds||6}"></div><div><label>Tur başına maksimum delegasyon</label><input id="maxDelegations" type="number" value="${o.maxDelegationsPerRound||8}"></div><div><label>JSON protokol tekrarları</label><input id="protocolRetries" type="number" value="${o.protocolRetries??1}"></div></div></div><div class="item"><label>Operatör rolü — roles/operator.md (akışı buradan kurgularsınız)</label><div id="operatorRoleBox" class="muted">yükleniyor…</div></div>`;loadOperatorRole()}
|
|
739
|
+
async function loadOperatorRole(){try{const r=await api('/api/roles/operator.md');const box=$('#operatorRoleBox');if(box)box.innerHTML=`<textarea id="operatorRoleText">${esc(r.content)}</textarea><button class="primary sm" style="margin-top:6px" onclick="saveOperatorRole()">Operatör rolünü kaydet</button>`}catch(e){const box=$('#operatorRoleBox');if(box)box.textContent=e.message}}
|
|
740
|
+
async function saveOperatorRole(){await api('/api/roles/operator.md','PUT',{content:$('#operatorRoleText').value});notify('Operatör rolü kaydedildi')}
|
|
741
|
+
/* ---- Beceriler ---- */
|
|
742
|
+
function ensureSkillsCfg(){STATE.config.skills={autoMatch:true,catalogLimit:12,maxSkillsPerAssignment:3,charBudget:2400,referenceCharBudget:1200,...(STATE.config.skills||{})};if(!Array.isArray(STATE.config.skills.enabled))STATE.config.skills.enabled=[];return STATE.config.skills}
|
|
743
|
+
function skillEnabled(name){return (STATE.config.skills?.enabled||[]).includes(name)}
|
|
744
|
+
function toggleSkill(name,on){const s=ensureSkillsCfg();const set=new Set(s.enabled);if(on)set.add(name);else set.delete(name);s.enabled=[...set];const q=$('#skillSearch')?.value||'';renderSkills($('#settingsContent'));const box=$('#skillSearch');if(box&&q){box.value=q;filterSkills(q)}}
|
|
745
|
+
function toggleAllSkills(on){const s=ensureSkillsCfg();s.enabled=on?(STATE.skills||[]).map(skill=>skill.name):[];renderSkills($('#settingsContent'))}
|
|
746
|
+
function filterSkills(value){const q=String(value||'').toLocaleLowerCase('tr-TR');document.querySelectorAll('[data-skill-search]').forEach(card=>card.style.display=card.dataset.skillSearch.includes(q)?'':'none')}
|
|
747
|
+
function renderSkills(c){const s=ensureSkillsCfg(),skills=(STATE.skills||[]).map((sk,idx)=>({sk,idx})).sort((a,b)=>{const ea=skillEnabled(a.sk.name),eb=skillEnabled(b.sk.name);if(ea!==eb)return ea?-1:1;return a.idx-b.idx}).map(x=>x.sk);
|
|
748
|
+
c.innerHTML=`<div class="hint">Beceriler uzmanların bir işi <b>nasıl</b> yapacağını anlatır. Yalnızca etkinleştirdikleriniz taranır; operatöre tüm katalog yerine görevle eşleşen kısa açıklamalar, uzmana ise seçilen rehberlerin yolu verilir. Tam metin ancak ihtiyaç anında okunur. Hiçbirini seçmezseniz davranış değişmez.</div>`
|
|
749
|
+
+`<div class="item"><div class="row"><b>Etkin beceriler</b><span class="badge" id="skillCount">${s.enabled.length} etkin</span><div class="grow"></div><button class="sm" onclick="toggleAllSkills(true)">Tümünü etkinleştir</button><button class="sm" onclick="toggleAllSkills(false)">Tümünü kapat</button></div><div class="grid2"><div><label>Katalog karakter bütçesi</label><input type="number" min="200" value="${s.charBudget}" onchange="ensureSkillsCfg().charBudget=+this.value"><div class="muted">Operatöre giden kısa listenin üst sınırı.</div></div><div><label>Katalog adedi</label><input type="number" min="1" value="${s.catalogLimit}" onchange="ensureSkillsCfg().catalogLimit=+this.value"></div><div><label>Delegasyon başına en çok skill</label><input type="number" min="1" value="${s.maxSkillsPerAssignment}" onchange="ensureSkillsCfg().maxSkillsPerAssignment=+this.value"></div><div><label>Uzman referans bütçesi</label><input type="number" min="300" value="${s.referenceCharBudget}" onchange="ensureSkillsCfg().referenceCharBudget=+this.value"></div></div><label style="margin-top:8px"><input type="checkbox" style="width:auto" ${s.autoMatch!==false?'checked':''} onchange="ensureSkillsCfg().autoMatch=this.checked"> Operatör alanı atladığında görev metninden otomatik eşleştir</label></div>`
|
|
750
|
+
+`<div class="item"><label>Skill ara</label><input id="skillSearch" placeholder="SEO, test, erişilebilirlik, API…" oninput="filterSkills(this.value)"></div>`
|
|
751
|
+
+(skills.length?skills.map((sk,i)=>`<div class="item" data-skill-search="${esc(`${sk.name} ${sk.category||''} ${sk.description||''}`.toLocaleLowerCase('tr-TR'))}"><div class="row"><b>${esc(sk.name)}</b><span class="badge">${esc(sk.category||'general')}</span>${(sk.appliesTo||[]).map(k=>`<span class="badge">${esc(k)}</span>`).join('')}<div class="grow"></div><label style="margin:0"><input type="checkbox" style="width:auto" ${skillEnabled(sk.name)?'checked':''} onchange="toggleSkill('${esc(sk.name)}',this.checked)"> Etkin</label></div><div class="muted">${esc(sk.description||'')}</div><div style="margin-top:6px"><button class="sm" onclick="editSkill(${i},'${esc(sk.file)}')">Düzenle</button> <button class="danger sm" onclick="removeSkill('${esc(sk.file)}','${esc(sk.name)}')">Sil</button></div><div id="skillEditor-${i}"></div></div>`).join(''):`<div class="item muted">Henüz beceri yok. Aşağıdan yeni bir beceri oluşturun.</div>`)
|
|
752
|
+
+`<div class="item"><div class="row"><div class="grow"><b>Yeni beceri</b><div class="muted">Kısa ad verin; frontmatter'lı bir şablon oluşturulur, ardından düzenleyin.</div></div><button class="primary" onclick="newSkill()">+ Beceri oluştur</button></div></div>`}
|
|
753
|
+
async function editSkill(i,file){const box=$('#skillEditor-'+i);if(!box)return;if(box.dataset.open==='1'){box.dataset.open='';box.innerHTML='';return}const r=await api('/api/skills/'+encodeURIComponent(file));box.dataset.open='1';box.innerHTML=`<label style="margin-top:8px">skills/${esc(file)}</label><textarea id="skillText-${i}" rows="12">${esc(r.content)}</textarea><button class="primary sm" onclick="saveSkill(${i},'${esc(file)}')">Beceriyi kaydet</button>`}
|
|
754
|
+
async function saveSkill(i,file){await api('/api/skills/'+encodeURIComponent(file),'PUT',{content:$('#skillText-'+i).value});STATE.skills=(await api('/api/state')).skills;notify('Beceri kaydedildi');renderSkills($('#settingsContent'))}
|
|
755
|
+
async function removeSkill(file,name){if(!await appConfirm({title:'Beceri silinsin mi?',body:`"${name}" becerisi kalıcı olarak silinecek.`,confirmLabel:'Sil',cancelLabel:'Vazgeç',danger:true}))return;await api('/api/skills/'+encodeURIComponent(file),'DELETE');const s=ensureSkillsCfg();s.enabled=s.enabled.filter(n=>n!==name);const fresh=await api('/api/state');STATE.skills=fresh.skills;notify('Beceri silindi');renderSkills($('#settingsContent'))}
|
|
756
|
+
async function newSkill(){let n=prompt('Beceri dosyası adı (kısa, kebab-case)','yeni-beceri');if(!n)return;const slug=n.trim().replace(/\.md$/i,'').replace(/[^a-zA-Z0-9_-]/g,'-');if(!slug)return notify('Geçersiz ad',true);const tpl=`---\nname: ${slug}\ndescription: Bu becerinin ne yaptığını ve ne zaman kullanılacağını kısa yazın\ncategory: general\nappliesTo: [implement, review]\nmatch: []\n---\n\n# ${slug}\n\nİzlenecek kısa ve somut prosedürü yazın.\n`;await api('/api/skills/'+encodeURIComponent(slug+'.md'),'PUT',{content:tpl});STATE.skills=(await api('/api/state')).skills;notify('Beceri oluşturuldu; düzenleyip Etkin işaretleyin.');renderSkills($('#settingsContent'))}
|
|
757
|
+
/* ---- Zamanlanmış görevler ---- */
|
|
758
|
+
const SCHED_DAYS=['Paz','Pzt','Sal','Çar','Per','Cum','Cmt'];
|
|
759
|
+
function scheduleTriggerText(s){const t=s.trigger||{};if(t.type==='interval'){const n=t.everyMinutes||0;return n%60===0&&n>=60?`Her ${n/60} saatte bir`:`Her ${n} dakikada bir`}if(t.type==='daily')return `Her gün ${esc(t.at||'')}`;if(t.type==='weekly')return `${(t.days||[]).map(d=>SCHED_DAYS[d]).join(', ')} · ${esc(t.at||'')}`;return '—'}
|
|
760
|
+
function scheduleNextText(s){if(s.enabled===false)return 'devre dışı';if(!s.nextRunAt)return '—';try{return new Date(s.nextRunAt).toLocaleString('tr-TR',{dateStyle:'short',timeStyle:'short'})}catch{return '—'}}
|
|
761
|
+
const INTERVAL_PRESETS=[[5,'5 dakikada bir'],[10,'10 dakikada bir'],[15,'15 dakikada bir'],[30,'30 dakikada bir'],[60,'Saatte bir'],[120,'2 saatte bir'],[180,'3 saatte bir'],[360,'6 saatte bir'],[720,'12 saatte bir'],[1440,'Günde bir']];
|
|
762
|
+
function hourOptions(sel){let o='';for(let h=0;h<24;h++){const v=String(h).padStart(2,'0');o+=`<option value="${v}" ${v===sel?'selected':''}>${v}</option>`}return o}
|
|
763
|
+
function minuteOptions(sel){let o='';for(let m=0;m<60;m+=5){const v=String(m).padStart(2,'0');o+=`<option value="${v}" ${v===sel?'selected':''}>${v}</option>`}return o}
|
|
764
|
+
function timeSelectHtml(h='09',m='00'){return `<div class="row" style="gap:6px"><select id="cs-hour" style="width:auto">${hourOptions(h)}</select><b>:</b><select id="cs-min" style="width:auto">${minuteOptions(m)}</select></div>`}
|
|
765
|
+
function csTriggerFields(type){if(type==='interval')return `<label>Sıklık</label><select id="cs-every">${INTERVAL_PRESETS.map(([mn,l])=>`<option value="${mn}" ${mn===60?'selected':''}>${l}</option>`).join('')}</select>`;if(type==='daily')return `<label>Saat</label>${timeSelectHtml()}`;return `<label>Günler</label><div class="row" style="flex-wrap:wrap;gap:10px">${SCHED_DAYS.map((d,i)=>`<label style="margin:0;display:inline-flex;align-items:center;gap:4px;text-transform:none;letter-spacing:0;font-size:12px;color:var(--tx)"><input type="checkbox" class="cs-day" value="${i}" style="width:auto" ${i>=1&&i<=5?'checked':''}> ${d}</label>`).join('')}</div><label>Saat</label>${timeSelectHtml()}`}
|
|
766
|
+
function csTypeChanged(){const box=$('#cs-fields');if(box)box.innerHTML=csTriggerFields($('#cs-type')?.value||'interval')}
|
|
767
|
+
// Composer modu: "now" (Görevi Ekle) veya "sched" (Zamanla). Zamanla modunda ana buton
|
|
768
|
+
// zamanlama oluşturur; böylece dolu zamanlama alanı yanlışlıkla hemen çalıştırılmaz.
|
|
769
|
+
let composerMode='now';
|
|
770
|
+
function setComposerMode(m){composerMode=m==='sched'?'sched':'now';$('#cmNow').classList.toggle('active',composerMode==='now');$('#cmSched').classList.toggle('active',composerMode==='sched');$('#schedFields').hidden=composerMode!=='sched';$('#composerSubmit').textContent=composerMode==='sched'?'Zamanla':'Görevi Ekle';if(composerMode==='sched')csTypeChanged()}
|
|
771
|
+
function composerSubmit(){if(composerMode==='sched')addScheduleFromComposer();else addTask()}
|
|
772
|
+
function composerScheduleBody(){const type=$('#cs-type').value,trigger={type};if(type==='interval')trigger.everyMinutes=+$('#cs-every').value;else{trigger.at=`${$('#cs-hour').value}:${$('#cs-min').value}`;if(type==='weekly')trigger.days=[...document.querySelectorAll('.cs-day:checked')].map(x=>+x.value)}return {prompt:$('#prompt').value.trim(),operatorCli:$('#taskOperator').value||undefined,executionMode:$('#executionMode').value,targetDir:$('#targetDir').value.trim()||undefined,trigger,enabled:true}}
|
|
773
|
+
function renderScheduleList(){const list=STATE.schedules||[];const cnt=$('#scheduleCount');if(cnt)cnt.textContent=list.length;const box=$('#scheduleList');if(!box)return;box.innerHTML=list.length?list.map(s=>`<div class="task"><div class="row"><span class="badge">${scheduleTriggerText(s)}</span>${s.operatorCli?`<span class="badge">${esc(cliLabel(s.operatorCli))}</span>`:''}<div class="grow"></div><label style="margin:0" title="Aktif/pasif"><input type="checkbox" style="width:auto" ${s.enabled!==false?'checked':''} onchange="toggleSchedule('${esc(s.id)}',this.checked)"></label></div><div class="prompt">${esc(s.prompt)}</div><div class="row" style="margin-top:5px"><span class="muted">Sonraki: ${scheduleNextText(s)}</span><div class="grow"></div><button class="danger sm" onclick="deleteSchedule('${esc(s.id)}')">Sil</button></div></div>`).join(''):'<div class="empty">Henüz zamanlanmış görev yok. Yeni görevde “Zamanla”yı kullanın.</div>'}
|
|
774
|
+
async function refreshSchedules(){try{const r=await api('/api/schedules');STATE.schedules=r.schedules||[]}catch{}}
|
|
775
|
+
async function addScheduleFromComposer(){const body=composerScheduleBody();if(!body.prompt){showToast('err','Hedef gerekli','Önce yukarıya görev hedefini yazın.');return}try{const s=await api('/api/schedules','POST',body);await refreshSchedules();renderScheduleList();$('#prompt').value='';setComposerMode('now');showToast('ok','Görev zamanlandı',`Sonraki çalışma: ${scheduleNextText(s)}`)}catch(e){showToast('err','Zamanlanamadı',e.message)}}
|
|
776
|
+
async function toggleSchedule(id,enabled){try{await api('/api/schedules/'+encodeURIComponent(id),'PUT',{enabled});await refreshSchedules();renderScheduleList()}catch(e){showToast('err','Güncellenemedi',e.message);renderScheduleList()}}
|
|
777
|
+
async function deleteSchedule(id){if(!await appConfirm({title:'Zamanlama silinsin mi?',body:'Bu zamanlanmış görev kaldırılacak; gelecekte otomatik çalışmayacak.',confirmLabel:'Sil',cancelLabel:'Vazgeç',danger:true}))return;try{await api('/api/schedules/'+encodeURIComponent(id),'DELETE');await refreshSchedules();renderScheduleList();showToast('ok','Zamanlama silindi')}catch(e){showToast('err','Silinemedi',e.message)}}
|
|
778
|
+
function renderGeneral(c){c.innerHTML=`<div class="item grid2"><div><label>Varsayılan çalışma klasörü</label><div class="pickrow"><input id="workingDir" value="${esc(STATE.config.workingDir||'..')}"><button type="button" onclick="openFs(v=>$('#workingDir').value=v,$('#workingDir').value)" title="Gözat">${ICN.folderOpen}</button></div></div><div><label>Onay modu</label><select id="approvalMode"><option ${STATE.config.approvalMode==='ask'?'selected':''}>ask</option><option ${STATE.config.approvalMode==='auto'?'selected':''}>auto</option></select></div><div><label>Günlük çağrı bütçesi</label><input id="dailyBudget" type="number" value="${STATE.config.dailyCallBudget||150}"></div><div><label>Agent zaman aşımı</label><input id="agentTimeout" type="number" value="${STATE.config.agentTimeoutSeconds||900}"></div><div><label>Hafıza karakter bütçesi</label><input id="memoryBudget" type="number" value="${STATE.config.memoryCharBudget||8000}"></div><div><label>Takım bağlam bütçesi</label><input id="contextBudget" type="number" value="${STATE.config.teamContextCharBudget||30000}"></div><div style="grid-column:1/-1"><label>Bildirim webhook URL (opsiyonel · Slack uyumlu)</label><input id="notifyWebhook" value="${esc(STATE.config.notify&&STATE.config.notify.webhookUrl||'')}" placeholder="https://hooks.slack.com/services/..."><div class="muted">Her görev bitince/başarısız olunca bu adrese bildirim POST edilir. Boş = kapalı.</div></div></div><details style="margin-top:10px"><summary>Gelişmiş: ham JSON yapılandırması</summary><p class="muted">Doğrudan JSON düzenleyip “Ham JSON'u uygula”ya basın, ardından Kaydet ile kalıcılaştırın.</p><textarea id="rawConfig" rows="16">${esc(JSON.stringify(STATE.config,null,2))}</textarea><button class="sm" style="margin-top:6px" onclick="applyRaw()">Ham JSON'u uygula</button></details>`}
|
|
779
|
+
function applyRaw(){try{STATE.config=JSON.parse($('#rawConfig').value);draftAgents=Object.entries(STATE.config.agents||{}).map(([name,a])=>({name,...JSON.parse(JSON.stringify(a))}));draftCliSettings=cloneCliSettings(STATE.config.cliSettings);normalizeDraftAgentModels();notify('Ham JSON belleğe alındı; Kaydet ile kalıcılaştırın.');renderGeneral($('#settingsContent'))}catch(e){notify('Geçersiz JSON: '+e.message,true)}}
|
|
780
|
+
async function saveSettings(){try{normalizeDraftAgentAdapters();const agents={};for(const a of draftAgents){const {name,...rest}=a;if(name.trim())agents[name.trim()]={...rest,args:rest.args||[]}}STATE.config.agents=agents;STATE.config.cliSettings=cloneCliSettings(draftCliSettings);STATE.config.discoveryIgnoredAdapters=[...draftIgnoredAdapters];if($('#defaultOperator'))STATE.config.operator={...(STATE.config.operator||{}),cli:$('#defaultOperator').value,roleFile:'roles/operator.md',maxRounds:+$('#maxRounds').value,maxDelegationsPerRound:+$('#maxDelegations').value,protocolRetries:+$('#protocolRetries').value};STATE.config.operator??={};STATE.config.operator.roleFile='roles/operator.md';delete STATE.config.operator.agent;delete STATE.config.operator.model;delete STATE.config.operator.codexSettings;if(!STATE.config.operator.cli)STATE.config.operator.cli=(installedClis()[0]||'codex');if($('#workingDir'))Object.assign(STATE.config,{workingDir:$('#workingDir').value,approvalMode:$('#approvalMode').value,dailyCallBudget:+$('#dailyBudget').value,agentTimeoutSeconds:+$('#agentTimeout').value,memoryCharBudget:+$('#memoryBudget').value,teamContextCharBudget:+$('#contextBudget').value,notify:{...(STATE.config.notify||{}),webhookUrl:(($('#notifyWebhook')&&$('#notifyWebhook').value)||'').trim()}});await api('/api/config','PUT',STATE.config);notify('Kaydedildi; sonraki görevde geçerli.');refreshOperatorSelect(true)}catch(e){notify(e.message,true)}}
|
|
781
|
+
|
|
782
|
+
bootstrapDashboard();
|
|
783
|
+
</script>
|
|
784
|
+
</body></html>
|