@lelouchhe/webagent 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +58 -23
  2. package/bin/webagent.mjs +119 -8
  3. package/config.toml +96 -3
  4. package/dist/index.html +64 -41
  5. package/dist/js/app.GSAIYHML.js +4 -0
  6. package/dist/js/chunk.AJZBJBMO.js +1 -0
  7. package/dist/js/chunk.CGWFHJI2.js +76 -0
  8. package/dist/js/chunk.D4ZYHJAM.js +1 -0
  9. package/dist/js/chunk.VZXGXFNN.js +5 -0
  10. package/dist/js/login.PYIK52HN.js +1 -0
  11. package/dist/js/viewer.6DT53STL.js +1 -0
  12. package/dist/login.html +49 -0
  13. package/dist/share-viewer.00gubshk.css +114 -0
  14. package/dist/share-viewer.html +53 -0
  15. package/dist/styles.012p32dz.css +1443 -0
  16. package/dist/sw.js +79 -27
  17. package/dist/theme-init.js +6 -0
  18. package/lib/agent-detect.js +110 -0
  19. package/lib/atomic-write.js +50 -0
  20. package/lib/attachment-dispatch.js +86 -0
  21. package/lib/attachment-interceptor.js +130 -0
  22. package/lib/attachment-labels.js +139 -0
  23. package/lib/attachments.js +154 -0
  24. package/lib/auth-middleware.js +102 -0
  25. package/lib/auth-store.js +269 -0
  26. package/lib/auth.js +89 -0
  27. package/lib/bootstrap.js +70 -0
  28. package/lib/bridge.js +244 -93
  29. package/lib/client-registry.js +60 -0
  30. package/lib/config.js +123 -9
  31. package/lib/daemon.js +175 -41
  32. package/lib/event-handler.js +209 -91
  33. package/lib/log-fmt.js +67 -0
  34. package/lib/log.js +83 -0
  35. package/lib/message-cleanup.js +48 -0
  36. package/lib/mode-bucket.js +62 -0
  37. package/lib/preflight.js +195 -0
  38. package/lib/push-service.js +338 -45
  39. package/lib/routes.js +1202 -144
  40. package/lib/server.js +149 -33
  41. package/lib/session-manager.js +164 -18
  42. package/lib/session-state.js +160 -0
  43. package/lib/sessions-anchor.js +28 -0
  44. package/lib/share/cleanup.js +45 -0
  45. package/lib/share/routes.js +972 -0
  46. package/lib/share/sanitize.js +179 -0
  47. package/lib/sse-manager.js +94 -8
  48. package/lib/sse-ticket.js +45 -0
  49. package/lib/startup-checks.js +94 -0
  50. package/lib/store.js +624 -30
  51. package/lib/title-service.js +42 -9
  52. package/lib/tokens.js +50 -0
  53. package/lib/types.js +23 -0
  54. package/package.json +38 -4
  55. package/dist/js/app.2562YGRO.js +0 -10
  56. package/dist/styles.008ve1hx.css +0 -669
  57. package/lib/shared/constants.js +0 -17
@@ -1,669 +0,0 @@
1
- :root {
2
- --bg: #0d1117;
3
- --surface: #161b22;
4
- --border: #30363d;
5
- --text: #c9d1d9;
6
- --text-dim: #6e7681;
7
- --accent: #58a6ff;
8
- --green: #3fb950;
9
- --yellow: #d29922;
10
- --red: #f85149;
11
- --blue: #58a6ff;
12
- --radius: 4px;
13
- --user-bg: #1e3a5f;
14
- --code-bg: #0d2818;
15
- --permission-bg: #1c1305;
16
- --permission-title: #d29922;
17
- }
18
- [data-theme="light"] {
19
- --bg: #ffffff;
20
- --surface: #f6f8fa;
21
- --border: #d0d7de;
22
- --text: #1f2328;
23
- --text-dim: #656d76;
24
- --accent: #0969da;
25
- --green: #1a7f37;
26
- --yellow: #9a6700;
27
- --red: #cf222e;
28
- --blue: #0969da;
29
- --user-bg: #ddf4ff;
30
- --code-bg: #f0faf0;
31
- --permission-bg: #fff4cc;
32
- --permission-title: #7a4f00;
33
- }
34
- @media (prefers-color-scheme: light) {
35
- [data-theme="auto"] {
36
- --bg: #ffffff;
37
- --surface: #f6f8fa;
38
- --border: #d0d7de;
39
- --text: #1f2328;
40
- --text-dim: #656d76;
41
- --accent: #0969da;
42
- --green: #1a7f37;
43
- --yellow: #9a6700;
44
- --red: #cf222e;
45
- --blue: #0969da;
46
- --user-bg: #ddf4ff;
47
- --code-bg: #f0faf0;
48
- --permission-bg: #fff4cc;
49
- --permission-title: #7a4f00;
50
- }
51
- }
52
- * { box-sizing: border-box; margin: 0; padding: 0; }
53
- body {
54
- font-family: "SF Mono", "Fira Code", "Cascadia Code", "Consolas", monospace;
55
- background: var(--bg);
56
- color: var(--text);
57
- height: 100dvh;
58
- display: flex;
59
- flex-direction: column;
60
- font-size: 16px;
61
- }
62
-
63
- /* Header */
64
- #header {
65
- padding: 6px 16px;
66
- background: var(--surface);
67
- border-bottom: 1px solid var(--border);
68
- display: grid;
69
- grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) minmax(0, 1fr);
70
- align-items: center;
71
- gap: 12px;
72
- font-size: 15px;
73
- }
74
- #header .logo { font-weight: bold; color: var(--green); }
75
- .header-side {
76
- min-width: 0;
77
- display: flex;
78
- align-items: center;
79
- }
80
- .header-left { justify-content: flex-start; }
81
- .header-right {
82
- justify-content: flex-end;
83
- gap: 12px;
84
- }
85
- #header .status { color: var(--text-dim); }
86
- #status {
87
- width: 12px;
88
- height: 12px;
89
- display: inline-block;
90
- flex: 0 0 auto;
91
- border-radius: 999px;
92
- border: 2px solid transparent;
93
- background: transparent;
94
- }
95
- #status.is-disconnected {
96
- background: var(--red);
97
- border-color: var(--red);
98
- }
99
- #status.is-connected {
100
- background: var(--green);
101
- border-color: var(--green);
102
- }
103
- #status.is-connecting {
104
- border-color: var(--yellow);
105
- animation: status-pulse 1.2s ease-in-out infinite;
106
- }
107
- @keyframes status-pulse {
108
- 0%, 100% {
109
- background: transparent;
110
- opacity: 0.9;
111
- }
112
- 50% {
113
- background: var(--yellow);
114
- opacity: 1;
115
- }
116
- }
117
- #session-info {
118
- overflow: hidden;
119
- text-overflow: ellipsis;
120
- white-space: nowrap;
121
- min-width: 0;
122
- width: 100%;
123
- text-align: center;
124
- }
125
- #theme-btn {
126
- background: transparent;
127
- border: none;
128
- color: var(--text-dim);
129
- cursor: pointer;
130
- font-size: 15px;
131
- font-family: inherit;
132
- padding: 2px 6px;
133
- }
134
- #theme-btn:hover { color: var(--text); }
135
- @media (max-width: 640px) {
136
- #header {
137
- display: flex;
138
- gap: 8px;
139
- }
140
- .header-left,
141
- .header-right {
142
- flex: 0 0 auto;
143
- }
144
- .header-right {
145
- margin-left: auto;
146
- }
147
- #session-info {
148
- flex: 1 1 auto;
149
- text-align: left;
150
- }
151
- }
152
-
153
- /* Messages */
154
- #messages {
155
- flex: 1;
156
- overflow-y: auto;
157
- padding: 16px;
158
- display: flex;
159
- flex-direction: column;
160
- gap: 4px;
161
- }
162
- .msg {
163
- padding: 8px 14px;
164
- line-height: 1.6;
165
- font-size: 16px;
166
- overflow-wrap: break-word;
167
- }
168
- .msg.user {
169
- background: var(--user-bg);
170
- border-left: 3px solid var(--accent);
171
- border-radius: 0 var(--radius) var(--radius) 0;
172
- margin: 8px 0 4px 0;
173
- font-weight: 500;
174
- }
175
- .msg.user::before {
176
- content: '▶ ';
177
- color: var(--accent);
178
- font-weight: bold;
179
- }
180
- .msg.assistant {
181
- padding: 4px 14px;
182
- }
183
- .msg.assistant pre {
184
- background: none;
185
- padding: 8px 14px;
186
- overflow-x: auto;
187
- margin: 0;
188
- font-size: 15px;
189
- border-radius: 0;
190
- }
191
- .msg.assistant code {
192
- font-family: "SF Mono", "Fira Code", monospace;
193
- font-size: 15px;
194
- }
195
-
196
- /* Code block wrapper — aligned with prompt/permission/thinking style */
197
- .code-block-wrapper {
198
- margin: 8px -14px;
199
- background: var(--code-bg);
200
- border-left: 3px solid var(--green);
201
- border-radius: 0 var(--radius) var(--radius) 0;
202
- position: relative;
203
- overflow: hidden;
204
- }
205
- .code-block-wrapper pre {
206
- margin: 0;
207
- padding: 8px 14px;
208
- }
209
- .code-block-wrapper pre code,
210
- .code-block-wrapper pre code.hljs {
211
- background: none;
212
- padding: 0;
213
- }
214
- .code-toolbar {
215
- display: flex;
216
- align-items: center;
217
- justify-content: flex-end;
218
- padding: 2px 6px;
219
- position: absolute;
220
- top: 0;
221
- right: 0;
222
- z-index: 1;
223
- font-size: 12px;
224
- color: var(--text-dim);
225
- gap: 8px;
226
- opacity: 0;
227
- transition: opacity 0.15s;
228
- }
229
- .code-block-wrapper:hover .code-toolbar {
230
- opacity: 1;
231
- }
232
- .copy-btn {
233
- background: transparent;
234
- color: var(--text-dim);
235
- border: 1px solid var(--border);
236
- border-radius: var(--radius);
237
- min-width: 28px;
238
- height: 22px;
239
- padding: 0 4px;
240
- font-size: 12px;
241
- font-family: inherit;
242
- cursor: pointer;
243
- display: inline-flex;
244
- align-items: center;
245
- justify-content: center;
246
- line-height: 1;
247
- transition: color 0.15s, border-color 0.15s;
248
- }
249
- .copy-btn:hover {
250
- color: var(--text);
251
- border-color: var(--text-dim);
252
- }
253
- .copy-btn.copied {
254
- color: var(--green);
255
- border-color: var(--green);
256
- }
257
- .msg.assistant p { margin: 4px 0; }
258
- .msg.assistant ul, .msg.assistant ol { padding-left: 20px; margin: 4px 0; }
259
-
260
- /* Thinking */
261
- .thinking {
262
- color: var(--text-dim);
263
- font-style: italic;
264
- font-size: 15px;
265
- padding: 6px 14px;
266
- border-left: 2px solid var(--border);
267
- }
268
- .thinking summary { cursor: pointer; }
269
- .thinking-content { cursor: default; }
270
- .thinking summary.active { animation: pulse 1.5s ease-in-out infinite; }
271
-
272
- /* Waiting indicator (after user sends, before first response) */
273
- @keyframes blink { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }
274
- @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
275
- #waiting {
276
- padding: 6px 14px;
277
- color: var(--text-dim);
278
- font-size: 15px;
279
- }
280
- #waiting .cursor { animation: blink 1s step-end infinite; }
281
-
282
- /* System messages */
283
- .system-msg {
284
- font-size: 15px;
285
- padding: 2px 14px;
286
- color: var(--text-dim);
287
- }
288
-
289
- /* Tool calls */
290
- .tool-call {
291
- font-size: 15px;
292
- padding: 2px 14px;
293
- color: var(--text-dim);
294
- animation: pulse 1.5s ease-in-out infinite;
295
- }
296
- .tool-call .icon { margin-right: 4px; }
297
- .tool-call.completed { color: var(--green); animation: none; }
298
- .tool-call.failed { color: var(--red); animation: none; }
299
- .tool-call { cursor: pointer; }
300
- .tool-call .tc-detail {
301
- display: block;
302
- color: var(--text-dim);
303
- white-space: nowrap;
304
- overflow: hidden;
305
- text-overflow: ellipsis;
306
- max-width: 100%;
307
- }
308
- .tool-call .tc-detail.expanded {
309
- white-space: pre-wrap;
310
- overflow: visible;
311
- text-overflow: unset;
312
- }
313
- .tool-call details { margin-top: 2px; }
314
- .tool-call details summary { cursor: pointer; color: var(--text-dim); }
315
- .tool-call .tc-content {
316
- margin: 4px 0 4px 16px;
317
- padding: 6px 10px;
318
- background: var(--code-bg);
319
- border-radius: var(--radius);
320
- font-size: 14px;
321
- white-space: pre-wrap;
322
- max-height: 200px;
323
- overflow-y: auto;
324
- color: var(--text);
325
- cursor: default;
326
- }
327
- .tool-call .tc-summary {
328
- margin: 4px 0 4px 16px;
329
- font-size: 14px;
330
- color: var(--text);
331
- }
332
-
333
- /* Diff view for edit tool calls */
334
- .diff-view {
335
- margin: 4px 0 4px 16px;
336
- padding: 6px 10px;
337
- background: var(--code-bg);
338
- border-radius: var(--radius);
339
- font-size: 13px;
340
- font-family: monospace;
341
- white-space: pre-wrap;
342
- word-break: break-all;
343
- max-height: 300px;
344
- overflow-y: auto;
345
- line-height: 1.4;
346
- cursor: default;
347
- }
348
- .diff-view .diff-file { color: var(--blue); font-weight: bold; }
349
- .diff-view .diff-hunk { color: var(--text-dim); }
350
- .diff-view .diff-del { color: var(--red); }
351
- .diff-view .diff-add { color: var(--green); }
352
-
353
- /* Plan */
354
- .plan {
355
- font-size: 15px;
356
- padding: 6px 14px;
357
- border-left: 3px solid var(--blue);
358
- border-radius: 0 var(--radius) var(--radius) 0;
359
- }
360
- .plan-title { color: var(--blue); font-weight: bold; margin-bottom: 2px; }
361
- .plan-entry { padding: 1px 0; }
362
-
363
- /* Permission dialog */
364
- .permission {
365
- background: var(--permission-bg);
366
- border-left: 3px solid var(--yellow);
367
- border-radius: 0 var(--radius) var(--radius) 0;
368
- padding: 8px 14px;
369
- font-size: 15px;
370
- }
371
- .permission .title { color: var(--permission-title); font-weight: bold; margin-bottom: 6px; }
372
- .permission button {
373
- margin: 2px 4px 2px 0;
374
- padding: 4px 10px;
375
- border: 1px solid var(--border);
376
- border-radius: var(--radius);
377
- background: transparent;
378
- color: var(--text);
379
- cursor: pointer;
380
- font-size: 14px;
381
- font-family: inherit;
382
- }
383
- .permission button:hover { background: var(--surface); }
384
- .permission button.allow { border-color: var(--green); }
385
- .permission button.deny { border-color: var(--red); }
386
-
387
- /* Bash command output */
388
- .bash-block {
389
- margin: 2px 0;
390
- padding: 2px 14px;
391
- font-size: 15px;
392
- }
393
- .bash-block .bash-cmd {
394
- color: var(--green);
395
- font-family: monospace;
396
- font-size: 14px;
397
- cursor: pointer;
398
- }
399
- .bash-block .bash-cmd::before { content: '$ '; color: var(--text-dim); }
400
- .bash-block .bash-cmd.running { animation: pulse 1.5s ease-in-out infinite; }
401
- .bash-block .bash-output {
402
- margin: 4px 0 4px 0;
403
- padding: 6px 10px;
404
- background: var(--code-bg);
405
- border-radius: var(--radius);
406
- font-size: 13px;
407
- font-family: monospace;
408
- white-space: pre-wrap;
409
- word-break: break-all;
410
- max-height: 400px;
411
- overflow-y: auto;
412
- color: var(--text);
413
- line-height: 1.4;
414
- display: none;
415
- }
416
- .bash-block .bash-output.has-content { display: block; }
417
- .bash-block .bash-output .stderr { color: var(--red); }
418
- .bash-block .bash-exit { font-size: 13px; color: var(--text-dim); margin-left: 8px; }
419
- .bash-block .bash-exit.ok { color: var(--green); }
420
- .bash-block .bash-exit.fail { color: var(--red); }
421
-
422
- /* Input */
423
- #input-area {
424
- position: relative;
425
- padding: 8px 16px 4px;
426
- background: var(--bg);
427
- border-top: 1px solid var(--border);
428
- display: flex;
429
- align-items: center;
430
- gap: 4px;
431
- transition: border-color .15s;
432
- }
433
- #input-area.bash-mode {
434
- border-top: 2px solid #e8872b;
435
- }
436
- #input-area.bash-mode #input-prompt {
437
- color: #e8872b;
438
- }
439
- #input-area.bash-mode #input {
440
- color: #e8872b;
441
- }
442
- #input-area.plan-mode {
443
- border-top: 2px solid var(--green);
444
- }
445
- #input-area.plan-mode::before {
446
- content: 'plan';
447
- position: absolute;
448
- top: -7px;
449
- left: 16px;
450
- font-size: 10px;
451
- line-height: 1;
452
- padding: 0 4px;
453
- background: var(--bg);
454
- color: var(--green);
455
- text-transform: uppercase;
456
- letter-spacing: 0.5px;
457
- }
458
- #input-area.plan-mode #input-prompt {
459
- color: var(--green);
460
- }
461
- #input-area.autopilot-mode {
462
- border-top: 2px solid var(--red);
463
- }
464
- #input-area.autopilot-mode::before {
465
- content: 'autopilot';
466
- position: absolute;
467
- top: -7px;
468
- left: 16px;
469
- font-size: 10px;
470
- line-height: 1;
471
- padding: 0 4px;
472
- background: var(--bg);
473
- color: var(--red);
474
- text-transform: uppercase;
475
- letter-spacing: 0.5px;
476
- }
477
- #input-area.autopilot-mode #input-prompt {
478
- color: var(--red);
479
- }
480
- #status-bar {
481
- display: flex;
482
- padding: 0 16px 10px;
483
- background: var(--bg);
484
- color: var(--text-dim);
485
- font-size: 11px;
486
- line-height: 1.4;
487
- white-space: nowrap;
488
- user-select: none;
489
- }
490
- #status-bar::before {
491
- content: '\200b';
492
- }
493
- #status-bar .status-cwd {
494
- overflow: hidden;
495
- text-overflow: ellipsis;
496
- direction: rtl;
497
- text-align: left;
498
- }
499
- #input-prompt {
500
- color: var(--text);
501
- font-weight: bold;
502
- user-select: none;
503
- line-height: 1.5;
504
- cursor: pointer;
505
- }
506
- #input {
507
- flex: 1;
508
- background: transparent;
509
- color: var(--text);
510
- border: none;
511
- padding: 4px 8px;
512
- font-size: 16px;
513
- font-family: inherit;
514
- resize: none;
515
- min-height: 28px;
516
- max-height: 200px;
517
- line-height: 1.5;
518
- }
519
- #input:focus { outline: none; }
520
- .input-btn {
521
- background: transparent;
522
- color: var(--text-dim);
523
- border: 1px solid var(--border);
524
- border-radius: var(--radius);
525
- width: 34px;
526
- height: 28px;
527
- padding: 0 6px;
528
- font-size: 14px;
529
- font-family: inherit;
530
- cursor: pointer;
531
- display: inline-flex;
532
- align-items: center;
533
- justify-content: center;
534
- line-height: 1;
535
- flex: 0 0 auto;
536
- }
537
- .input-btn:hover {
538
- color: var(--text);
539
- border-color: var(--text-dim);
540
- }
541
- #send-btn.cancel {
542
- color: var(--red);
543
- border-color: var(--red);
544
- font-weight: bold;
545
- }
546
- #send-btn.cancel:hover,
547
- #send-btn.cancel:focus,
548
- #send-btn.cancel:active {
549
- color: var(--red);
550
- border-color: var(--red);
551
- }
552
- #input-prompt.busy { font-size: 0; position: relative; }
553
- #input-prompt.busy::before {
554
- content: '⠋';
555
- font-size: 16px;
556
- animation: spinner 0.8s steps(1) infinite;
557
- }
558
- @keyframes spinner {
559
- 0% { content: '⠋'; } 10% { content: '⠙'; } 20% { content: '⠹'; }
560
- 30% { content: '⠸'; } 40% { content: '⠼'; } 50% { content: '⠴'; }
561
- 60% { content: '⠦'; } 70% { content: '⠧'; } 80% { content: '⠇'; }
562
- 90% { content: '⠏'; }
563
- }
564
-
565
- /* Slash command autocomplete */
566
- #slash-menu {
567
- display: none;
568
- position: absolute;
569
- bottom: 100%;
570
- left: 0;
571
- right: 0;
572
- background: var(--surface);
573
- border: 1px solid var(--border);
574
- border-bottom: none;
575
- max-height: 220px;
576
- overflow-y: auto;
577
- z-index: 10;
578
- }
579
- #slash-menu.active { display: block; }
580
- .slash-item {
581
- padding: 6px 14px;
582
- cursor: pointer;
583
- font-size: 15px;
584
- display: flex;
585
- gap: 12px;
586
- }
587
- .slash-item:hover, .slash-item.selected {
588
- background: var(--accent);
589
- color: #fff;
590
- }
591
- .slash-item .slash-cmd { font-weight: bold; min-width: 120px; }
592
- .slash-item .slash-desc { color: var(--text-dim); }
593
- .slash-item:hover .slash-desc, .slash-item.selected .slash-desc { color: rgba(255,255,255,0.8); }
594
-
595
- /* Image attach */
596
- #attach-preview {
597
- display: none;
598
- padding: 4px 16px;
599
- background: var(--bg);
600
- border-top: 1px solid var(--border);
601
- }
602
- #attach-preview.active { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
603
- .attach-thumb {
604
- position: relative;
605
- display: inline-block;
606
- }
607
- .attach-thumb img {
608
- max-height: 60px;
609
- max-width: 120px;
610
- border-radius: 4px;
611
- border: 1px solid var(--border);
612
- }
613
- .attach-thumb .remove {
614
- position: absolute;
615
- top: -4px;
616
- right: -4px;
617
- background: var(--red);
618
- color: #fff;
619
- border: none;
620
- border-radius: 50%;
621
- width: 16px;
622
- height: 16px;
623
- font-size: 10px;
624
- line-height: 16px;
625
- text-align: center;
626
- cursor: pointer;
627
- padding: 0;
628
- }
629
- .msg.user img.user-image {
630
- max-width: 200px;
631
- max-height: 150px;
632
- border-radius: 4px;
633
- border: 1px solid var(--border);
634
- display: block;
635
- margin-top: 4px;
636
- cursor: zoom-in;
637
- }
638
-
639
- /* Image lightbox overlay */
640
- #lightbox-overlay {
641
- position: fixed;
642
- inset: 0;
643
- z-index: 9999;
644
- background: rgba(0, 0, 0, 0.85);
645
- display: flex;
646
- align-items: center;
647
- justify-content: center;
648
- cursor: zoom-out;
649
- visibility: hidden;
650
- pointer-events: none;
651
- }
652
- #lightbox-overlay.active {
653
- visibility: visible;
654
- pointer-events: auto;
655
- }
656
- #lightbox-overlay img {
657
- max-width: 90vw;
658
- max-height: 90vh;
659
- border-radius: 6px;
660
- cursor: default;
661
- }
662
-
663
- /* History pagination sentinel */
664
- .history-sentinel {
665
- text-align: center;
666
- padding: 8px;
667
- opacity: 0.5;
668
- font-size: 0.85em;
669
- }
@@ -1,17 +0,0 @@
1
- // Shared constants used by both frontend and backend.
2
- // --- Tool call kind → display icon ---
3
- export const TOOL_ICONS = {
4
- task_complete: "✔",
5
- read: "cat",
6
- edit: "edit",
7
- execute: "exec",
8
- search: "find",
9
- delete: "rm",
10
- };
11
- export const DEFAULT_TOOL_ICON = "run";
12
- // --- Plan entry status → display symbol ---
13
- export const PLAN_STATUS_ICONS = {
14
- pending: "○",
15
- in_progress: "◉",
16
- completed: "●",
17
- };