shadok-ai 0.1.199 → 0.1.200
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/package.json +1 -1
- package/public/index.html +288 -127
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -426,20 +426,6 @@
|
|
|
426
426
|
dépasse (quand une session tourne, le transcript remplit exactement → inerte). */
|
|
427
427
|
.chat-pane { flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow-y: auto; }
|
|
428
428
|
|
|
429
|
-
/* Start screen */
|
|
430
|
-
#setup {
|
|
431
|
-
margin: auto;
|
|
432
|
-
width: min(520px, 92vw);
|
|
433
|
-
border: 1px solid var(--line);
|
|
434
|
-
border-radius: 8px;
|
|
435
|
-
background: var(--bg-raised);
|
|
436
|
-
padding: 28px;
|
|
437
|
-
display: flex;
|
|
438
|
-
flex-direction: column;
|
|
439
|
-
gap: 18px;
|
|
440
|
-
}
|
|
441
|
-
#setup h1 { font-family: var(--mono); font-size: 16px; color: var(--amber); font-weight: 500; }
|
|
442
|
-
#setup p.hint { color: var(--text-dim); font-size: 13px; line-height: 1.5; }
|
|
443
429
|
.field { display: flex; flex-direction: column; gap: 6px; }
|
|
444
430
|
input[type="text"] {
|
|
445
431
|
font-family: var(--mono);
|
|
@@ -852,7 +838,6 @@
|
|
|
852
838
|
/* Interactive terminal (xterm.js), per channel, filling the main area. Toggled
|
|
853
839
|
by ⌨️; when on, .chat-pane gets .terminal-mode → chat hidden, terminal shown. */
|
|
854
840
|
#terminalView { flex: 1; min-height: 0; display: none; flex-direction: column; }
|
|
855
|
-
.chat-pane.terminal-mode #setup,
|
|
856
841
|
.chat-pane.terminal-mode #transcripts,
|
|
857
842
|
.chat-pane.terminal-mode #thinking,
|
|
858
843
|
.chat-pane.terminal-mode #composer { display: none !important; }
|
|
@@ -872,6 +857,19 @@
|
|
|
872
857
|
#chanBar button.icon.on { border-color: var(--amber); color: var(--amber); background: rgba(240, 168, 72, 0.15); }
|
|
873
858
|
#xtermHost { flex: 1; min-height: 0; overflow: auto; padding: 8px 10px; }
|
|
874
859
|
#xtermHost .xterm { height: 100%; }
|
|
860
|
+
/* Aucun agent ouvert. Le panneau central n'affiche plus un formulaire à demi
|
|
861
|
+
rempli d'avance : il invite à agir, et rien d'autre. */
|
|
862
|
+
#emptyState {
|
|
863
|
+
flex: 1; min-height: 0; display: none;
|
|
864
|
+
flex-direction: column; align-items: center; justify-content: center;
|
|
865
|
+
gap: 12px; padding: 32px; text-align: center;
|
|
866
|
+
}
|
|
867
|
+
#emptyState.visible { display: flex; }
|
|
868
|
+
#emptyState .es-title {
|
|
869
|
+
font-family: var(--mono); font-size: 12px;
|
|
870
|
+
letter-spacing: 0.18em; text-transform: uppercase; color: var(--text-dim);
|
|
871
|
+
}
|
|
872
|
+
#emptyState .es-hint { color: var(--text-dim); font-size: 13px; line-height: 1.5; max-width: 44ch; }
|
|
875
873
|
.keys {
|
|
876
874
|
display: flex;
|
|
877
875
|
gap: 6px;
|
|
@@ -978,8 +976,78 @@
|
|
|
978
976
|
@media (max-width: 900px) {
|
|
979
977
|
#machine, #diffpanel { position: fixed; inset: 0; width: auto; max-width: none; z-index: 10; border-left: none; }
|
|
980
978
|
}
|
|
981
|
-
|
|
982
|
-
|
|
979
|
+
/* ── Popins ───────────────────────────────────────────────
|
|
980
|
+
UNE CLASSE, pas une liste d'ids. La règle nommait
|
|
981
|
+
#secretsOverlay/#profilesOverlay/#telegramOverlay ; le panneau des crons,
|
|
982
|
+
ajouté après, n'y figurait pas — il s'affichait donc dans le flux de la page
|
|
983
|
+
au lieu de se centrer. Toute popin hérite maintenant du comportement en
|
|
984
|
+
portant `.overlay`, il n'y a plus de liste à ne pas oublier. */
|
|
985
|
+
.overlay {
|
|
986
|
+
position: fixed; inset: 0; z-index: 50;
|
|
987
|
+
display: flex; align-items: center; justify-content: center;
|
|
988
|
+
padding: 24px;
|
|
989
|
+
background: rgba(8, 9, 13, 0.62);
|
|
990
|
+
backdrop-filter: blur(2px);
|
|
991
|
+
}
|
|
992
|
+
.overlay[hidden] { display: none; }
|
|
993
|
+
/* Le panneau était hors palette : #1b1d22 au lieu de --bg-raised, #e8e8ea au
|
|
994
|
+
lieu de --text, des bordures rgba(255,255,255,.15) et un rayon de 10px quand
|
|
995
|
+
tout le cockpit est à 6 — soit un gris et un blanc qui n'existaient nulle
|
|
996
|
+
part ailleurs. Le filet ambre en haut reprend la grammaire des instruments
|
|
997
|
+
(bord de l'onglet actif, ⌨️ allumé) : ce panneau est au premier plan. */
|
|
998
|
+
#secretsPanel, .sec-panel {
|
|
999
|
+
background: var(--bg-raised); color: var(--text);
|
|
1000
|
+
width: min(560px, 100%); max-height: min(80vh, 760px); overflow: auto;
|
|
1001
|
+
border: 1px solid var(--line); border-top: 1px solid var(--amber);
|
|
1002
|
+
border-radius: 6px; padding: 14px 16px;
|
|
1003
|
+
box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55);
|
|
1004
|
+
}
|
|
1005
|
+
/* Squelette entête / corps / pied. Seul le CORPS défile, donc l'action
|
|
1006
|
+
principale reste atteignable : le formulaire de création dépasse l'écran sur
|
|
1007
|
+
un portable, et son bouton « Start » se trouvait en bas d'une page à
|
|
1008
|
+
dérouler. Réservé aux popins qui ont un pied — les autres gardent leur
|
|
1009
|
+
rembourrage simple. */
|
|
1010
|
+
.sec-panel.modal { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
|
|
1011
|
+
.modal > .sec-head {
|
|
1012
|
+
flex: 0 0 auto; margin: 0; padding: 11px 16px;
|
|
1013
|
+
border-bottom: 1px solid var(--line);
|
|
1014
|
+
}
|
|
1015
|
+
/* Même traitement que les libellés du cockpit (.label, barre du canal) : la
|
|
1016
|
+
popin appartient visiblement à la même famille. */
|
|
1017
|
+
.modal > .sec-head strong {
|
|
1018
|
+
font-family: var(--mono); font-size: 11px; font-weight: normal;
|
|
1019
|
+
letter-spacing: 0.18em; text-transform: uppercase; color: var(--text-dim);
|
|
1020
|
+
}
|
|
1021
|
+
.modal > .sec-head strong .obj { color: var(--amber); }
|
|
1022
|
+
.modal-body { flex: 1 1 auto; overflow: auto; padding: 14px 16px; }
|
|
1023
|
+
.modal-foot {
|
|
1024
|
+
flex: 0 0 auto; display: flex; align-items: center; justify-content: flex-end;
|
|
1025
|
+
gap: 8px; padding: 11px 16px;
|
|
1026
|
+
border-top: 1px solid var(--line); background: rgba(0, 0, 0, 0.18);
|
|
1027
|
+
}
|
|
1028
|
+
/* Pousse ce qui suit à droite : un pied peut porter une note à gauche. */
|
|
1029
|
+
.modal-foot .foot-note { margin-right: auto; font-size: 11px; color: var(--text-dim); }
|
|
1030
|
+
.sec-panel :focus-visible { outline: 2px solid var(--amber); outline-offset: 1px; }
|
|
1031
|
+
/* Les <select> et les champs numériques n'étaient couverts par aucune règle :
|
|
1032
|
+
ils tombaient sur le rendu par défaut du navigateur — clair — au milieu d'un
|
|
1033
|
+
panneau sombre. */
|
|
1034
|
+
.sec-panel select, .sec-panel input[type=number], .sec-panel input[type=time] {
|
|
1035
|
+
padding: 6px 8px; border-radius: 6px; border: 1px solid var(--line);
|
|
1036
|
+
background: rgba(0, 0, 0, 0.25); color: inherit; font-family: inherit; font-size: inherit;
|
|
1037
|
+
}
|
|
1038
|
+
/* Une précision dans un libellé ne SE CRIE PAS : `.label` met en majuscules et
|
|
1039
|
+
espace les lettres, ce qui rendait une phrase entière illisible. */
|
|
1040
|
+
.label .check-hint { text-transform: none; letter-spacing: normal; font-weight: normal; }
|
|
1041
|
+
#cronList { list-style: none; padding: 0; margin: 0 0 14px; }
|
|
1042
|
+
#cronList li { padding: 6px 8px; border-radius: 6px; }
|
|
1043
|
+
#cronList li:nth-child(odd) { background: rgba(255, 255, 255, 0.04); }
|
|
1044
|
+
.cron-form { border-top: 1px solid var(--line); padding-top: 12px; }
|
|
1045
|
+
.cron-form .field { margin: 12px 0; }
|
|
1046
|
+
.cron-form .field:last-child { margin-bottom: 0; }
|
|
1047
|
+
/* `.field` est en colonne : sans redresser la direction ici, les trois champs
|
|
1048
|
+
s'empilaient — et `align-items: center` les centrait au lieu de les aligner. */
|
|
1049
|
+
.cron-when { flex-direction: row; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
1050
|
+
.cron-when input[type=number] { width: 76px; }
|
|
983
1051
|
.sec-panel .field { margin: 8px 0; }
|
|
984
1052
|
.sec-panel textarea, .sec-panel input[type=text], .sec-panel input:not([type]) { width: 100%; box-sizing: border-box; padding: 6px 8px; border-radius: 6px; border: 1px solid rgba(255,255,255,.15); background: rgba(0,0,0,.25); color: inherit; font-family: inherit; }
|
|
985
1053
|
#profilesList { list-style: none; padding: 0; margin: 0 0 8px; }
|
|
@@ -1059,7 +1127,7 @@
|
|
|
1059
1127
|
<div class="vm-hint" id="vmModeHint"></div>
|
|
1060
1128
|
</div>
|
|
1061
1129
|
|
|
1062
|
-
<div id="secretsOverlay" hidden>
|
|
1130
|
+
<div id="secretsOverlay" class="overlay" hidden>
|
|
1063
1131
|
<div id="secretsPanel">
|
|
1064
1132
|
<div class="sec-head">
|
|
1065
1133
|
<strong>Secret vault</strong>
|
|
@@ -1075,36 +1143,106 @@
|
|
|
1075
1143
|
</div>
|
|
1076
1144
|
</div>
|
|
1077
1145
|
|
|
1078
|
-
<div id="cronOverlay" hidden>
|
|
1079
|
-
|
|
1146
|
+
<div id="cronOverlay" class="overlay" hidden>
|
|
1147
|
+
<!-- Le panneau portait id="secretsPanel" — un id en DOUBLON de la vraie popin
|
|
1148
|
+
des secrets, donc $("secretsPanel") pouvait tomber sur le mauvais. -->
|
|
1149
|
+
<div id="cronPanel" class="sec-panel modal">
|
|
1080
1150
|
<div class="sec-head">
|
|
1081
|
-
<strong>⏰ Schedule — <span id="cronChanName">this agent</span></strong>
|
|
1151
|
+
<strong>⏰ Schedule — <span class="obj" id="cronChanName">this agent</span></strong>
|
|
1082
1152
|
<button id="cronClose" title="Close">✕</button>
|
|
1083
1153
|
</div>
|
|
1084
|
-
<
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
<
|
|
1088
|
-
<
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
<
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
<input id="
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1154
|
+
<div class="modal-body">
|
|
1155
|
+
<p class="sec-note">Prompts sent to this agent on a timer — monitoring, reporting. The server fires them even when nobody's watching; answers land here and in Telegram.</p>
|
|
1156
|
+
<p class="sec-note"><b>Simplest way:</b> just ask the agent — « surveille X chaque matin et alerte-moi si Y ». It writes the check and schedules it itself.</p>
|
|
1157
|
+
<ul id="cronList"></ul>
|
|
1158
|
+
<form id="cronAdd" class="cron-form">
|
|
1159
|
+
<div class="field">
|
|
1160
|
+
<span class="label">Prompt to run</span>
|
|
1161
|
+
<textarea id="cronPrompt" rows="2" placeholder="e.g. « Rédige une alerte à partir de l'anomalie détectée »"></textarea>
|
|
1162
|
+
</div>
|
|
1163
|
+
<div class="field">
|
|
1164
|
+
<span class="label">Check command <span class="check-hint">— optional; prints nothing = nothing to report, and the agent stays asleep (0 tokens)</span></span>
|
|
1165
|
+
<input id="cronCheck" placeholder="e.g. ~/scripts/check-budget.sh" autocomplete="off" spellcheck="false" />
|
|
1166
|
+
</div>
|
|
1167
|
+
<div class="field cron-when">
|
|
1168
|
+
<select id="cronKind" aria-label="How often">
|
|
1169
|
+
<option value="interval">Every…</option>
|
|
1170
|
+
<option value="daily">Daily at…</option>
|
|
1171
|
+
</select>
|
|
1172
|
+
<span id="cronIntervalFields">
|
|
1173
|
+
<input id="cronEvery" type="number" min="1" value="60" aria-label="Interval">
|
|
1174
|
+
<select id="cronUnit" aria-label="Unit"><option value="1">minutes</option><option value="60">hours</option></select>
|
|
1175
|
+
</span>
|
|
1176
|
+
<span id="cronDailyFields" hidden>
|
|
1177
|
+
<input id="cronTime" type="time" value="09:00" aria-label="Time of day">
|
|
1178
|
+
</span>
|
|
1179
|
+
</div>
|
|
1180
|
+
</form>
|
|
1181
|
+
</div>
|
|
1182
|
+
<div class="modal-foot">
|
|
1183
|
+
<span class="foot-note" id="cronTzNote"></span>
|
|
1184
|
+
<button type="submit" form="cronAdd" class="primary">Add schedule</button>
|
|
1185
|
+
</div>
|
|
1186
|
+
</div>
|
|
1187
|
+
</div>
|
|
1188
|
+
|
|
1189
|
+
<div id="setupOverlay" class="overlay" hidden>
|
|
1190
|
+
<div id="setupPanel" class="sec-panel modal">
|
|
1191
|
+
<div class="sec-head">
|
|
1192
|
+
<strong>New <span class="obj">agent</span></strong>
|
|
1193
|
+
<button id="setupClose" title="Close">✕</button>
|
|
1194
|
+
</div>
|
|
1195
|
+
<div class="modal-body">
|
|
1196
|
+
<p class="sec-note">A real Claude Code session, driven inside a pseudo-terminal. Open as many as you want Claudes working in parallel.</p>
|
|
1197
|
+
<div class="field" id="profileField">
|
|
1198
|
+
<div class="field-head">
|
|
1199
|
+
<span class="label">Which agent?</span>
|
|
1200
|
+
<button type="button" class="linkish" id="editProfilesLink" title="Create or edit agent profiles">✎ edit profiles</button>
|
|
1201
|
+
</div>
|
|
1202
|
+
<div id="profileGrid" role="radiogroup" aria-label="Agent profile"></div>
|
|
1203
|
+
<span class="check-hint" id="profileNaNote" hidden>Applies to new sessions only.</span>
|
|
1204
|
+
</div>
|
|
1205
|
+
<div class="field">
|
|
1206
|
+
<span class="label">Working directory</span>
|
|
1207
|
+
<input type="text" id="cwdInput" placeholder="/path/to/project">
|
|
1208
|
+
</div>
|
|
1209
|
+
<label class="check" id="worktreeField">
|
|
1210
|
+
<input type="checkbox" id="worktreeInput" checked>
|
|
1211
|
+
<span>Isolate in a git worktree <span class="check-hint">— the agent edits a fresh branch; review & merge from the Diff panel. Auto-removed on close if unused, kept if it has changes. (new sessions only)</span></span>
|
|
1212
|
+
</label>
|
|
1213
|
+
<details class="fold" id="advancedField">
|
|
1214
|
+
<summary>Advanced — resume an existing session</summary>
|
|
1215
|
+
<div class="field">
|
|
1216
|
+
<div class="radio-row">
|
|
1217
|
+
<label><input type="radio" name="mode" value="new" checked> new session</label>
|
|
1218
|
+
<label><input type="radio" name="mode" value="continue"> latest in directory</label>
|
|
1219
|
+
<label><input type="radio" name="mode" value="resume"> by id</label>
|
|
1220
|
+
</div>
|
|
1221
|
+
</div>
|
|
1222
|
+
<div class="field" id="resumeField" hidden>
|
|
1223
|
+
<span class="label">Session id</span>
|
|
1224
|
+
<input type="text" id="resumeInput" placeholder="5fe046dd-…">
|
|
1225
|
+
<span class="label">Sessions in this directory</span>
|
|
1226
|
+
<div class="session-list" id="sessionList"></div>
|
|
1227
|
+
</div>
|
|
1228
|
+
</details>
|
|
1229
|
+
<details class="fold" id="recoverField" hidden>
|
|
1230
|
+
<summary>Reopen a past session <span class="fold-count" id="recoverCount"></span></summary>
|
|
1231
|
+
<div class="recover-list" id="recoverList"></div>
|
|
1232
|
+
</details>
|
|
1233
|
+
<details class="fold" id="liveField" hidden>
|
|
1234
|
+
<summary>Agents running now <span class="fold-count" id="liveCount"></span></summary>
|
|
1235
|
+
<div class="recover-list" id="liveList"></div>
|
|
1236
|
+
</details>
|
|
1237
|
+
</div>
|
|
1238
|
+
<div class="modal-foot">
|
|
1239
|
+
<button id="setupCancel">Cancel</button>
|
|
1240
|
+
<button class="primary" id="startBtn">Start agent</button>
|
|
1241
|
+
</div>
|
|
1104
1242
|
</div>
|
|
1105
1243
|
</div>
|
|
1106
1244
|
|
|
1107
|
-
<div id="profilesOverlay" hidden>
|
|
1245
|
+
<div id="profilesOverlay" class="overlay" hidden>
|
|
1108
1246
|
<div id="profilesPanel" class="sec-panel">
|
|
1109
1247
|
<div class="sec-head">
|
|
1110
1248
|
<strong>Agent profiles</strong>
|
|
@@ -1127,7 +1265,7 @@
|
|
|
1127
1265
|
</div>
|
|
1128
1266
|
</div>
|
|
1129
1267
|
|
|
1130
|
-
<div id="telegramOverlay" hidden>
|
|
1268
|
+
<div id="telegramOverlay" class="overlay" hidden>
|
|
1131
1269
|
<div id="telegramPanel" class="sec-panel">
|
|
1132
1270
|
<div class="sec-head">
|
|
1133
1271
|
<strong>Telegram bot</strong>
|
|
@@ -1179,52 +1317,13 @@
|
|
|
1179
1317
|
</span>
|
|
1180
1318
|
</div>
|
|
1181
1319
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
<
|
|
1188
|
-
|
|
1189
|
-
<span class="label">Which agent?</span>
|
|
1190
|
-
<button type="button" class="linkish" id="editProfilesLink" title="Create or edit agent profiles">✎ edit profiles</button>
|
|
1191
|
-
</div>
|
|
1192
|
-
<div id="profileGrid" role="radiogroup" aria-label="Agent profile"></div>
|
|
1193
|
-
<span class="check-hint" id="profileNaNote" hidden>Applies to new sessions only.</span>
|
|
1194
|
-
</div>
|
|
1195
|
-
<div class="field">
|
|
1196
|
-
<span class="label">Working directory</span>
|
|
1197
|
-
<input type="text" id="cwdInput" placeholder="/path/to/project">
|
|
1198
|
-
</div>
|
|
1199
|
-
<label class="check" id="worktreeField">
|
|
1200
|
-
<input type="checkbox" id="worktreeInput" checked>
|
|
1201
|
-
<span>Isolate in a git worktree <span class="check-hint">— the agent edits a fresh branch; review & merge from the Diff panel. Auto-removed on close if unused, kept if it has changes. (new sessions only)</span></span>
|
|
1202
|
-
</label>
|
|
1203
|
-
<details class="fold" id="advancedField">
|
|
1204
|
-
<summary>Advanced — resume an existing session</summary>
|
|
1205
|
-
<div class="field">
|
|
1206
|
-
<div class="radio-row">
|
|
1207
|
-
<label><input type="radio" name="mode" value="new" checked> new session</label>
|
|
1208
|
-
<label><input type="radio" name="mode" value="continue"> latest in directory</label>
|
|
1209
|
-
<label><input type="radio" name="mode" value="resume"> by id</label>
|
|
1210
|
-
</div>
|
|
1211
|
-
</div>
|
|
1212
|
-
<div class="field" id="resumeField" hidden>
|
|
1213
|
-
<span class="label">Session id</span>
|
|
1214
|
-
<input type="text" id="resumeInput" placeholder="5fe046dd-…">
|
|
1215
|
-
<span class="label">Sessions in this directory</span>
|
|
1216
|
-
<div class="session-list" id="sessionList"></div>
|
|
1217
|
-
</div>
|
|
1218
|
-
</details>
|
|
1219
|
-
<details class="fold" id="recoverField" hidden>
|
|
1220
|
-
<summary>Reopen a past session <span class="fold-count" id="recoverCount"></span></summary>
|
|
1221
|
-
<div class="recover-list" id="recoverList"></div>
|
|
1222
|
-
</details>
|
|
1223
|
-
<details class="fold" id="liveField" hidden>
|
|
1224
|
-
<summary>Agents running now <span class="fold-count" id="liveCount"></span></summary>
|
|
1225
|
-
<div class="recover-list" id="liveList"></div>
|
|
1226
|
-
</details>
|
|
1227
|
-
<button class="primary" id="startBtn">Start agent</button>
|
|
1320
|
+
<!-- Aucun agent : une invitation, pas un formulaire pré-rempli. La création
|
|
1321
|
+
se fait dans une popin (#setupOverlay) et ne crée le canal qu'à la
|
|
1322
|
+
validation — ouvrir puis renoncer ne laisse plus d'onglet mort-né. -->
|
|
1323
|
+
<div id="emptyState">
|
|
1324
|
+
<span class="es-title">No agent open</span>
|
|
1325
|
+
<p class="es-hint">Each agent is its own Claude Code session, working on its own directory. Start one and it takes the controls.</p>
|
|
1326
|
+
<button class="primary" id="emptyNewBtn">+ new agent</button>
|
|
1228
1327
|
</div>
|
|
1229
1328
|
|
|
1230
1329
|
<div id="transcripts"></div>
|
|
@@ -1299,7 +1398,7 @@
|
|
|
1299
1398
|
const $ = (id) => document.getElementById(id);
|
|
1300
1399
|
const tabbar = $("tabbar"), newTabBtn = $("newTab"), newGroupBtn = $("newGroup");
|
|
1301
1400
|
const ungroupedEl = $("ungrouped"), groupsEl = $("groups");
|
|
1302
|
-
const
|
|
1401
|
+
const transcripts = $("transcripts"), thinking = $("thinking");
|
|
1303
1402
|
const composer = $("composer"), promptInput = $("promptInput"), sendBtn = $("sendBtn");
|
|
1304
1403
|
const machine = $("machine"), screenEl = $("screen");
|
|
1305
1404
|
|
|
@@ -1315,7 +1414,7 @@
|
|
|
1315
1414
|
.then((r) => r.json())
|
|
1316
1415
|
.then((d) => {
|
|
1317
1416
|
serverCwd = d.cwd || "";
|
|
1318
|
-
if (
|
|
1417
|
+
if (!$("setupOverlay").hidden && !$("cwdInput").value) $("cwdInput").value = serverCwd || "";
|
|
1319
1418
|
})
|
|
1320
1419
|
.catch(() => {});
|
|
1321
1420
|
|
|
@@ -1915,7 +2014,8 @@
|
|
|
1915
2014
|
refreshTitle();
|
|
1916
2015
|
if (active === tab) {
|
|
1917
2016
|
if (tabs.length) activate(tabs[tabs.length - 1]);
|
|
1918
|
-
|
|
2017
|
+
// Plus aucun agent : l'invitation, pas un onglet « setup » fantôme.
|
|
2018
|
+
else { active = null; refreshChrome(); }
|
|
1919
2019
|
}
|
|
1920
2020
|
}
|
|
1921
2021
|
|
|
@@ -2126,9 +2226,38 @@
|
|
|
2126
2226
|
refreshBadge();
|
|
2127
2227
|
}
|
|
2128
2228
|
|
|
2129
|
-
/**
|
|
2229
|
+
/**
|
|
2230
|
+
* Keeps header, composer, channel bar and engine room in sync with the active
|
|
2231
|
+
* channel — ou avec l'ABSENCE de canal.
|
|
2232
|
+
*
|
|
2233
|
+
* `active` peut désormais être nul : la création vit dans une popin et ne crée
|
|
2234
|
+
* plus d'onglet « setup » d'avance, donc fermer le dernier agent ne laisse plus
|
|
2235
|
+
* un formulaire à la place. Tout ce qui décrit un canal retombe alors sur « — »
|
|
2236
|
+
* et le panneau central montre l'invitation.
|
|
2237
|
+
*/
|
|
2130
2238
|
function refreshChrome() {
|
|
2131
2239
|
const t = active;
|
|
2240
|
+
if (!t) {
|
|
2241
|
+
$("led").className = "led ";
|
|
2242
|
+
$("state").textContent = "no agent";
|
|
2243
|
+
$("cwdView").textContent = "—";
|
|
2244
|
+
$("sessionView").textContent = "—";
|
|
2245
|
+
$("sessionView").title = "";
|
|
2246
|
+
$("branchGauge").hidden = true;
|
|
2247
|
+
composer.hidden = true;
|
|
2248
|
+
$("chanBar").hidden = true;
|
|
2249
|
+
thinking.classList.remove("visible");
|
|
2250
|
+
screenEl.textContent = "";
|
|
2251
|
+
// Programmer ou arrêter n'a pas de cible : mieux vaut des boutons éteints
|
|
2252
|
+
// qu'une popin qui parle d'un agent inexistant.
|
|
2253
|
+
$("cronBtn").disabled = true;
|
|
2254
|
+
$("stopBtn").disabled = true;
|
|
2255
|
+
$("emptyState").classList.add("visible");
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
$("emptyState").classList.remove("visible");
|
|
2259
|
+
$("cronBtn").disabled = !t.sessionId;
|
|
2260
|
+
$("stopBtn").disabled = false;
|
|
2132
2261
|
$("led").className = "led " +
|
|
2133
2262
|
({ setup: "", connecting: "busy", ready: "on", busy: "busy", dead: "off" }[t.status]);
|
|
2134
2263
|
$("state").textContent = t.stateText;
|
|
@@ -2139,33 +2268,14 @@
|
|
|
2139
2268
|
$("sessionView").title = t.sessionId || "";
|
|
2140
2269
|
$("branchGauge").hidden = !t.branch;
|
|
2141
2270
|
$("branchView").textContent = t.branch || "—";
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
// agent » : il n'y a pas encore de canal à programmer, arrêter ou ouvrir en
|
|
2146
|
-
// terminal. Le nom vit ici (et non dans syncTerm) pour qu'un renommage ou un
|
|
2147
|
-
// changement de canal le mette à jour même terminal fermé.
|
|
2148
|
-
$("chanBar").hidden = t.status === "setup";
|
|
2271
|
+
composer.hidden = false;
|
|
2272
|
+
// La barre du canal accompagne les deux vues du canal.
|
|
2273
|
+
$("chanBar").hidden = false;
|
|
2149
2274
|
$("chanBarName").textContent = t.nameEl ? t.nameEl.textContent : "";
|
|
2150
2275
|
$("chanBarName").title = t.cwd || "";
|
|
2151
2276
|
sendBtn.disabled = t.status !== "ready" || !!t.pendingChoices;
|
|
2152
2277
|
thinking.classList.toggle("visible", t.status === "busy");
|
|
2153
2278
|
screenEl.textContent = t.screenText;
|
|
2154
|
-
if (t.status === "setup") {
|
|
2155
|
-
// Only pre-fill when empty — never clobber what the user is typing.
|
|
2156
|
-
if (!$("cwdInput").value) {
|
|
2157
|
-
$("cwdInput").value =
|
|
2158
|
-
t.cwd || localStorage.getItem("cp.cwd") || serverCwd || "";
|
|
2159
|
-
}
|
|
2160
|
-
const last = localStorage.getItem("cp.session");
|
|
2161
|
-
if (last && !$("resumeInput").value) $("resumeInput").value = last;
|
|
2162
|
-
$("startBtn").disabled = false;
|
|
2163
|
-
// refreshChrome tourne souvent : on ne rappelle la mémoire qu'au premier
|
|
2164
|
-
// passage sur CET onglet, sinon on écraserait le choix de l'utilisateur.
|
|
2165
|
-
if (profileTab !== t) { profileTab = t; profileTouched = false; applyRememberedProfile(); }
|
|
2166
|
-
refreshRecoverList();
|
|
2167
|
-
refreshLiveList();
|
|
2168
|
-
}
|
|
2169
2279
|
}
|
|
2170
2280
|
|
|
2171
2281
|
/** Paints a channel's context-window usage bar + tiny number in its tab. */
|
|
@@ -2778,9 +2888,33 @@
|
|
|
2778
2888
|
}
|
|
2779
2889
|
}
|
|
2780
2890
|
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2891
|
+
/**
|
|
2892
|
+
* Ouvre la popin de création. Elle ne crée RIEN : l'onglet naît au « Start
|
|
2893
|
+
* agent » (cf. startNewAgent). Ouvrir puis renoncer ne laissait auparavant pas
|
|
2894
|
+
* seulement un onglet mort-né — il devenait le canal actif, avec son formulaire
|
|
2895
|
+
* à la place de la conversation qu'on regardait.
|
|
2896
|
+
*/
|
|
2897
|
+
function openSetup() {
|
|
2898
|
+
// Ne jamais écraser ce qui est déjà tapé (la popin peut se rouvrir).
|
|
2899
|
+
if (!$("cwdInput").value) {
|
|
2900
|
+
$("cwdInput").value = (active && active.cwd) || localStorage.getItem("cp.cwd") || serverCwd || "";
|
|
2901
|
+
}
|
|
2902
|
+
const last = localStorage.getItem("cp.session");
|
|
2903
|
+
if (last && !$("resumeInput").value) $("resumeInput").value = last;
|
|
2904
|
+
$("startBtn").disabled = false;
|
|
2905
|
+
// Le profil retenu n'est rappelé qu'à l'OUVERTURE : rappelé en continu, il
|
|
2906
|
+
// écraserait le choix en cours de saisie.
|
|
2907
|
+
profileTouched = false;
|
|
2908
|
+
applyRememberedProfile();
|
|
2909
|
+
refreshRecoverList();
|
|
2910
|
+
refreshLiveList();
|
|
2911
|
+
$("setupOverlay").hidden = false;
|
|
2912
|
+
$("cwdInput").focus();
|
|
2913
|
+
}
|
|
2914
|
+
function closeSetup() { $("setupOverlay").hidden = true; }
|
|
2915
|
+
|
|
2916
|
+
/** Valide le formulaire → C'EST ICI que le canal est créé, et pas avant. */
|
|
2917
|
+
function startNewAgent() {
|
|
2784
2918
|
const cwd = $("cwdInput").value.trim();
|
|
2785
2919
|
const mode = document.querySelector('input[name="mode"]:checked').value;
|
|
2786
2920
|
const msg = { cwd };
|
|
@@ -2797,7 +2931,9 @@
|
|
|
2797
2931
|
if (cwd) localStorage.setItem("cp.profile:" + cwd, selectedProfile);
|
|
2798
2932
|
}
|
|
2799
2933
|
$("startBtn").disabled = true;
|
|
2800
|
-
|
|
2934
|
+
closeSetup();
|
|
2935
|
+
// createTab active l'onglet créé ; launchTab ouvre le lien.
|
|
2936
|
+
launchTab(createTab(), msg);
|
|
2801
2937
|
}
|
|
2802
2938
|
|
|
2803
2939
|
// ── Version display + auto-reload ─────────────────────────────────────────
|
|
@@ -3189,13 +3325,17 @@
|
|
|
3189
3325
|
}
|
|
3190
3326
|
|
|
3191
3327
|
// ── Controls ────────────────────────────────────────────
|
|
3192
|
-
$("startBtn").addEventListener("click",
|
|
3328
|
+
$("startBtn").addEventListener("click", startNewAgent);
|
|
3329
|
+
$("setupClose").addEventListener("click", closeSetup);
|
|
3330
|
+
$("setupCancel").addEventListener("click", closeSetup);
|
|
3331
|
+
$("setupOverlay").addEventListener("click", (e) => { if (e.target === $("setupOverlay")) closeSetup(); });
|
|
3332
|
+
$("emptyNewBtn").addEventListener("click", openSetup);
|
|
3193
3333
|
document.querySelectorAll('input[name="mode"]').forEach((r) =>
|
|
3194
3334
|
r.addEventListener("change", () => {
|
|
3195
3335
|
const mode = document.querySelector('input[name="mode"]:checked').value;
|
|
3196
3336
|
const isNew = mode === "new";
|
|
3197
3337
|
$("resumeField").hidden = mode !== "resume";
|
|
3198
|
-
// Profil et worktree ne valent que pour une session neuve :
|
|
3338
|
+
// Profil et worktree ne valent que pour une session neuve : startNewAgent
|
|
3199
3339
|
// les ignore déjà en resume/continue, l'UI cesse simplement de le cacher.
|
|
3200
3340
|
$("profileField").classList.toggle("na", !isNew);
|
|
3201
3341
|
$("profileNaNote").hidden = isNew;
|
|
@@ -3213,7 +3353,7 @@
|
|
|
3213
3353
|
// Agents can appear while the setup screen sits open (pilotctl spawns one,
|
|
3214
3354
|
// another tab starts one) — keep the list current without a reload.
|
|
3215
3355
|
setInterval(() => {
|
|
3216
|
-
if (
|
|
3356
|
+
if (!$("setupOverlay").hidden) refreshLiveList();
|
|
3217
3357
|
}, 5000);
|
|
3218
3358
|
|
|
3219
3359
|
function submitPrompt(t, text, force) {
|
|
@@ -3323,6 +3463,7 @@
|
|
|
3323
3463
|
}
|
|
3324
3464
|
promptInput.addEventListener("input", () => {
|
|
3325
3465
|
autosizePrompt();
|
|
3466
|
+
if (!active) return; // aucun agent : le composer est masqué, mais pas de crash gratuit
|
|
3326
3467
|
active.draft = promptInput.value;
|
|
3327
3468
|
persistDraft(active);
|
|
3328
3469
|
});
|
|
@@ -3452,7 +3593,7 @@
|
|
|
3452
3593
|
$("refreshDiff").addEventListener("click", loadDiff);
|
|
3453
3594
|
document.querySelectorAll(".keys button[data-key]").forEach((b) =>
|
|
3454
3595
|
b.addEventListener("click", () => {
|
|
3455
|
-
if (active.ws) active.ws.send(JSON.stringify({ type: "key", key: b.dataset.key }));
|
|
3596
|
+
if (active && active.ws) active.ws.send(JSON.stringify({ type: "key", key: b.dataset.key }));
|
|
3456
3597
|
})
|
|
3457
3598
|
);
|
|
3458
3599
|
// La touche Échap envoie esc à la session active (comme le bouton Esc de
|
|
@@ -3469,6 +3610,9 @@
|
|
|
3469
3610
|
// 1. Overlays / panneaux ouverts → fermer, pas d'esc.
|
|
3470
3611
|
if (!$("secretsOverlay").hidden) { $("secretsOverlay").hidden = true; e.preventDefault(); return; }
|
|
3471
3612
|
if (!$("profilesOverlay").hidden) { closeProfilesPanel(); e.preventDefault(); return; }
|
|
3613
|
+
if (!$("setupOverlay").hidden) { closeSetup(); e.preventDefault(); return; }
|
|
3614
|
+
if (!$("cronOverlay").hidden) { $("cronOverlay").hidden = true; e.preventDefault(); return; }
|
|
3615
|
+
if (!$("telegramOverlay").hidden) { $("telegramOverlay").hidden = true; e.preventDefault(); return; }
|
|
3472
3616
|
if ($("diffpanel").classList.contains("open")) { $("diffpanel").classList.remove("open"); e.preventDefault(); return; }
|
|
3473
3617
|
// 2. Dialogue clickable en attente de réponse → ne rien faire.
|
|
3474
3618
|
if (active && active.dialogBubble) return;
|
|
@@ -3488,11 +3632,11 @@
|
|
|
3488
3632
|
}
|
|
3489
3633
|
});
|
|
3490
3634
|
$("settleBtn").addEventListener("click", () => {
|
|
3491
|
-
if (active.ws && active.status === "ready")
|
|
3635
|
+
if (active && active.ws && active.status === "ready")
|
|
3492
3636
|
active.ws.send(JSON.stringify({ type: "settle" }));
|
|
3493
3637
|
});
|
|
3494
3638
|
$("stopBtn").addEventListener("click", () => {
|
|
3495
|
-
if (active.ws) active.ws.send(JSON.stringify({ type: "stop" }));
|
|
3639
|
+
if (active && active.ws) active.ws.send(JSON.stringify({ type: "stop" }));
|
|
3496
3640
|
});
|
|
3497
3641
|
|
|
3498
3642
|
// ── Secret vault panel (central, global; profiles pick which to inject) ──
|
|
@@ -3577,8 +3721,23 @@
|
|
|
3577
3721
|
if (s.kind === "interval") return s.everyMin % 60 === 0 ? "every " + (s.everyMin / 60) + "h" : "every " + s.everyMin + "m";
|
|
3578
3722
|
return "daily at " + String(s.hour).padStart(2, "0") + ":" + String(s.minute).padStart(2, "0");
|
|
3579
3723
|
}
|
|
3724
|
+
/**
|
|
3725
|
+
* Fuseau réellement appliqué aux horaires quotidiens, dans le pied de la popin.
|
|
3726
|
+
* Le panneau annonçait « Times are server-local », ce qui ne dit pas 9h OÙ —
|
|
3727
|
+
* or le serveur peut tourner dans un autre fuseau que le navigateur.
|
|
3728
|
+
*/
|
|
3729
|
+
async function showCronTz() {
|
|
3730
|
+
const el = $("cronTzNote");
|
|
3731
|
+
el.textContent = "";
|
|
3732
|
+
try {
|
|
3733
|
+
const tz = await (await fetch("/timezone")).json();
|
|
3734
|
+
const zone = tz.timezone || tz.system;
|
|
3735
|
+
if (zone) el.textContent = "Daily times run in " + zone;
|
|
3736
|
+
} catch { /* la popin marche très bien sans cette note */ }
|
|
3737
|
+
}
|
|
3580
3738
|
async function renderCrons() {
|
|
3581
3739
|
const ul = $("cronList"); ul.innerHTML = "";
|
|
3740
|
+
showCronTz();
|
|
3582
3741
|
$("cronChanName").textContent = (active && (active.name || (active.sessionId || "").slice(0, 8))) || "no agent";
|
|
3583
3742
|
if (!active || !active.sessionId) { ul.innerHTML = '<li><span style="opacity:.6">Open an agent to schedule prompts.</span></li>'; return; }
|
|
3584
3743
|
let all = [];
|
|
@@ -3937,7 +4096,7 @@
|
|
|
3937
4096
|
el.value = "";
|
|
3938
4097
|
});
|
|
3939
4098
|
|
|
3940
|
-
newTabBtn.addEventListener("click",
|
|
4099
|
+
newTabBtn.addEventListener("click", openSetup);
|
|
3941
4100
|
newGroupBtn.addEventListener("click", () => {
|
|
3942
4101
|
createGroup();
|
|
3943
4102
|
persistGroups();
|
|
@@ -4086,7 +4245,9 @@
|
|
|
4086
4245
|
if (i !== targetIdx) launchTab(pending[i].t, pending[i].msg);
|
|
4087
4246
|
}
|
|
4088
4247
|
} else {
|
|
4089
|
-
|
|
4248
|
+
// Rien à restaurer : l'invitation. Le formulaire ne s'ouvre pas de force —
|
|
4249
|
+
// on peut aussi arriver là pour regarder, ou lancer depuis Telegram.
|
|
4250
|
+
refreshChrome();
|
|
4090
4251
|
// Nothing to restore: enable persistence for user actions, but do NOT
|
|
4091
4252
|
// push a blank list (a failed /channels fetch must never wipe the store).
|
|
4092
4253
|
restoring = false;
|