@omerrgocmen/crewctl 1.0.2 → 1.1.1
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/orchestrator/README.md +462 -462
- package/orchestrator/config.default.json +71 -71
- package/orchestrator/roles/operator.md +77 -77
- package/orchestrator/roles/reviewer.md +63 -63
- package/orchestrator/skills/acceptance-criteria.md +18 -18
- package/orchestrator/skills/accessibility-audit.md +16 -16
- package/orchestrator/skills/accessible-forms.md +16 -16
- package/orchestrator/skills/api-design.md +27 -27
- package/orchestrator/skills/api-documentation.md +16 -16
- package/orchestrator/skills/architecture-decision-record.md +18 -18
- package/orchestrator/skills/authentication-design.md +16 -16
- package/orchestrator/skills/authorization-review.md +16 -16
- package/orchestrator/skills/backward-compatibility.md +17 -17
- package/orchestrator/skills/changelog-writing.md +16 -16
- package/orchestrator/skills/ci-pipeline-design.md +16 -16
- package/orchestrator/skills/cli-design.md +16 -16
- package/orchestrator/skills/code-review.md +27 -27
- package/orchestrator/skills/configuration-management.md +16 -16
- package/orchestrator/skills/container-review.md +16 -16
- package/orchestrator/skills/contract-testing.md +16 -16
- package/orchestrator/skills/dashboard-design.md +16 -16
- package/orchestrator/skills/database-migration.md +16 -16
- package/orchestrator/skills/database-schema-design.md +16 -16
- package/orchestrator/skills/debugging.md +26 -26
- package/orchestrator/skills/dependency-review.md +16 -16
- package/orchestrator/skills/design-review.md +29 -29
- package/orchestrator/skills/design-system.md +16 -16
- package/orchestrator/skills/docs-api-reference.md +16 -16
- package/orchestrator/skills/docs-troubleshooting.md +16 -16
- package/orchestrator/skills/docs-tutorial.md +16 -16
- package/orchestrator/skills/docs-writing.md +25 -25
- package/orchestrator/skills/empty-error-loading-states.md +16 -16
- package/orchestrator/skills/end-to-end-testing.md +16 -16
- package/orchestrator/skills/error-handling.md +16 -16
- package/orchestrator/skills/frontend-design.md +28 -28
- package/orchestrator/skills/git-commit-writing.md +16 -16
- package/orchestrator/skills/graphql-design.md +16 -16
- package/orchestrator/skills/incident-runbook.md +18 -18
- package/orchestrator/skills/input-validation.md +16 -16
- package/orchestrator/skills/integration-testing.md +16 -16
- package/orchestrator/skills/interaction-design.md +16 -16
- package/orchestrator/skills/landing-page-design.md +16 -16
- package/orchestrator/skills/observability-design.md +16 -16
- package/orchestrator/skills/openapi-contract.md +16 -16
- package/orchestrator/skills/performance-profiling.md +16 -16
- package/orchestrator/skills/privacy-review.md +16 -16
- package/orchestrator/skills/property-based-testing.md +16 -16
- package/orchestrator/skills/pull-request-writing.md +16 -16
- package/orchestrator/skills/refactoring.md +16 -16
- package/orchestrator/skills/release-readiness.md +16 -16
- package/orchestrator/skills/responsive-design.md +16 -16
- package/orchestrator/skills/secrets-management.md +16 -16
- package/orchestrator/skills/secure-file-upload.md +16 -16
- package/orchestrator/skills/security-review.md +26 -26
- package/orchestrator/skills/semantic-versioning.md +17 -17
- package/orchestrator/skills/seo-on-page.md +16 -16
- package/orchestrator/skills/seo-structured-data.md +16 -16
- package/orchestrator/skills/seo-technical-audit.md +16 -16
- package/orchestrator/skills/sql-query-review.md +16 -16
- package/orchestrator/skills/supply-chain-security.md +16 -16
- package/orchestrator/skills/test-strategy.md +16 -16
- package/orchestrator/skills/threat-modeling.md +16 -16
- package/orchestrator/skills/unit-testing.md +16 -16
- package/orchestrator/skills/write-tests.md +28 -28
- package/orchestrator/src/cli-registry.js +682 -616
- package/orchestrator/src/cli.js +131 -131
- package/orchestrator/src/doctor.js +64 -64
- package/orchestrator/src/engine.js +246 -37
- package/orchestrator/src/server.js +795 -766
- package/orchestrator/src/skill-registry.js +272 -272
- package/orchestrator/src/store.js +426 -399
- package/orchestrator/web/OrbitControls.js +1417 -1417
- package/orchestrator/web/flow.html +740 -740
- package/orchestrator/web/index.html +601 -557
- 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/three.module.min.js +6 -6
- package/package.json +2 -2
|
@@ -1,558 +1,602 @@
|
|
|
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">
|
|
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
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 .node-info{border-color:color-mix(in srgb,var(--nacc) 38%,var(--line));background:linear-gradient(180deg,color-mix(in srgb,var(--nacc) 6%,var(--panel2)),var(--nodebg))}
|
|
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:8px;padding:7px 11px;transition:border-color .18s,background .18s,transform .1s,box-shadow .18s}
|
|
25
|
-
button:hover{border-color:var(--acc)} button:active{transform:translateY(1px)} button.primary{background:linear-gradient(180deg,var(--acc),#4b8ff0);border-color:var(--acc);color:#071324;font-weight:700}
|
|
26
|
-
button.primary:hover{box-shadow:0 6px 18px var(--glow)}
|
|
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 9px;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(300px,360px) minmax(440px,1fr);gap:14px;padding:16px;align-items:start;max-width:1600px;margin:0 auto}
|
|
33
|
-
.stack{display:flex;flex-direction:column;gap:12px}
|
|
34
|
-
.card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:14px}
|
|
35
|
-
.card h2{font-size:11px;letter-spacing:.7px;text-transform:uppercase;color:var(--dim);margin:0 0 10px}
|
|
36
|
-
.row{display:flex;align-items:center;gap:7px}.muted{font-size:12px;color:var(--dim)}
|
|
37
|
-
input,textarea,select{width:100%;color:var(--tx);background:var(--input);border:1px solid var(--line);border-radius:8px;padding:8px 9px}
|
|
38
|
-
input:focus,textarea:focus,select:focus{outline:none;border-color:var(--acc);box-shadow:0 0 0 3px var(--glow)}
|
|
39
|
-
textarea{resize:vertical;font:12.5px/1.5 ui-monospace,Consolas,monospace}
|
|
40
|
-
label{display:block;color:var(--dim);font-size:10px;text-transform:uppercase;letter-spacing:.5px;margin:8px 0 3px}
|
|
41
|
-
.hint{font-size:11px;color:var(--dim);background:var(--input);border:1px solid var(--line);border-radius:8px;padding:8px 9px;margin:8px 0}
|
|
42
|
-
.task{border:1px solid var(--line);background:var(--panel2);border-radius:9px;padding:9px;margin:7px 0}
|
|
43
|
-
.task .id{font:10px ui-monospace;color:var(--dim)}.task .prompt{margin:4px 0}
|
|
44
|
-
.badge{font-size:10px;border:1px solid var(--line);border-radius:999px;padding:1px 6px;color:var(--dim)}
|
|
45
|
-
details summary{cursor:pointer;color:var(--dim);font-size:12px} pre{white-space:pre-wrap;word-break:break-word}
|
|
46
|
-
.command-head{display:flex;align-items:center;gap:8px;margin-bottom:10px}.command-head h2{margin:0}
|
|
47
|
-
.empty{color:var(--dim);font-size:12px;padding:16px;text-align:center;border:1px dashed var(--line);border-radius:9px}
|
|
48
|
-
.header-state{display:flex;align-items:center;gap:7px}.header-tools{display:flex;align-items:center;gap:7px}.header-tools>button{white-space:nowrap}
|
|
49
|
-
.section-title{display:flex;align-items:center;gap:8px;margin-bottom:10px}.section-title h2{margin:0}.section-title .badge{margin-left:auto}
|
|
50
|
-
.composer-sub{font-size:12px;color:var(--dim);margin:-5px 0 10px}.composer textarea{min-height:116px;font-family:inherit;font-size:14px}
|
|
51
|
-
.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)}
|
|
52
|
-
.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}
|
|
53
|
-
.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}
|
|
54
|
-
.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}
|
|
55
|
-
/* Ekip haritasi — operasyon dugumleri */
|
|
56
|
-
.mission-top{display:flex;align-items:center;gap:10px}
|
|
57
|
-
.mission-title{font-weight:750;font-size:15px;letter-spacing:-.15px}.mission-sub{color:var(--dim);font-size:11px}
|
|
58
|
-
.team-map{display:flex;align-items:center;gap:0;overflow-x:auto;padding:18px 8px 8px;min-height:154px;scrollbar-width:thin}
|
|
59
|
-
.team-node{--nacc:var(--acc);position:relative;min-width:154px;display:flex;flex-direction:column;align-items:center;text-align:center;color:var(--tx);transition:filter .25s,transform .25s}
|
|
60
|
-
.node-orb{position:relative;isolation:isolate;width:68px;height:68px;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) 15%,var(--nodebg)),var(--nodebg) 58%) padding-box,conic-gradient(from 210deg,var(--nacc),#38577d 36%,var(--line) 61%,var(--nacc)) border-box;box-shadow:inset 0 0 0 6px color-mix(in srgb,var(--nacc) 7%,transparent),inset 0 -12px 24px #0003,0 14px 30px #0006;transition:box-shadow .25s,transform .25s}
|
|
61
|
-
.node-orb:before{content:'';position:absolute;inset:-6px;border:1px dashed color-mix(in srgb,var(--nacc) 38%,transparent);border-radius:50%;animation:nodeOrbit 12s linear infinite}.node-orb:after{content:'';position:absolute;z-index:-1;inset:10px;border:1px solid color-mix(in srgb,var(--nacc) 30%,transparent);border-radius:50%;box-shadow:inset 0 0 18px color-mix(in srgb,var(--nacc) 10%,transparent)}
|
|
62
|
-
.node-glyph{position:relative;z-index:1;font:850 14px/1 ui-monospace,Consolas,monospace;letter-spacing:1px;color:var(--nacc);text-shadow:0 0 12px color-mix(in srgb,var(--nacc) 65%,transparent)}
|
|
63
|
-
.node-status{position:absolute;z-index:2;right:0;bottom:5px;width:13px;height:13px;border:3px solid var(--panel);border-radius:50%;background:var(--dim);box-shadow:0 0 0 1px var(--line)}
|
|
64
|
-
.node-info{min-width:132px;margin-top:10px;padding:7px 10px;border:1px solid var(--line);border-radius:10px;background:linear-gradient(180deg,color-mix(in srgb,var(--panel2) 86%,transparent),var(--nodebg));box-shadow:0 8px 22px #0003}
|
|
65
|
-
.team-node .nrole{font-size:8px;letter-spacing:1px;text-transform:uppercase;color:var(--dim)}
|
|
66
|
-
.team-node .nname{max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:750;font-size:12px;margin-top:1px}.team-node .nstate{font-size:9.5px;color:var(--dim);margin-top:2px}
|
|
67
|
-
.team-node.operator .node-orb{background:linear-gradient(var(--nodebg),var(--nodebg)) padding-box,conic-gradient(from 210deg,var(--nacc),#64449a 42%,var(--line) 65%,var(--nacc)) border-box;box-shadow:inset 0 0 0 5px color-mix(in srgb,var(--nacc) 9%,transparent),0 12px 30px #0005}.team-node.operator .node-glyph{color:var(--nacc)}
|
|
68
|
-
.team-node.working{transform:translateY(-3px)}.team-node.working .node-orb{box-shadow:inset 0 0 0 5px color-mix(in srgb,var(--ok) 10%,transparent),0 0 0 5px color-mix(in srgb,var(--ok) 8%,transparent),0 12px 34px color-mix(in srgb,var(--ok) 24%,transparent)}.team-node.working .node-status{background:var(--ok);box-shadow:0 0 10px var(--ok)}
|
|
69
|
-
.team-node.done .node-status{background:var(--acc)}.team-node.failed .node-status{background:var(--err);box-shadow:0 0 9px var(--err)}.team-node.failed .node-orb{filter:saturate(.7)}
|
|
70
|
-
.team-link{position:relative;height:2px;min-width:66px;flex:1;margin:0 -18px 54px;background:repeating-linear-gradient(90deg,color-mix(in srgb,var(--acc) 48%,var(--line)) 0 5px,transparent 5px 12px);background-size:24px 2px;animation:teamRail 1.1s linear infinite;filter:drop-shadow(0 0 4px color-mix(in srgb,var(--acc) 22%,transparent))}.team-link:before{content:'';position:absolute;left:0;top:50%;width:7px;height:7px;border-radius:50%;background:var(--acc);box-shadow:0 0 9px var(--acc);transform:translate(-50%,-50%);opacity:0}.team-link:after{content:'';position:absolute;right:-1px;top:50%;width:7px;height:7px;border-top:2px solid var(--acc);border-right:2px solid var(--acc);transform:translateY(-50%) rotate(45deg);filter:drop-shadow(0 0 4px var(--acc))}.team-link.active{background:linear-gradient(90deg,transparent,var(--acc),transparent);box-shadow:0 0 12px color-mix(in srgb,var(--acc) 70%,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 5px var(--ok))}
|
|
71
|
-
@keyframes nodeOrbit{to{transform:rotate(360deg)}}@keyframes teamRail{to{background-position:24px 0}}@keyframes teamPulse{from{left:0}to{left:100%}}
|
|
72
|
-
/* Agent calisma panelleri */
|
|
73
|
-
.agent-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(330px,1fr));gap:14px}
|
|
74
|
-
.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}
|
|
75
|
-
.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}
|
|
76
|
-
.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)}
|
|
77
|
-
.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}
|
|
78
|
-
.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}
|
|
79
|
-
.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}
|
|
80
|
-
.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}
|
|
81
|
-
.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)}
|
|
82
|
-
.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}
|
|
83
|
-
.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)}
|
|
84
|
-
.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}
|
|
85
|
-
@keyframes agentPulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.72)}}
|
|
86
|
-
@keyframes agentScan{0%{transform:translateX(-120%)}55%,100%{transform:translateX(260%)}}
|
|
87
|
-
@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}}
|
|
88
|
-
.stderr{color:#ff9aa2}
|
|
89
|
-
|
|
90
|
-
.
|
|
91
|
-
.friendly-error
|
|
92
|
-
.
|
|
93
|
-
|
|
94
|
-
.
|
|
95
|
-
|
|
96
|
-
.feed-
|
|
97
|
-
.feed-item
|
|
98
|
-
.
|
|
99
|
-
.
|
|
100
|
-
.fi-
|
|
101
|
-
.fi-
|
|
102
|
-
.fi-
|
|
103
|
-
.fi-
|
|
104
|
-
.fi-
|
|
105
|
-
.fi-
|
|
106
|
-
.fi-
|
|
107
|
-
.fi-
|
|
108
|
-
.fi-
|
|
109
|
-
.fi-
|
|
110
|
-
.fi-
|
|
111
|
-
.fi-
|
|
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
|
-
.fs-
|
|
146
|
-
.fs-
|
|
147
|
-
.fs-
|
|
148
|
-
.fs-
|
|
149
|
-
.fs-
|
|
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
|
-
.mission-
|
|
187
|
-
.mission-
|
|
188
|
-
.mission-
|
|
189
|
-
.mission-
|
|
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
|
-
<div id="
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
<
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
<div class="
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
<div class="
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
</div>
|
|
290
|
-
|
|
291
|
-
</
|
|
292
|
-
|
|
293
|
-
<div
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
</
|
|
298
|
-
|
|
299
|
-
<div
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
</div>
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
<div
|
|
309
|
-
<div class="dialog-
|
|
310
|
-
<div class="dialog-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
</
|
|
318
|
-
|
|
319
|
-
<div
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
<
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
</div>
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
<div class="fs-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
<div
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
</div
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
function
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
function
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
function
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
function
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
function
|
|
386
|
-
|
|
387
|
-
function
|
|
388
|
-
function
|
|
389
|
-
function
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
function
|
|
396
|
-
function
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
function
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
async function
|
|
410
|
-
function
|
|
411
|
-
|
|
412
|
-
function
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
function
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
function
|
|
419
|
-
function
|
|
420
|
-
function
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
function
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
function
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
function
|
|
431
|
-
function
|
|
432
|
-
function
|
|
433
|
-
function
|
|
434
|
-
function
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
function
|
|
438
|
-
function
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
function
|
|
443
|
-
function
|
|
444
|
-
function
|
|
445
|
-
function
|
|
446
|
-
|
|
447
|
-
function
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
function
|
|
461
|
-
function
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
/*
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
function
|
|
482
|
-
function
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
function
|
|
486
|
-
function
|
|
487
|
-
function
|
|
488
|
-
function
|
|
489
|
-
function
|
|
490
|
-
function
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
async function
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
async function
|
|
510
|
-
function
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
function
|
|
517
|
-
function
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
const
|
|
533
|
-
function
|
|
534
|
-
function
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
function
|
|
545
|
-
function
|
|
546
|
-
function
|
|
547
|
-
function
|
|
548
|
-
async function
|
|
549
|
-
async function
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
function
|
|
553
|
-
function
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
</
|
|
558
|
-
|
|
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 .node-info{border-color:color-mix(in srgb,var(--nacc) 38%,var(--line));background:linear-gradient(180deg,color-mix(in srgb,var(--nacc) 6%,var(--panel2)),var(--nodebg))}
|
|
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:8px;padding:7px 11px;transition:border-color .18s,background .18s,transform .1s,box-shadow .18s}
|
|
25
|
+
button:hover{border-color:var(--acc)} button:active{transform:translateY(1px)} button.primary{background:linear-gradient(180deg,var(--acc),#4b8ff0);border-color:var(--acc);color:#071324;font-weight:700}
|
|
26
|
+
button.primary:hover{box-shadow:0 6px 18px var(--glow)}
|
|
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 9px;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(300px,360px) minmax(440px,1fr);gap:14px;padding:16px;align-items:start;max-width:1600px;margin:0 auto}
|
|
33
|
+
.stack{display:flex;flex-direction:column;gap:12px}
|
|
34
|
+
.card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:14px}
|
|
35
|
+
.card h2{font-size:11px;letter-spacing:.7px;text-transform:uppercase;color:var(--dim);margin:0 0 10px}
|
|
36
|
+
.row{display:flex;align-items:center;gap:7px}.muted{font-size:12px;color:var(--dim)}
|
|
37
|
+
input,textarea,select{width:100%;color:var(--tx);background:var(--input);border:1px solid var(--line);border-radius:8px;padding:8px 9px}
|
|
38
|
+
input:focus,textarea:focus,select:focus{outline:none;border-color:var(--acc);box-shadow:0 0 0 3px var(--glow)}
|
|
39
|
+
textarea{resize:vertical;font:12.5px/1.5 ui-monospace,Consolas,monospace}
|
|
40
|
+
label{display:block;color:var(--dim);font-size:10px;text-transform:uppercase;letter-spacing:.5px;margin:8px 0 3px}
|
|
41
|
+
.hint{font-size:11px;color:var(--dim);background:var(--input);border:1px solid var(--line);border-radius:8px;padding:8px 9px;margin:8px 0}
|
|
42
|
+
.task{border:1px solid var(--line);background:var(--panel2);border-radius:9px;padding:9px;margin:7px 0}
|
|
43
|
+
.task .id{font:10px ui-monospace;color:var(--dim)}.task .prompt{margin:4px 0}
|
|
44
|
+
.badge{font-size:10px;border:1px solid var(--line);border-radius:999px;padding:1px 6px;color:var(--dim)}
|
|
45
|
+
details summary{cursor:pointer;color:var(--dim);font-size:12px} pre{white-space:pre-wrap;word-break:break-word}
|
|
46
|
+
.command-head{display:flex;align-items:center;gap:8px;margin-bottom:10px}.command-head h2{margin:0}
|
|
47
|
+
.empty{color:var(--dim);font-size:12px;padding:16px;text-align:center;border:1px dashed var(--line);border-radius:9px}
|
|
48
|
+
.header-state{display:flex;align-items:center;gap:7px}.header-tools{display:flex;align-items:center;gap:7px}.header-tools>button{white-space:nowrap}
|
|
49
|
+
.section-title{display:flex;align-items:center;gap:8px;margin-bottom:10px}.section-title h2{margin:0}.section-title .badge{margin-left:auto}
|
|
50
|
+
.composer-sub{font-size:12px;color:var(--dim);margin:-5px 0 10px}.composer textarea{min-height:116px;font-family:inherit;font-size:14px}
|
|
51
|
+
.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)}
|
|
52
|
+
.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}
|
|
53
|
+
.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}
|
|
54
|
+
.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}
|
|
55
|
+
/* Ekip haritasi — operasyon dugumleri */
|
|
56
|
+
.mission-top{display:flex;align-items:center;gap:10px}
|
|
57
|
+
.mission-title{font-weight:750;font-size:15px;letter-spacing:-.15px}.mission-sub{color:var(--dim);font-size:11px}
|
|
58
|
+
.team-map{display:flex;align-items:center;gap:0;overflow-x:auto;padding:18px 8px 8px;min-height:154px;scrollbar-width:thin}
|
|
59
|
+
.team-node{--nacc:var(--acc);position:relative;min-width:154px;display:flex;flex-direction:column;align-items:center;text-align:center;color:var(--tx);transition:filter .25s,transform .25s}
|
|
60
|
+
.node-orb{position:relative;isolation:isolate;width:68px;height:68px;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) 15%,var(--nodebg)),var(--nodebg) 58%) padding-box,conic-gradient(from 210deg,var(--nacc),#38577d 36%,var(--line) 61%,var(--nacc)) border-box;box-shadow:inset 0 0 0 6px color-mix(in srgb,var(--nacc) 7%,transparent),inset 0 -12px 24px #0003,0 14px 30px #0006;transition:box-shadow .25s,transform .25s}
|
|
61
|
+
.node-orb:before{content:'';position:absolute;inset:-6px;border:1px dashed color-mix(in srgb,var(--nacc) 38%,transparent);border-radius:50%;animation:nodeOrbit 12s linear infinite}.node-orb:after{content:'';position:absolute;z-index:-1;inset:10px;border:1px solid color-mix(in srgb,var(--nacc) 30%,transparent);border-radius:50%;box-shadow:inset 0 0 18px color-mix(in srgb,var(--nacc) 10%,transparent)}
|
|
62
|
+
.node-glyph{position:relative;z-index:1;font:850 14px/1 ui-monospace,Consolas,monospace;letter-spacing:1px;color:var(--nacc);text-shadow:0 0 12px color-mix(in srgb,var(--nacc) 65%,transparent)}
|
|
63
|
+
.node-status{position:absolute;z-index:2;right:0;bottom:5px;width:13px;height:13px;border:3px solid var(--panel);border-radius:50%;background:var(--dim);box-shadow:0 0 0 1px var(--line)}
|
|
64
|
+
.node-info{min-width:132px;margin-top:10px;padding:7px 10px;border:1px solid var(--line);border-radius:10px;background:linear-gradient(180deg,color-mix(in srgb,var(--panel2) 86%,transparent),var(--nodebg));box-shadow:0 8px 22px #0003}
|
|
65
|
+
.team-node .nrole{font-size:8px;letter-spacing:1px;text-transform:uppercase;color:var(--dim)}
|
|
66
|
+
.team-node .nname{max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:750;font-size:12px;margin-top:1px}.team-node .nstate{font-size:9.5px;color:var(--dim);margin-top:2px}
|
|
67
|
+
.team-node.operator .node-orb{background:linear-gradient(var(--nodebg),var(--nodebg)) padding-box,conic-gradient(from 210deg,var(--nacc),#64449a 42%,var(--line) 65%,var(--nacc)) border-box;box-shadow:inset 0 0 0 5px color-mix(in srgb,var(--nacc) 9%,transparent),0 12px 30px #0005}.team-node.operator .node-glyph{color:var(--nacc)}
|
|
68
|
+
.team-node.working{transform:translateY(-3px)}.team-node.working .node-orb{box-shadow:inset 0 0 0 5px color-mix(in srgb,var(--ok) 10%,transparent),0 0 0 5px color-mix(in srgb,var(--ok) 8%,transparent),0 12px 34px color-mix(in srgb,var(--ok) 24%,transparent)}.team-node.working .node-status{background:var(--ok);box-shadow:0 0 10px var(--ok)}
|
|
69
|
+
.team-node.done .node-status{background:var(--acc)}.team-node.failed .node-status{background:var(--err);box-shadow:0 0 9px var(--err)}.team-node.failed .node-orb{filter:saturate(.7)}
|
|
70
|
+
.team-link{position:relative;height:2px;min-width:66px;flex:1;margin:0 -18px 54px;background:repeating-linear-gradient(90deg,color-mix(in srgb,var(--acc) 48%,var(--line)) 0 5px,transparent 5px 12px);background-size:24px 2px;animation:teamRail 1.1s linear infinite;filter:drop-shadow(0 0 4px color-mix(in srgb,var(--acc) 22%,transparent))}.team-link:before{content:'';position:absolute;left:0;top:50%;width:7px;height:7px;border-radius:50%;background:var(--acc);box-shadow:0 0 9px var(--acc);transform:translate(-50%,-50%);opacity:0}.team-link:after{content:'';position:absolute;right:-1px;top:50%;width:7px;height:7px;border-top:2px solid var(--acc);border-right:2px solid var(--acc);transform:translateY(-50%) rotate(45deg);filter:drop-shadow(0 0 4px var(--acc))}.team-link.active{background:linear-gradient(90deg,transparent,var(--acc),transparent);box-shadow:0 0 12px color-mix(in srgb,var(--acc) 70%,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 5px var(--ok))}
|
|
71
|
+
@keyframes nodeOrbit{to{transform:rotate(360deg)}}@keyframes teamRail{to{background-position:24px 0}}@keyframes teamPulse{from{left:0}to{left:100%}}
|
|
72
|
+
/* Agent calisma panelleri */
|
|
73
|
+
.agent-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(330px,1fr));gap:14px}
|
|
74
|
+
.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}
|
|
75
|
+
.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}
|
|
76
|
+
.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)}
|
|
77
|
+
.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}
|
|
78
|
+
.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}
|
|
79
|
+
.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}
|
|
80
|
+
.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}
|
|
81
|
+
.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)}
|
|
82
|
+
.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}
|
|
83
|
+
.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)}
|
|
84
|
+
.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}
|
|
85
|
+
@keyframes agentPulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.72)}}
|
|
86
|
+
@keyframes agentScan{0%{transform:translateX(-120%)}55%,100%{transform:translateX(260%)}}
|
|
87
|
+
@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}}
|
|
88
|
+
.stderr{color:#ff9aa2}
|
|
89
|
+
/* Kutuya basılan metin agent çıktısıdır: boşluksuz uzun parçalar (dosya yolu, URL, hash)
|
|
90
|
+
içerebilir. overflow-wrap olmadan bunlar kutunun kenarını taşıyordu. */
|
|
91
|
+
.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}
|
|
92
|
+
.friendly-error b{display:block;color:#ff8f97;margin-bottom:3px;overflow-wrap:anywhere}.friendly-error small{color:#d5aeb2}
|
|
93
|
+
.friendly-error.success{border-color:#42d39255;background:#10261e;color:#d7ffec}.friendly-error.success b{color:var(--ok)}.friendly-error.success small{color:#a7cfbd}
|
|
94
|
+
.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}
|
|
95
|
+
/* Birlesik akis — zaman cizelgesi (olay tipine gore renkli, uzun icerik katlanir) */
|
|
96
|
+
.feed{max-height:360px;overflow:auto;display:flex;flex-direction:column;gap:2px;padding-left:2px}
|
|
97
|
+
.feed-item{position:relative;display:flex;gap:9px;padding:6px 8px 6px 4px;border-radius:9px;transition:background .14s}
|
|
98
|
+
.feed-item:hover{background:color-mix(in srgb,var(--panel2) 55%,transparent)}
|
|
99
|
+
.feed-item:not(:last-child)::before{content:'';position:absolute;left:13px;top:25px;bottom:-2px;width:1px;background:color-mix(in srgb,var(--line) 65%,transparent)}
|
|
100
|
+
.fi-dot{position:relative;z-index:1;flex:none;margin-top:2px;width:19px;height:19px;border-radius:50%;display:grid;place-items:center;font:700 10px/1 ui-monospace,Consolas,monospace;color:var(--evc,var(--dim));border:1px solid color-mix(in srgb,var(--evc,var(--line)) 50%,var(--line));background:color-mix(in srgb,var(--evc,var(--dim)) 13%,var(--panel))}
|
|
101
|
+
.fi-main{min-width:0;flex:1}
|
|
102
|
+
.fi-head{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:11.5px;line-height:1.5}
|
|
103
|
+
.fi-from{font-weight:700}.fi-arrow{color:var(--dim);opacity:.55}.fi-to{font-weight:600;color:var(--tx)}
|
|
104
|
+
.fi-tag{font:10px ui-monospace,Consolas,monospace;color:var(--dim);border:1px solid var(--line);border-radius:5px;padding:0 5px}
|
|
105
|
+
.fi-head time{margin-left:auto;color:var(--dim);font:10px ui-monospace,Consolas,monospace;flex:none}
|
|
106
|
+
.fi-msg{color:var(--tx)}
|
|
107
|
+
.fi-body{margin-top:3px;font-size:12px;line-height:1.55;color:var(--dim);white-space:pre-wrap;word-break:break-word}
|
|
108
|
+
.fi-body.clamp{max-height:74px;overflow:hidden;-webkit-mask-image:linear-gradient(180deg,#000 58%,transparent);mask-image:linear-gradient(180deg,#000 58%,transparent)}
|
|
109
|
+
.fi-more{margin-top:3px;font-size:11px;color:var(--acc);background:none;border:0;padding:1px 0}.fi-more:hover{text-decoration:underline}
|
|
110
|
+
.fi-delegation{--evc:var(--purple)}.fi-result{--evc:var(--ok)}.fi-failure,.fi-blocked{--evc:var(--err)}
|
|
111
|
+
.fi-failure,.fi-blocked{background:color-mix(in srgb,var(--err) 7%,transparent)}
|
|
112
|
+
.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)}
|
|
113
|
+
.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}
|
|
114
|
+
/* Teslimat */
|
|
115
|
+
.delivery{margin-top:8px;border-top:1px solid var(--line);padding-top:7px}
|
|
116
|
+
.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}
|
|
117
|
+
.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)}
|
|
118
|
+
.usage-detail{margin-top:3px;font-size:11px;color:var(--dim);overflow-wrap:anywhere}
|
|
119
|
+
.usage-agents{margin-top:6px;border-top:1px solid var(--line);padding-top:5px;display:grid;gap:2px}
|
|
120
|
+
.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}
|
|
121
|
+
.usage-note{margin-top:5px;font-size:10.5px;color:var(--warn)}
|
|
122
|
+
.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}
|
|
123
|
+
.failure-box .fb-title{font-weight:700;font-size:12.5px;display:flex;gap:7px;align-items:baseline;flex-wrap:wrap}
|
|
124
|
+
.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}
|
|
125
|
+
.failure-box .fb-action{margin-top:5px;font-size:12px;color:var(--dim);line-height:1.5}
|
|
126
|
+
.failure-box details{margin-top:6px}.failure-box pre{max-height:220px;overflow:auto;font-size:11px;margin-top:5px}
|
|
127
|
+
.file-change{display:flex;gap:6px;align-items:center;font:11px ui-monospace;margin:3px 0}
|
|
128
|
+
.file-change .created{color:var(--ok)}.file-change .modified{color:var(--warn)}.file-change .deleted{color:var(--err)}
|
|
129
|
+
.file-change span:first-child{flex:none;min-width:52px;font-weight:700}.file-change code{color:var(--tx);word-break:break-all}
|
|
130
|
+
/* Modal */
|
|
131
|
+
.modal{display:none;position:fixed;inset:0;background:#000b;z-index:20;align-items:center;justify-content:center}.modal.open{display:flex}
|
|
132
|
+
.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}
|
|
133
|
+
.dialog-head,.dialog-foot{padding:13px 16px;display:flex;align-items:center;gap:8px}
|
|
134
|
+
.dialog-head{border-bottom:1px solid var(--line)}.dialog-foot{border-top:1px solid var(--line)}
|
|
135
|
+
.dialog-body{padding:12px 16px;overflow:auto}
|
|
136
|
+
.tabs{display:flex;gap:6px;margin-bottom:12px}.tabs button.active{background:var(--acc);color:#071324}
|
|
137
|
+
.item{border:1px solid var(--line);background:var(--panel2);border-radius:10px;padding:11px;margin:9px 0}
|
|
138
|
+
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:8px}.grid3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px}
|
|
139
|
+
.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)}
|
|
140
|
+
#roleText,#operatorRoleText{min-height:280px}
|
|
141
|
+
/* Klasör seçici */
|
|
142
|
+
.pickrow{display:flex;gap:7px;align-items:stretch}.pickrow input{flex:1}.pickrow button{white-space:nowrap}
|
|
143
|
+
#fsModal{--fsbar:color-mix(in srgb,var(--panel2) 72%,var(--panel));backdrop-filter:blur(8px)}
|
|
144
|
+
#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}
|
|
145
|
+
.fs-titlebar{padding:11px 14px;background:var(--fsbar)}.fs-title{display:flex;align-items:center;gap:9px;font-size:13px}.fs-appicon{width:24px;height:24px;display:grid;place-items:center;border-radius:5px;background:linear-gradient(135deg,#ffd867,#e9aa28);color:#5d3900;font-size:14px}
|
|
146
|
+
.fs-toolbar{display:flex;align-items:center;gap:6px;padding:8px 10px;border-bottom:1px solid var(--line);background:var(--fsbar)}
|
|
147
|
+
.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)}
|
|
148
|
+
.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}
|
|
149
|
+
.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}
|
|
150
|
+
.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}
|
|
151
|
+
.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}
|
|
152
|
+
.fs-layout{display:grid;grid-template-columns:190px minmax(0,1fr);flex:1;min-height:0;background:var(--input)}
|
|
153
|
+
.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}
|
|
154
|
+
.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;text-align:center;color:var(--acc);font-size:14px}.fs-place.drive .ico{color:var(--dim)}
|
|
155
|
+
.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}
|
|
156
|
+
.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}
|
|
157
|
+
.fs-list{flex:1;overflow:auto;padding:4px 6px;background:var(--panel)}
|
|
158
|
+
.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}
|
|
159
|
+
.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)}
|
|
160
|
+
.fs-item .fname{display:flex;align-items:center;gap:8px;min-width:0}.fs-item .folder-icon{font-size:17px;color:#efbd44;filter:saturate(1.1)}.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}
|
|
161
|
+
.fs-empty{color:var(--dim);font-size:12px;padding:40px 14px;text-align:center}
|
|
162
|
+
.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}
|
|
163
|
+
#fsModal .dialog-foot{padding:10px 13px;background:var(--fsbar);justify-content:flex-end}.fs-pick{min-width:110px}
|
|
164
|
+
@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}}
|
|
165
|
+
.toggle{display:flex}.toggle button{border-radius:0}.toggle button:first-child{border-radius:8px 0 0 8px}.toggle button:last-child{border-radius:0 8px 8px 0}.toggle button.active{background:var(--acc);color:#071324}
|
|
166
|
+
/* Motor başlat/durdur — durum farkında */
|
|
167
|
+
.enginebtn{display:inline-flex;align-items:center;gap:7px;font-weight:700}
|
|
168
|
+
.enginebtn .ic{font-size:11px;line-height:1}
|
|
169
|
+
.enginebtn.ready{background:linear-gradient(180deg,var(--ok),#2fb87c);border-color:var(--ok);color:#04140d;box-shadow:0 4px 16px #42d39233}
|
|
170
|
+
.enginebtn.ready:hover{box-shadow:0 7px 22px #42d39247;border-color:var(--ok)}
|
|
171
|
+
.enginebtn.live{background:linear-gradient(180deg,var(--err),#e0555f);border-color:var(--err);color:#fff;box-shadow:0 4px 16px #ff6b7533}
|
|
172
|
+
.enginebtn.live:hover{box-shadow:0 7px 22px #ff6b7547;border-color:var(--err)}
|
|
173
|
+
.enginebtn.on{background:#12351f;border-color:var(--ok);color:var(--ok);cursor:default}
|
|
174
|
+
[data-theme=light] .enginebtn.on{background:#e4f7ee}
|
|
175
|
+
.enginebtn.idle{background:var(--panel2);border-color:var(--line);color:var(--dim);cursor:default}
|
|
176
|
+
.enginebtn:disabled{opacity:1}
|
|
177
|
+
#stopBtn{display:none}#stopBtn.live{display:inline-flex}#startBtn.on{display:none}
|
|
178
|
+
.rdot{width:8px;height:8px;border-radius:50%;background:currentColor;box-shadow:0 0 7px currentColor;animation:rpulse 1.3s ease-in-out infinite}
|
|
179
|
+
@keyframes rpulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.7)}}
|
|
180
|
+
.chat-history{display:flex;flex-direction:column;gap:9px;min-height:180px;max-height:52vh;overflow:auto;padding:4px}
|
|
181
|
+
.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)}
|
|
182
|
+
.chat-compose{display:flex;gap:7px;margin-top:10px}.chat-compose textarea{min-height:65px}
|
|
183
|
+
@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}}
|
|
184
|
+
@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}}
|
|
185
|
+
/* Görev durum rozeti + tamamlanma vurgusu */
|
|
186
|
+
.mission-card{position:relative;transition:border-color .3s,box-shadow .3s}
|
|
187
|
+
.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)}
|
|
188
|
+
.mission-status .sdot{width:8px;height:8px;border-radius:50%;background:currentColor;box-shadow:0 0 8px currentColor}
|
|
189
|
+
.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)}
|
|
190
|
+
.mission-status.running .sdot{animation:rpulse 1.3s ease-in-out infinite}
|
|
191
|
+
.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)}
|
|
192
|
+
.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)}
|
|
193
|
+
.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)}
|
|
194
|
+
.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)}
|
|
195
|
+
.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)}
|
|
196
|
+
.mission-card.completed .mission-banner,.mission-card.failed .mission-banner{display:flex}
|
|
197
|
+
.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))}
|
|
198
|
+
.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))}
|
|
199
|
+
.mission-banner .mb-ico{flex:none;width:36px;height:36px;display:grid;place-items:center;border-radius:10px;font-size:18px;font-weight:800}
|
|
200
|
+
.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)}
|
|
201
|
+
.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)}
|
|
202
|
+
.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}
|
|
203
|
+
.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}
|
|
204
|
+
@keyframes bannerIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
|
|
205
|
+
/* Bildirim (toast) */
|
|
206
|
+
#toasts{position:fixed;right:18px;bottom:18px;z-index:80;display:flex;flex-direction:column;gap:9px}
|
|
207
|
+
.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)}
|
|
208
|
+
.toast .t-ico{flex:none;width:28px;height:28px;display:grid;place-items:center;border-radius:8px;font-weight:800}
|
|
209
|
+
.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)}
|
|
210
|
+
.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)}
|
|
211
|
+
.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}
|
|
212
|
+
.toast.hide{animation:toastOut .3s forwards}
|
|
213
|
+
@keyframes toastIn{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}
|
|
214
|
+
@keyframes toastOut{to{opacity:0;transform:translateX(24px)}}
|
|
215
|
+
/* Yardım (?) modalı */
|
|
216
|
+
.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}
|
|
217
|
+
.help-overlay[hidden]{display:none}
|
|
218
|
+
.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}
|
|
219
|
+
.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)}
|
|
220
|
+
.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)}
|
|
221
|
+
.help-card .hc-close{margin-left:auto}
|
|
222
|
+
.help-card .hc-body{padding:16px 18px;font-size:13px;line-height:1.65}
|
|
223
|
+
.help-card .hc-body p{margin:0 0 12px}
|
|
224
|
+
.hc-row{display:flex;gap:11px;align-items:flex-start;padding:10px 0;border-top:1px solid var(--line)}
|
|
225
|
+
.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)}
|
|
226
|
+
.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}
|
|
227
|
+
.hc-num{flex:none;width:20px;color:var(--acc);font-weight:800;font-size:12.5px}
|
|
228
|
+
.help-card .hc-h{margin:16px 0 6px;font-weight:800;font-size:13.5px;color:var(--acc)}
|
|
229
|
+
#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)}
|
|
230
|
+
#helpBtn:hover{color:var(--acc);border-color:var(--acc)}
|
|
231
|
+
/* Sayfa geçiş yükleyicisi (Ekip Akışı) */
|
|
232
|
+
.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}
|
|
233
|
+
.nav-loader[hidden]{display:none}
|
|
234
|
+
.nav-loader .lt{font-size:12.5px;letter-spacing:.4px;color:#8fa0bb}
|
|
235
|
+
@keyframes navFade{from{opacity:0}to{opacity:1}}
|
|
236
|
+
.orbit-spinner{position:relative;width:64px;height:64px}
|
|
237
|
+
.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}
|
|
238
|
+
.orbit-spinner .ring{position:absolute;inset:0;border-radius:50%;border:1.5px solid #2a3d63}
|
|
239
|
+
.orbit-spinner .ring:nth-child(2){inset:8px}.orbit-spinner .ring:nth-child(3){inset:18px}
|
|
240
|
+
.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}
|
|
241
|
+
.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}
|
|
242
|
+
.orbit-spinner .o2 .planet{background:#2fd08a;box-shadow:0 0 8px #2fd08a}.orbit-spinner .o3 .planet{background:#d97757;box-shadow:0 0 8px #d97757}
|
|
243
|
+
@keyframes orbitspin{to{transform:rotate(360deg)}}
|
|
244
|
+
@media(prefers-reduced-motion:reduce){.orbit-spinner .o1,.orbit-spinner .o2,.orbit-spinner .o3{animation:none}}
|
|
245
|
+
</style>
|
|
246
|
+
<link rel="stylesheet" href="app.css">
|
|
247
|
+
<script src="app.js" defer></script>
|
|
248
|
+
</head>
|
|
249
|
+
<body>
|
|
250
|
+
<header>
|
|
251
|
+
<h1><span class="brand">CrewCtl</span> Command Center</h1>
|
|
252
|
+
<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>
|
|
253
|
+
<div class="grow"></div>
|
|
254
|
+
<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>
|
|
255
|
+
</header>
|
|
256
|
+
<main>
|
|
257
|
+
<aside class="stack">
|
|
258
|
+
<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>
|
|
259
|
+
<textarea id="prompt" rows="5" placeholder="Ekibin çözmesini istediğiniz hedefi ve başarı ölçütlerini yazın..."></textarea>
|
|
260
|
+
<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><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)">Gözat</button></div></div></details>
|
|
261
|
+
<div class="toggle" style="margin-top:10px"><button id="cmNow" type="button" class="active" style="flex:1;justify-content:center" onclick="setComposerMode('now')">Hemen çalıştır</button><button id="cmSched" type="button" style="flex:1;justify-content:center" onclick="setComposerMode('sched')">⏱ Zamanla</button></div>
|
|
262
|
+
<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>
|
|
263
|
+
<button class="primary" id="composerSubmit" style="width:100%;margin-top:10px" onclick="composerSubmit()">Görevi Ekle</button>
|
|
264
|
+
</section>
|
|
265
|
+
<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>
|
|
266
|
+
<section class="card" id="approvalCard" hidden><h2>Onay bekleyen</h2><div id="approval"></div></section>
|
|
267
|
+
<section class="card"><div class="section-title"><h2>Görevler</h2><span class="badge" id="pendingCount">0 bekleyen</span></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>
|
|
268
|
+
</aside>
|
|
269
|
+
|
|
270
|
+
<section class="stack">
|
|
271
|
+
<div class="card mission-card" id="missionCard">
|
|
272
|
+
<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>
|
|
273
|
+
<div id="teamMap" class="team-map"><div class="empty" style="width:100%">Henüz aktif ekip yok.</div></div>
|
|
274
|
+
<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>
|
|
275
|
+
</div>
|
|
276
|
+
<div class="card"><div class="command-head"><h2>Takım akışı</h2><div class="grow"></div><span class="muted">Canlı görev günlüğü</span></div><div id="feed" class="feed"><div class="empty">Henüz etkinlik yok.</div></div></div>
|
|
277
|
+
<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>
|
|
278
|
+
</section>
|
|
279
|
+
</main>
|
|
280
|
+
<div id="toasts"></div>
|
|
281
|
+
|
|
282
|
+
<div class="nav-loader" id="navLoader" hidden>
|
|
283
|
+
<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>
|
|
284
|
+
<div class="lt">Ekip Akışı açılıyor…</div>
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
<div class="help-overlay" id="helpModal" hidden>
|
|
288
|
+
<div class="help-card">
|
|
289
|
+
<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>
|
|
290
|
+
<div class="hc-body">
|
|
291
|
+
<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>
|
|
292
|
+
<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>
|
|
293
|
+
<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>
|
|
294
|
+
<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>
|
|
295
|
+
|
|
296
|
+
<div class="hc-h">Uzman ajanlar nasıl oluşturulur?</div>
|
|
297
|
+
<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>
|
|
298
|
+
<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>
|
|
299
|
+
<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>
|
|
300
|
+
<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>
|
|
301
|
+
<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>
|
|
302
|
+
<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>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
<div id="settings" class="modal"><div class="dialog">
|
|
308
|
+
<div class="dialog-head"><b>Takım ve CLI Ayarları</b><div class="grow"></div><button onclick="closeSettings()">Kapat</button></div>
|
|
309
|
+
<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>
|
|
310
|
+
<div class="dialog-foot"><button class="primary" onclick="saveSettings()">Kaydet</button><span id="saveState" class="muted"></span></div>
|
|
311
|
+
</div></div>
|
|
312
|
+
|
|
313
|
+
<div id="consentModal" class="modal" style="z-index:60" data-testid="autonomous-consent"><div class="dialog" style="width:min(620px,94vw);height:auto">
|
|
314
|
+
<div class="dialog-head"><b>Otonom CLI çalışma izni</b></div>
|
|
315
|
+
<div class="dialog-body">
|
|
316
|
+
<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>
|
|
317
|
+
<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>
|
|
318
|
+
</div>
|
|
319
|
+
<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>
|
|
320
|
+
</div></div>
|
|
321
|
+
|
|
322
|
+
<div id="editModal" class="modal"><div class="dialog" style="width:min(640px,95vw);height:auto;max-height:90vh">
|
|
323
|
+
<div class="dialog-head"><b>Bekleyen görevi düzenle</b><div class="grow"></div><button onclick="closeEdit()">Kapat</button></div>
|
|
324
|
+
<div class="dialog-body">
|
|
325
|
+
<label>Hedef ve başarı ölçütleri</label><textarea id="editPrompt" rows="6"></textarea>
|
|
326
|
+
<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>
|
|
327
|
+
<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)">📁 Gözat</button></div>
|
|
328
|
+
<div class="hint">Hedefi değiştirirseniz operatör planı sıfırlanır ve görev bir sonraki turda yeniden planlanır.</div>
|
|
329
|
+
</div>
|
|
330
|
+
<div class="dialog-foot"><button class="primary" onclick="saveEdit()">Kaydet</button><span id="editState" class="muted"></span></div>
|
|
331
|
+
</div></div>
|
|
332
|
+
|
|
333
|
+
<div id="fsModal" class="modal" style="z-index:40"><div class="dialog">
|
|
334
|
+
<div class="dialog-head fs-titlebar"><div class="fs-title"><span class="fs-appicon">📁</span><b>Çalışma klasörü seç</b></div><div class="grow"></div><button class="fs-nav" onclick="closeFs()" title="Kapat">✕</button></div>
|
|
335
|
+
<div class="fs-toolbar">
|
|
336
|
+
<button class="fs-nav" onclick="fsBack()" id="fsBackBtn" title="Geri">←</button>
|
|
337
|
+
<button class="fs-nav" onclick="fsUp()" id="fsUpBtn" title="Üst klasör">↑</button>
|
|
338
|
+
<button class="fs-nav" onclick="fsGo('')" title="Bu bilgisayar">⌂</button>
|
|
339
|
+
<div class="fs-address" id="fsAddress" ondblclick="fsEditAddress()">
|
|
340
|
+
<div class="fs-breadcrumbs" id="fsBreadcrumbs"></div>
|
|
341
|
+
<input id="fsPath" aria-label="Klasör yolu" onkeydown="fsAddressKey(event)" onblur="fsEndAddress(false)">
|
|
342
|
+
</div>
|
|
343
|
+
<button class="fs-nav" onclick="fsGo(fsCurrent,false)" title="Yenile">↻</button>
|
|
344
|
+
</div>
|
|
345
|
+
<div class="fs-layout">
|
|
346
|
+
<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>
|
|
347
|
+
<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>
|
|
348
|
+
</div>
|
|
349
|
+
<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>
|
|
350
|
+
<div class="dialog-foot"><button onclick="closeFs()">İptal</button><button class="primary fs-pick" id="fsPickBtn" onclick="fsPick()">Klasör seç</button></div>
|
|
351
|
+
</div></div>
|
|
352
|
+
|
|
353
|
+
<div id="chatModal" class="modal"><div class="dialog" style="width:min(760px,95vw);height:auto;max-height:90vh">
|
|
354
|
+
<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>
|
|
355
|
+
<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>
|
|
356
|
+
</div></div>
|
|
357
|
+
|
|
358
|
+
<script>
|
|
359
|
+
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;
|
|
360
|
+
const OPERATOR_ROLES=['operator.md','operator-chat.md'];
|
|
361
|
+
const workerRoles=()=>STATE.roles.filter(r=>!OPERATOR_ROLES.includes(r));
|
|
362
|
+
const esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
363
|
+
function applyTheme(t){document.documentElement.setAttribute('data-theme',t);try{localStorage.setItem('cliTheme',t)}catch{}}
|
|
364
|
+
function toggleTheme(){applyTheme(document.documentElement.getAttribute('data-theme')==='light'?'dark':'light')}
|
|
365
|
+
(function(){let t='dark';try{t=localStorage.getItem('cliTheme')||'dark'}catch{}applyTheme(t)})();
|
|
366
|
+
const time=at=>new Date(at||Date.now()).toLocaleTimeString();
|
|
367
|
+
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}
|
|
368
|
+
function notify(text,error=false){const el=$('#saveState');if(el){el.textContent=text;el.style.color=error?'var(--err)':''}showToast(error?'err':'ok',text)}
|
|
369
|
+
/* Yardım (?) modalı */
|
|
370
|
+
(function(){const m=$('#helpModal'),b=$('#helpBtn'),c=$('#helpClose');if(!m||!b)return;const open=()=>{m.hidden=false},close=()=>{m.hidden=true};
|
|
371
|
+
b.addEventListener('click',open);if(c)c.addEventListener('click',close);
|
|
372
|
+
m.addEventListener('click',e=>{if(e.target===m)close()});
|
|
373
|
+
document.addEventListener('keydown',e=>{if(e.key==='Escape'&&!m.hidden)close()});})();
|
|
374
|
+
/* Ekip Akışı'na geçerken anında yükleme efekti göster, sonra git */
|
|
375
|
+
function gotoFlow(){const n=$('#navLoader');if(n)n.hidden=false;setTimeout(()=>{location.href='flow.html'},40)}
|
|
376
|
+
const CLI_LABEL={codex:'Codex',claude:'Claude Code',gemini:'Gemini',opencode:'OpenCode'};
|
|
377
|
+
// Proje genelinde tek renk standardi: her CLI kendi marka rengiyle gosterilir (flow.html
|
|
378
|
+
// CLI_META ile birebir ayni). Durum (calisiyor/hata) ayri ipuclarıyla (nabiz, ilerleme, halka)
|
|
379
|
+
// belirtilir; kimlik rengi degismez. custom = notr gri.
|
|
380
|
+
const CLI_COLOR={codex:'#10a37f',claude:'#d97757',gemini:'#4285f4',opencode:'#0ea5e9',custom:'#8b98b0'};
|
|
381
|
+
function cliColor(id){return CLI_COLOR[id]||CLI_COLOR.custom}
|
|
382
|
+
// Bir katilimcinin (operator CLI id'si ya da uzman agent adi) CLI'sini coz. Operator mesaj/
|
|
383
|
+
// aktivite olaylarinda dogrudan CLI id'siyle gelir; uzman ise config.agents anahtaridir.
|
|
384
|
+
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'}
|
|
385
|
+
function agentColor(name){return cliColor(agentCli(name))}
|
|
386
|
+
const CLI_SETTINGS_DEFAULTS={codex:{model:'',reasoningEffort:'medium',serviceTier:'fast'},opencode:{model:''}};
|
|
387
|
+
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]||{})}]))}
|
|
388
|
+
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'}
|
|
389
|
+
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'}
|
|
390
|
+
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}}
|
|
391
|
+
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'))}
|
|
392
|
+
function cliLabel(id){return CLI_LABEL[id]||(String(id||'').charAt(0).toUpperCase()+String(id||'').slice(1))}
|
|
393
|
+
// Operatör, uzman agent'lardan BAĞIMSIZ bir CLI'dır. Seçenekler kurulu CLI'lardan üretilir;
|
|
394
|
+
// bir uzman agent silinse bile operatör listesi etkilenmez.
|
|
395
|
+
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))}
|
|
396
|
+
function operatorCliOptions(includeUnready=false){return installedClis(includeUnready).map(id=>({cli:id,label:cliLabel(id)}))}
|
|
397
|
+
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('')}
|
|
398
|
+
// sync=true → görev formunu ayarlardaki varsayılan operatör CLI'sına eşitle
|
|
399
|
+
function refreshOperatorSelect(sync){const el=$('#taskOperator');if(!el)return;fillOperatorSelect(el,sync?STATE.config.operator?.cli:(el.value||STATE.config.operator?.cli))}
|
|
400
|
+
/* Token/maliyet biçimleyicileri. Yalnızca JSON modunda çalışan CLI'lar (bugün OpenCode) veri
|
|
401
|
+
üretir; veri yoksa sayı uydurmak yerine bölüm hiç gösterilmez. */
|
|
402
|
+
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)}
|
|
403
|
+
function fmtCost(n){const v=Number(n)||0;if(!v)return '$0';return v<0.01?'<$0.01':'$'+v.toFixed(v<1?3:2)}
|
|
404
|
+
function usageLine(u){if(!u)return '';const t=u.total||u;return `${fmtTokens((t.input||0)+(t.output||0))} token · ${fmtCost(t.cost)}`}
|
|
405
|
+
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)}
|
|
406
|
+
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'}}
|
|
407
|
+
function showConsent(){const modal=$('#consentModal');if(modal)modal.classList.add('open')}
|
|
408
|
+
function dismissConsent(){$('#consentModal').classList.remove('open')}
|
|
409
|
+
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)}}
|
|
410
|
+
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)}}
|
|
411
|
+
/* ---- Görev durumu (belirgin tamamlanma göstergesi) ---- */
|
|
412
|
+
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)}
|
|
413
|
+
/* Operatörün nihai cevabı, banner'ın ana içeriğidir: "merhaba" gibi sohbet görevlerinde
|
|
414
|
+
kullanıcının görmesi gereken tek şey odur, istatistik satırı değil. */
|
|
415
|
+
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||''}
|
|
416
|
+
/* Başarısızlıkta ham hata metnini kırpmak yerine sınıflandırılmış teşhisi gösteririz:
|
|
417
|
+
başlıkta NE OLDU, altında NE YAPMALI. failure yoksa eski davranış korunur. */
|
|
418
|
+
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||''}
|
|
419
|
+
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)}
|
|
420
|
+
async function addTask(){const prompt=$('#prompt').value.trim(),operatorCli=$('#taskOperator').value,targetDir=$('#targetDir').value.trim(),executionMode=$('#executionMode').value;if(!prompt)return;ensureNotifyPermission();try{await api('/api/tasks','POST',{prompt,operatorCli,executionMode,targetDir:targetDir||undefined});$('#prompt').value='';showToast('ok','Görev kuyruğa eklendi','Operatör sıraya alındığında çalışmaya başlayacak.')}catch(e){showToast('err','Görev eklenemedi',e.message)}}
|
|
421
|
+
|
|
422
|
+
/* ---- Kuyruk ---- */
|
|
423
|
+
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>`}
|
|
424
|
+
/* Maliyet dökümü. reportingCalls < calls ise bazı CLI'lar telemetri vermemiştir; bunu
|
|
425
|
+
gizlemek yerine söyleriz, yoksa kullanıcı eksik toplamı gerçek sanır. */
|
|
426
|
+
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>`}
|
|
427
|
+
/* Başarısız görev kartı: kod + ne olduğu + ne yapılacağı görünür, ham çıktı katlanmış durur. */
|
|
428
|
+
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>`}
|
|
429
|
+
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"><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></div><div class="prompt">${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>`}
|
|
430
|
+
function renderQueue(q){STATE.queue=q;$('#pending').innerHTML=q.pending.length?q.pending.map(t=>taskCard(t,'pending')).join(''):'<div class="empty">Bekleyen görev yok.</div>';$('#approvalCard').hidden=!q.approval.length;$('#approval').innerHTML=q.approval.map(t=>taskCard(t,'approval')).join('');$('#done').innerHTML=q.done.length?q.done.map(t=>taskCard(t,'done')).join(''):'<span class="muted">Henüz tamamlanan görev yok.</span>';$('#failed').innerHTML=q.failed.length?q.failed.map(t=>taskCard(t,'failed')).join(''):'<span class="muted">Başarısız görev yok.</span>';$('#pendingCount').textContent=`${q.pending.length} bekleyen`;$('#doneCount').textContent=q.done.length;$('#failedCount').textContent=q.failed.length;$('#historyCount').textContent=`${q.done.length+q.failed.length} görev`}
|
|
431
|
+
async function approve(id){try{await api(`/api/tasks/${id}/approve`,'POST')}catch(e){alert(e.message)}}
|
|
432
|
+
async function rejectTask(id){try{await api(`/api/tasks/${id}/reject`,'POST')}catch(e){alert(e.message)}}
|
|
433
|
+
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)}}
|
|
434
|
+
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)}}
|
|
435
|
+
/* ---- Klasör seçici ---- */
|
|
436
|
+
let fsTarget=null,fsCurrent='',fsParent=null,fsEntries=[],fsSelected='',fsHistory=[],fsHistoryIndex=-1;
|
|
437
|
+
async function openFs(setter,start){fsTarget=setter;fsHistory=[];fsHistoryIndex=-1;fsSelected='';$('#fsState').textContent='';$('#fsState').style.color='';$('#fsModal').classList.add('open');await fsGo(start&&start.trim()?start.trim():'')}
|
|
438
|
+
function closeFs(){$('#fsModal').classList.remove('open');fsTarget=null;fsEndAddress(false)}
|
|
439
|
+
function fsDate(value){if(!value)return '—';try{return new Date(value).toLocaleString('tr-TR',{dateStyle:'short',timeStyle:'short'})}catch{return '—'}}
|
|
440
|
+
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})}}
|
|
441
|
+
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||''}
|
|
442
|
+
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">▰</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]))}
|
|
443
|
+
function fsSelect(index){const entry=fsEntries[index];if(!entry)return;fsSelected=entry.path;document.querySelectorAll('#fsList .fs-item').forEach((row,i)=>row.classList.toggle('selected',i===index));$('#fsSelection').textContent=entry.path;$('#fsPickBtn').textContent='Klasör seç'}
|
|
444
|
+
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">Bu klasörde alt klasör yok.<br>Mevcut klasörü seçebilirsiniz.</div>';else{list.innerHTML=fsEntries.map((entry,i)=>`<button class="fs-item" data-i="${i}" title="Açmak için çift tıklayın"><span class="fname"><span class="folder-icon">${entry.type==='drive'?'▰':'📁'}</span><b>${esc(entry.name)}</b></span><span class="date">${esc(fsDate(entry.modifiedAt))}</span><span class="type">${entry.type==='drive'?'Yerel Disk':'Dosya klasörü'}</span></button>`).join('');list.querySelectorAll('.fs-item').forEach(row=>{const i=+row.dataset.i;row.onclick=()=>fsSelect(i);row.ondblclick=()=>fsGo(fsEntries[i].path)})}}
|
|
445
|
+
async function fsGo(pathArg,push=true){try{$('#fsState').style.color='';$('#fsState').textContent='Yükleniyor…';fsSelected='';const p=pathArg?String(pathArg):'',q=p?('?path='+encodeURIComponent(p)):'';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)'}}
|
|
446
|
+
function fsBack(){if(fsHistoryIndex>0){fsHistoryIndex--;fsGo(fsHistory[fsHistoryIndex],false)}}
|
|
447
|
+
function fsUp(){if(fsParent!==null)fsGo(fsParent||'')}
|
|
448
|
+
function fsEditAddress(){$('#fsAddress').classList.add('editing');$('#fsPath').value=fsCurrent;$('#fsPath').focus();$('#fsPath').select()}
|
|
449
|
+
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}}
|
|
450
|
+
function fsAddressKey(event){if(event.key==='Enter')fsEndAddress(true);else if(event.key==='Escape')fsEndAddress(false)}
|
|
451
|
+
function fsPick(){if(!fsTarget)return;const picked=fsSelected||fsCurrent;if(!picked){$('#fsState').textContent='Önce bir sürücü veya klasör seçin.';return}fsTarget(picked);closeFs()}
|
|
452
|
+
let editId=null;
|
|
453
|
+
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')}
|
|
454
|
+
function closeEdit(){$('#editModal').classList.remove('open');editId=null}
|
|
455
|
+
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)'}}
|
|
456
|
+
|
|
457
|
+
/* ---- Ekip haritasi + terminaller ---- */
|
|
458
|
+
function updateMissionStats(){$('#missionStats').textContent=`${missionMessages} mesaj · ${missionCalls} çağrı`}
|
|
459
|
+
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()}
|
|
460
|
+
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}
|
|
461
|
+
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}
|
|
462
|
+
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()}
|
|
463
|
+
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:''};callCards.set(e.callId,item);return item}
|
|
464
|
+
/* Ham CLI çıktısını teşhise çeviren tablo. engine.js'teki classifyCliError ile AYNI kodları ve
|
|
465
|
+
aynı sırayı kullanır: kota bitmesi ile hız sınırı, oturum sorunu ile kurulum sorunu ayrı
|
|
466
|
+
teşhislerdir — kullanıcının "limitim mi bitti, CLI mı açılmadı" sorusu tam olarak budur.
|
|
467
|
+
Sıra önemli: /not found/ gibi geniş desenler kimlik kontrollerinin ARKASINDA kalmalı. */
|
|
468
|
+
const FAILURE_RULES=[
|
|
469
|
+
[/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.'],
|
|
470
|
+
[/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.','Ayarlar → Agent\'lar bölümünden anahtarı yenileyin (Gemini: GEMINI_API_KEY / GOOGLE_API_KEY).'],
|
|
471
|
+
[/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.','Terminalde oturum açın (Gemini: `gemini` çalıştırıp "Login with Google" seçin ya da GEMINI_API_KEY tanımlayın).'],
|
|
472
|
+
[/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.'],
|
|
473
|
+
[/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.'],
|
|
474
|
+
[/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.'],
|
|
475
|
+
[/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.'],
|
|
476
|
+
[/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.'],
|
|
477
|
+
[/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.','CLI\'yi kurun (Gemini: `npm install -g @google/gemini-cli`) ve Ayarlar → Agent\'lar bölümünde komut adını doğrulayın.'],
|
|
478
|
+
[/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.'],
|
|
479
|
+
];
|
|
480
|
+
function friendlyFailure(text,agent){for(const [re,title,body,action] of FAILURE_RULES){if(re.test(text))return {title:`${agent} ${title}`,body,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.'}}
|
|
481
|
+
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}
|
|
482
|
+
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.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.state.textContent='başlatıldı';setTeamState(e.agent,'working',role==='operator'?'ekibi yönetiyor':'görevi yürütüyor',role)}}
|
|
483
|
+
|
|
484
|
+
/* ---- Birlesik akis: mesajlar + sistem olaylari ---- */
|
|
485
|
+
function feedReady(){const feed=$('#feed');if(feed.querySelector('.empty'))feed.innerHTML='';return feed}
|
|
486
|
+
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)}
|
|
487
|
+
function toggleFeedBody(btn){const body=btn.previousElementSibling;if(!body)return;const clamped=body.classList.toggle('clamp');btn.textContent=clamped?'Devamını göster':'Gizle'}
|
|
488
|
+
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)}
|
|
489
|
+
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)}
|
|
490
|
+
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}}
|
|
491
|
+
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}
|
|
492
|
+
async function restoreDashboardRun(){const run=latestDashboardRun();if(!run?.id)return [];return replayRun(run.id)}
|
|
493
|
+
async function inspectRun(id){const nav=$('#navLoader'),lt=nav&&nav.querySelector('.lt'),shownAt=performance.now();
|
|
494
|
+
if(nav){if(lt)lt.textContent='Görev akışı yükleniyor…';nav.hidden=false}
|
|
495
|
+
const hideNav=()=>{if(nav){nav.hidden=true;if(lt)lt.textContent='Ekip Akışı açılıyor…'}};
|
|
496
|
+
// Emniyet: fetch herhangi bir nedenle asılırsa yükleyici en geç 15 sn'de kapansın.
|
|
497
|
+
const guard=setTimeout(()=>{hideNav();showToast('err','Akış yüklenemedi','Sunucu yanıt vermedi. Sayfayı yenileyip tekrar deneyin.')},15000);
|
|
498
|
+
try{await replayRun(id,{scroll:true})}
|
|
499
|
+
catch(e){showToast('err','Akış yüklenemedi',e.message)}
|
|
500
|
+
finally{clearTimeout(guard);const wait=Math.max(0,650-(performance.now()-shownAt));setTimeout(hideNav,wait)}}
|
|
501
|
+
|
|
502
|
+
/* ---- Sohbet ---- */
|
|
503
|
+
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)}}
|
|
504
|
+
function closeChat(){$('#chatModal').classList.remove('open');chatParent=null}
|
|
505
|
+
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}
|
|
506
|
+
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}}
|
|
507
|
+
|
|
508
|
+
/* ---- SSE ---- */
|
|
509
|
+
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ı'})}
|
|
510
|
+
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()}}
|
|
511
|
+
// Görev bitince/başarısız olunca işletim sistemi bildirimi göster (panel arka planda olsa da görünür).
|
|
512
|
+
// İzin bir kullanıcı hareketinde istenir (ensureNotifyPermission). Webhook'tan bağımsızdır ve yapılandırma gerektirmez.
|
|
513
|
+
function ensureNotifyPermission(){try{if('Notification'in window&&Notification.permission==='default')Notification.requestPermission()}catch{}}
|
|
514
|
+
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{}}
|
|
515
|
+
function receiveDashboardEvent(type,event){const data=JSON.parse(event.data);if(bootstrapping)bufferedEvents.push({type,data});else dispatchDashboardEvent(type,data)}
|
|
516
|
+
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))}
|
|
517
|
+
async function bootstrapDashboard(){connectDashboardEvents();let replayed=[];try{const s=await api('/api/state');STATE=s;applyStatus(s.status);renderQueue(s.queue);$('#targetDir').placeholder=s.workingDirAbs||'';refreshOperatorSelect();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)}}}
|
|
518
|
+
// Sayfa (ör. Ekip Akışı'na geçiş) bfcache'e girerken açık SSE bağlantısını kapat — aksi halde
|
|
519
|
+
// donmuş bağlantılar birikip tarayıcının host başına bağlantı limitini doldurur ve sonraki
|
|
520
|
+
// fetch'ler (Akışı incele) sonsuz bekler. Geri/bfcache ile dönüşte sayfayı tazele.
|
|
521
|
+
window.addEventListener('pagehide',()=>{try{es?.close()}catch{}});
|
|
522
|
+
window.addEventListener('pageshow',e=>{if(e.persisted)location.reload()});
|
|
523
|
+
|
|
524
|
+
/* ---- Ayarlar ---- */
|
|
525
|
+
function normalizeDraftAgentModels(){for(const agent of draftAgents){const adapter=adapterOf(agent);if(adapter==='claude'||adapter==='gemini'){delete agent.model;delete agent.modelOverride}else if(adapter==='opencode'&&agent.autoDiscovered&&agent.model&&agent.modelOverride!==true)delete agent.model}}
|
|
526
|
+
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}}
|
|
527
|
+
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');ensureCodexModelCatalog().then(()=>{if(tab==='agents')refreshAgentCodexModels()}).catch(()=>{})}
|
|
528
|
+
function closeSettings(){$('#settings').classList.remove('open')}
|
|
529
|
+
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)}
|
|
530
|
+
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()}))}
|
|
531
|
+
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('')}
|
|
532
|
+
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==='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 ''}
|
|
533
|
+
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})}
|
|
534
|
+
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];
|
|
535
|
+
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>`
|
|
536
|
+
+`<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>`
|
|
537
|
+
+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('')
|
|
538
|
+
+`<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>`
|
|
539
|
+
+`<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>`;
|
|
540
|
+
if(currentRole&&workerRoles().includes(currentRole))loadRole()}
|
|
541
|
+
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'))}
|
|
542
|
+
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.`)}
|
|
543
|
+
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;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)}}
|
|
544
|
+
async function restoreDiscovered(){draftIgnoredAdapters=[];await scanClis()}
|
|
545
|
+
async function selectRole(name){currentRole=name;await loadRole()}
|
|
546
|
+
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>`}
|
|
547
|
+
async function saveRole(){await api(`/api/roles/${encodeURIComponent(currentRole)}`,'PUT',{content:$('#roleText').value});notify('Rol kaydedildi')}
|
|
548
|
+
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'))}
|
|
549
|
+
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'))}
|
|
550
|
+
// Model ayarları operatöre değil CLI'ya aittir; aynı CLI'yı kullanan tüm çağrılara uygulanır.
|
|
551
|
+
let codexModelCatalog=[],codexModelsPromise=null;
|
|
552
|
+
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}
|
|
553
|
+
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}}
|
|
554
|
+
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}}
|
|
555
|
+
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==='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||'')}
|
|
556
|
+
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()}
|
|
557
|
+
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}}
|
|
558
|
+
async function saveOperatorRole(){await api('/api/roles/operator.md','PUT',{content:$('#operatorRoleText').value});notify('Operatör rolü kaydedildi')}
|
|
559
|
+
/* ---- Beceriler ---- */
|
|
560
|
+
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}
|
|
561
|
+
function skillEnabled(name){return (STATE.config.skills?.enabled||[]).includes(name)}
|
|
562
|
+
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)}}
|
|
563
|
+
function toggleAllSkills(on){const s=ensureSkillsCfg();s.enabled=on?(STATE.skills||[]).map(skill=>skill.name):[];renderSkills($('#settingsContent'))}
|
|
564
|
+
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')}
|
|
565
|
+
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);
|
|
566
|
+
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>`
|
|
567
|
+
+`<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>`
|
|
568
|
+
+`<div class="item"><label>Skill ara</label><input id="skillSearch" placeholder="SEO, test, erişilebilirlik, API…" oninput="filterSkills(this.value)"></div>`
|
|
569
|
+
+(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>`)
|
|
570
|
+
+`<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>`}
|
|
571
|
+
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>`}
|
|
572
|
+
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'))}
|
|
573
|
+
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'))}
|
|
574
|
+
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'))}
|
|
575
|
+
/* ---- Zamanlanmış görevler ---- */
|
|
576
|
+
const SCHED_DAYS=['Paz','Pzt','Sal','Çar','Per','Cum','Cmt'];
|
|
577
|
+
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 '—'}
|
|
578
|
+
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 '—'}}
|
|
579
|
+
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']];
|
|
580
|
+
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}
|
|
581
|
+
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}
|
|
582
|
+
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>`}
|
|
583
|
+
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()}`}
|
|
584
|
+
function csTypeChanged(){const box=$('#cs-fields');if(box)box.innerHTML=csTriggerFields($('#cs-type')?.value||'interval')}
|
|
585
|
+
// Composer modu: "now" (Görevi Ekle) veya "sched" (Zamanla). Zamanla modunda ana buton
|
|
586
|
+
// zamanlama oluşturur; böylece dolu zamanlama alanı yanlışlıkla hemen çalıştırılmaz.
|
|
587
|
+
let composerMode='now';
|
|
588
|
+
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()}
|
|
589
|
+
function composerSubmit(){if(composerMode==='sched')addScheduleFromComposer();else addTask()}
|
|
590
|
+
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}}
|
|
591
|
+
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>'}
|
|
592
|
+
async function refreshSchedules(){try{const r=await api('/api/schedules');STATE.schedules=r.schedules||[]}catch{}}
|
|
593
|
+
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)}}
|
|
594
|
+
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()}}
|
|
595
|
+
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)}}
|
|
596
|
+
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)">📁</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>`}
|
|
597
|
+
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)}}
|
|
598
|
+
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)}}
|
|
599
|
+
|
|
600
|
+
bootstrapDashboard();
|
|
601
|
+
</script>
|
|
602
|
+
</body></html>
|