cli-jaw 1.7.33 → 1.7.34
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/dist/bin/commands/dispatch.js +8 -0
- package/dist/bin/commands/dispatch.js.map +1 -1
- package/dist/bin/commands/memory.js +7 -1
- package/dist/bin/commands/memory.js.map +1 -1
- package/dist/bin/commands/orchestrate.js +67 -8
- package/dist/bin/commands/orchestrate.js.map +1 -1
- package/dist/src/agent/args.js +4 -0
- package/dist/src/agent/args.js.map +1 -1
- package/dist/src/agent/events.js +50 -20
- package/dist/src/agent/events.js.map +1 -1
- package/dist/src/agent/opencode-diagnostics.js +106 -0
- package/dist/src/agent/opencode-diagnostics.js.map +1 -0
- package/dist/src/agent/spawn-env.js +75 -4
- package/dist/src/agent/spawn-env.js.map +1 -1
- package/dist/src/agent/spawn.js +104 -15
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/cli/commands.js +1 -1
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/handlers-runtime.js +23 -5
- package/dist/src/cli/handlers-runtime.js.map +1 -1
- package/dist/src/core/compact.js +8 -7
- package/dist/src/core/compact.js.map +1 -1
- package/dist/src/core/runtime-settings-gate.js +40 -0
- package/dist/src/core/runtime-settings-gate.js.map +1 -0
- package/dist/src/core/runtime-settings.js +71 -64
- package/dist/src/core/runtime-settings.js.map +1 -1
- package/dist/src/orchestrator/pipeline.js +20 -0
- package/dist/src/orchestrator/pipeline.js.map +1 -1
- package/dist/src/orchestrator/state-machine.js +8 -5
- package/dist/src/orchestrator/state-machine.js.map +1 -1
- package/dist/src/prompt/templates/a1-system.md +9 -1
- package/dist/src/prompt/templates/employee.md +5 -1
- package/dist/src/routes/orchestrate.js +52 -11
- package/dist/src/routes/orchestrate.js.map +1 -1
- package/package.json +6 -5
- package/public/css/modals.css +126 -0
- package/public/dist/assets/{employees-Do9d6Xi5.js → employees-p53cgGmH.js} +1 -1
- package/public/dist/assets/{index-qALA03H1.css → index-CLKLbGzn.css} +1 -1
- package/public/dist/assets/index-wUWc2M5K.js +32 -0
- package/public/dist/assets/memory-6zLEr-qI.js +1 -0
- package/public/dist/assets/{memory-DeZSzBAb.js → memory-C2i7ZIvv.js} +2 -2
- package/public/dist/assets/{render-CQnnZ-_i.js → render-CulTuvJs.js} +1 -1
- package/public/dist/assets/settings-BUEiZgkm.js +40 -0
- package/public/dist/assets/settings-BhrOslae.js +1 -0
- package/public/dist/assets/{skills-Ci5t_dsV.js → skills-CSuSbBWa.js} +1 -1
- package/public/dist/assets/skills-CgwxEvFx.js +1 -0
- package/public/dist/assets/slash-commands-Bo8jvBfI.js +1 -0
- package/public/dist/assets/{slash-commands-0RvnZU9z.js → slash-commands-D-v0DlbY.js} +1 -1
- package/public/dist/assets/ui-4JiRyxJy.js +131 -0
- package/public/dist/assets/ui-Dx0MwI23.js +1 -0
- package/public/dist/assets/ws-DKtFfZsY.js +14 -0
- package/public/dist/index.html +74 -15
- package/public/index.html +72 -13
- package/public/js/features/attention-badge.ts +151 -0
- package/public/js/features/chat.ts +16 -0
- package/public/js/features/help-content.ts +75 -0
- package/public/js/features/help-dialog.ts +164 -0
- package/public/js/features/memory.ts +2 -2
- package/public/js/features/orchestrate-scope.ts +4 -0
- package/public/js/features/settings-core.ts +36 -11
- package/public/js/main.ts +4 -0
- package/public/js/ui.ts +21 -1
- package/public/js/virtual-scroll.ts +72 -8
- package/public/js/ws.ts +50 -6
- package/public/locales/en.json +183 -2
- package/public/locales/ko.json +183 -2
- package/scripts/smoke/opencode-external-dir-smoke.ts +350 -0
- package/public/dist/assets/index-yGExjgR_.js +0 -32
- package/public/dist/assets/memory-Dpe-qPbZ.js +0 -1
- package/public/dist/assets/settings-C8bSXG3q.js +0 -40
- package/public/dist/assets/settings-COrhSfDh.js +0 -1
- package/public/dist/assets/skills-BO0V4aHG.js +0 -1
- package/public/dist/assets/slash-commands-DbUvFtCk.js +0 -1
- package/public/dist/assets/ui-Cxk1_e0b.js +0 -1
- package/public/dist/assets/ui-IWxpAzJ7.js +0 -131
- package/public/dist/assets/ws-FsYmCE65.js +0 -14
- /package/public/dist/assets/{constants-IeOVgtYz.js → constants-BU8a_R5s.js} +0 -0
package/public/dist/index.html
CHANGED
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@400;500;600;700&family=Outfit:wght@400;500;600;700&display=swap"
|
|
26
26
|
rel="stylesheet">
|
|
27
27
|
<!-- Vite handles module bundling in dev (HMR) and production (build) -->
|
|
28
|
-
<script type="module" crossorigin src="/dist/assets/index-
|
|
28
|
+
<script type="module" crossorigin src="/dist/assets/index-wUWc2M5K.js"></script>
|
|
29
29
|
<link rel="stylesheet" crossorigin href="/dist/assets/vendor-render-Bjnw0wQ6.css">
|
|
30
|
-
<link rel="stylesheet" crossorigin href="/dist/assets/index-
|
|
30
|
+
<link rel="stylesheet" crossorigin href="/dist/assets/index-CLKLbGzn.css">
|
|
31
31
|
</head>
|
|
32
32
|
|
|
33
33
|
<body>
|
|
@@ -43,7 +43,11 @@
|
|
|
43
43
|
</div>
|
|
44
44
|
|
|
45
45
|
<div>
|
|
46
|
-
<div class="section-title
|
|
46
|
+
<div class="section-title-row">
|
|
47
|
+
<div class="section-title" data-i18n="sidebar.memory">메모리</div>
|
|
48
|
+
<button class="help-trigger" type="button" data-help-topic="memory"
|
|
49
|
+
data-i18n-aria="help.memory.aria" aria-label="메모리 도움말">?</button>
|
|
50
|
+
</div>
|
|
47
51
|
<button class="sidebar-hb-btn" id="memorySidebarBtn" aria-label="Open memory panel"><span data-icon="brain"></span> Memory (0)</button>
|
|
48
52
|
</div>
|
|
49
53
|
|
|
@@ -118,7 +122,11 @@
|
|
|
118
122
|
</div>
|
|
119
123
|
<button class="sidebar-hb-btn" id="btnClearChat">/clear</button>
|
|
120
124
|
<button class="sidebar-hb-btn" id="hbSidebarBtn"><span data-icon="heartPulse"></span> Heartbeat (0)</button>
|
|
121
|
-
<
|
|
125
|
+
<div class="sidebar-action-row">
|
|
126
|
+
<button class="sidebar-hb-btn" data-action="openTemplates"><span data-icon="plan"></span> 프롬프트 템플릿</button>
|
|
127
|
+
<button class="help-trigger" type="button" data-help-topic="promptTemplates"
|
|
128
|
+
data-i18n-aria="help.promptTemplates.aria" aria-label="프롬프트 템플릿 도움말">?</button>
|
|
129
|
+
</div>
|
|
122
130
|
<button class="sidebar-hb-btn" id="langToggle" title="한국어 ⇄ English"><span data-icon="web"></span> 한국어</button>
|
|
123
131
|
</div>
|
|
124
132
|
</nav>
|
|
@@ -197,7 +205,11 @@
|
|
|
197
205
|
<!-- Agents Tab -->
|
|
198
206
|
<div id="tabAgents" class="tab-content active" role="tabpanel" aria-labelledby="tabBtnAgents">
|
|
199
207
|
<div>
|
|
200
|
-
<label
|
|
208
|
+
<label class="label-with-help">
|
|
209
|
+
<span data-i18n="label.activeCli">활성 CLI</span>
|
|
210
|
+
<button class="help-trigger" type="button" data-help-topic="activeCli"
|
|
211
|
+
data-i18n-aria="help.activeCli.aria" aria-label="활성 CLI 도움말">?</button>
|
|
212
|
+
</label>
|
|
201
213
|
<select id="selCli">
|
|
202
214
|
<option value="claude">Claude</option>
|
|
203
215
|
<option value="codex">Codex</option>
|
|
@@ -208,11 +220,19 @@
|
|
|
208
220
|
</div>
|
|
209
221
|
|
|
210
222
|
<div>
|
|
211
|
-
<label
|
|
223
|
+
<label class="label-with-help">
|
|
224
|
+
<span data-i18n="label.model">모델</span>
|
|
225
|
+
<button class="help-trigger" type="button" data-help-topic="model"
|
|
226
|
+
data-i18n-aria="help.model.aria" aria-label="모델 도움말">?</button>
|
|
227
|
+
</label>
|
|
212
228
|
<select id="selModel"></select>
|
|
213
229
|
</div>
|
|
214
230
|
<div>
|
|
215
|
-
<label
|
|
231
|
+
<label class="label-with-help">
|
|
232
|
+
<span data-i18n="label.effort">추론 강도</span>
|
|
233
|
+
<button class="help-trigger" type="button" data-help-topic="effort"
|
|
234
|
+
data-i18n-aria="help.effort.aria" aria-label="추론 강도 도움말">?</button>
|
|
235
|
+
</label>
|
|
216
236
|
<select id="selEffort">
|
|
217
237
|
<option value="">— none</option>
|
|
218
238
|
<option value="low">🟢 low</option>
|
|
@@ -222,7 +242,11 @@
|
|
|
222
242
|
</div>
|
|
223
243
|
|
|
224
244
|
<div>
|
|
225
|
-
<label
|
|
245
|
+
<label class="label-with-help">
|
|
246
|
+
<span data-i18n="label.permissions">권한</span>
|
|
247
|
+
<button class="help-trigger" type="button" data-help-topic="permissions"
|
|
248
|
+
data-i18n-aria="help.permissions.aria" aria-label="권한 도움말">?</button>
|
|
249
|
+
</label>
|
|
226
250
|
<div class="perm-toggle">
|
|
227
251
|
<span class="perm-btn active perm-auto" ><span data-icon="exec"></span> Auto</span>
|
|
228
252
|
</div>
|
|
@@ -238,6 +262,8 @@
|
|
|
238
262
|
<details id="flushAgentDetails" class="flush-agent-section">
|
|
239
263
|
<summary class="section-title" style="cursor:pointer; user-select:none;">
|
|
240
264
|
<span data-icon="lightbulb"></span> Flush Agent
|
|
265
|
+
<button class="help-trigger" type="button" data-help-topic="flushAgent"
|
|
266
|
+
data-i18n-aria="help.flushAgent.aria" aria-label="Flush Agent 도움말">?</button>
|
|
241
267
|
<span id="flushAgentBadge" class="text-xs text-dim" style="margin-left:4px"></span>
|
|
242
268
|
</summary>
|
|
243
269
|
<div class="flush-agent-fields" style="margin-top:6px">
|
|
@@ -264,7 +290,11 @@
|
|
|
264
290
|
<hr class="hr-divider">
|
|
265
291
|
|
|
266
292
|
<div class="flex justify-between items-center">
|
|
267
|
-
<div class="
|
|
293
|
+
<div class="label-with-help">
|
|
294
|
+
<div class="section-title" data-i18n="sidebar.employees">직원</div>
|
|
295
|
+
<button class="help-trigger" type="button" data-help-topic="employees"
|
|
296
|
+
data-i18n-aria="help.employees.aria" aria-label="직원 도움말">?</button>
|
|
297
|
+
</div>
|
|
268
298
|
<button class="btn-clear btn-action-sm" data-action="addEmployee"
|
|
269
299
|
data-i18n="btn.addEmployee">+ 추가</button>
|
|
270
300
|
</div>
|
|
@@ -275,6 +305,11 @@
|
|
|
275
305
|
|
|
276
306
|
<!-- Skills Tab -->
|
|
277
307
|
<div id="tabSkills" class="tab-content" role="tabpanel" aria-labelledby="tabBtnSkills">
|
|
308
|
+
<div class="section-title-row">
|
|
309
|
+
<div class="section-title" data-i18n="tab.skills">스킬</div>
|
|
310
|
+
<button class="help-trigger" type="button" data-help-topic="skills"
|
|
311
|
+
data-i18n-aria="help.skills.aria" aria-label="스킬 도움말">?</button>
|
|
312
|
+
</div>
|
|
278
313
|
<div class="flex gap-2 flex-wrap">
|
|
279
314
|
<button class="skill-filter active" data-filter="all" data-i18n="skill.filter.all">전체</button>
|
|
280
315
|
<button class="skill-filter" data-filter="installed" data-i18n="skill.filter.installed"><span data-icon="compacting"></span> 설치됨</button>
|
|
@@ -299,7 +334,11 @@
|
|
|
299
334
|
시스템 프롬프트 편집</button>
|
|
300
335
|
<!-- Active Channel Toggle -->
|
|
301
336
|
<div class="settings-group">
|
|
302
|
-
<h4
|
|
337
|
+
<h4 class="section-title-row">
|
|
338
|
+
<span><span data-icon="radio"></span> Active Channel</span>
|
|
339
|
+
<button class="help-trigger" type="button" data-help-topic="activeChannel"
|
|
340
|
+
data-i18n-aria="help.activeChannel.aria" aria-label="Active Channel 도움말">?</button>
|
|
341
|
+
</h4>
|
|
303
342
|
<div class="settings-row">
|
|
304
343
|
<label>Runtime</label>
|
|
305
344
|
<div>
|
|
@@ -311,7 +350,11 @@
|
|
|
311
350
|
|
|
312
351
|
<!-- Telegram Bot -->
|
|
313
352
|
<div id="channelTelegramSettings" class="settings-group">
|
|
314
|
-
<h4
|
|
353
|
+
<h4 class="section-title-row">
|
|
354
|
+
<span><span data-provider="telegram"></span> Telegram</span>
|
|
355
|
+
<button class="help-trigger" type="button" data-help-topic="telegram"
|
|
356
|
+
data-i18n-aria="help.telegram.aria" aria-label="Telegram 도움말">?</button>
|
|
357
|
+
</h4>
|
|
315
358
|
<div class="settings-row">
|
|
316
359
|
<label data-i18n="label.enabled">활성화</label>
|
|
317
360
|
<div>
|
|
@@ -346,7 +389,11 @@
|
|
|
346
389
|
|
|
347
390
|
<!-- Discord Bot -->
|
|
348
391
|
<div id="channelDiscordSettings" class="settings-group" style="display:none">
|
|
349
|
-
<h4
|
|
392
|
+
<h4 class="section-title-row">
|
|
393
|
+
<span><span data-provider="discord"></span> Discord</span>
|
|
394
|
+
<button class="help-trigger" type="button" data-help-topic="discord"
|
|
395
|
+
data-i18n-aria="help.discord.aria" aria-label="Discord 도움말">?</button>
|
|
396
|
+
</h4>
|
|
350
397
|
<div class="settings-row">
|
|
351
398
|
<label data-i18n="label.enabled">활성화</label>
|
|
352
399
|
<div>
|
|
@@ -508,7 +555,11 @@
|
|
|
508
555
|
|
|
509
556
|
<!-- Fallback Order -->
|
|
510
557
|
<div class="settings-group">
|
|
511
|
-
<h4
|
|
558
|
+
<h4 class="section-title-row">
|
|
559
|
+
<span><span data-icon="exec"></span> Fallback</span>
|
|
560
|
+
<button class="help-trigger" type="button" data-help-topic="fallbackOrder"
|
|
561
|
+
data-i18n-aria="help.fallbackOrder.aria" aria-label="Fallback 도움말">?</button>
|
|
562
|
+
</h4>
|
|
512
563
|
<p class="text-xs text-dim fallback-desc" data-i18n="fallback.desc">CLI 실패 시 자동
|
|
513
564
|
재시도 순서</p>
|
|
514
565
|
<div id="fallbackOrderList"></div>
|
|
@@ -516,7 +567,11 @@
|
|
|
516
567
|
|
|
517
568
|
<!-- MCP Servers -->
|
|
518
569
|
<div class="settings-group">
|
|
519
|
-
<h4
|
|
570
|
+
<h4 class="section-title-row">
|
|
571
|
+
<span><span data-icon="link"></span> MCP Servers</span>
|
|
572
|
+
<button class="help-trigger" type="button" data-help-topic="mcp"
|
|
573
|
+
data-i18n-aria="help.mcp.aria" aria-label="MCP 도움말">?</button>
|
|
574
|
+
</h4>
|
|
520
575
|
<div id="mcpServerList" class="text-status py-1">
|
|
521
576
|
Loading...
|
|
522
577
|
</div>
|
|
@@ -529,7 +584,11 @@
|
|
|
529
584
|
|
|
530
585
|
<!-- STT Settings -->
|
|
531
586
|
<div class="settings-group">
|
|
532
|
-
<h4
|
|
587
|
+
<h4 class="section-title-row">
|
|
588
|
+
<span><span data-icon="mic"></span> <span data-i18n="stt.title">음성 인식 (STT)</span></span>
|
|
589
|
+
<button class="help-trigger" type="button" data-help-topic="stt"
|
|
590
|
+
data-i18n-aria="help.stt.aria" aria-label="STT 도움말">?</button>
|
|
591
|
+
</h4>
|
|
533
592
|
<div class="settings-row">
|
|
534
593
|
<label data-i18n="stt.engine">엔진</label>
|
|
535
594
|
<select id="sttEngine">
|
package/public/index.html
CHANGED
|
@@ -49,7 +49,11 @@
|
|
|
49
49
|
</div>
|
|
50
50
|
|
|
51
51
|
<div>
|
|
52
|
-
<div class="section-title
|
|
52
|
+
<div class="section-title-row">
|
|
53
|
+
<div class="section-title" data-i18n="sidebar.memory">메모리</div>
|
|
54
|
+
<button class="help-trigger" type="button" data-help-topic="memory"
|
|
55
|
+
data-i18n-aria="help.memory.aria" aria-label="메모리 도움말">?</button>
|
|
56
|
+
</div>
|
|
53
57
|
<button class="sidebar-hb-btn" id="memorySidebarBtn" aria-label="Open memory panel"><span data-icon="brain"></span> Memory (0)</button>
|
|
54
58
|
</div>
|
|
55
59
|
|
|
@@ -124,7 +128,11 @@
|
|
|
124
128
|
</div>
|
|
125
129
|
<button class="sidebar-hb-btn" id="btnClearChat">/clear</button>
|
|
126
130
|
<button class="sidebar-hb-btn" id="hbSidebarBtn"><span data-icon="heartPulse"></span> Heartbeat (0)</button>
|
|
127
|
-
<
|
|
131
|
+
<div class="sidebar-action-row">
|
|
132
|
+
<button class="sidebar-hb-btn" data-action="openTemplates"><span data-icon="plan"></span> 프롬프트 템플릿</button>
|
|
133
|
+
<button class="help-trigger" type="button" data-help-topic="promptTemplates"
|
|
134
|
+
data-i18n-aria="help.promptTemplates.aria" aria-label="프롬프트 템플릿 도움말">?</button>
|
|
135
|
+
</div>
|
|
128
136
|
<button class="sidebar-hb-btn" id="langToggle" title="한국어 ⇄ English"><span data-icon="web"></span> 한국어</button>
|
|
129
137
|
</div>
|
|
130
138
|
</nav>
|
|
@@ -203,7 +211,11 @@
|
|
|
203
211
|
<!-- Agents Tab -->
|
|
204
212
|
<div id="tabAgents" class="tab-content active" role="tabpanel" aria-labelledby="tabBtnAgents">
|
|
205
213
|
<div>
|
|
206
|
-
<label
|
|
214
|
+
<label class="label-with-help">
|
|
215
|
+
<span data-i18n="label.activeCli">활성 CLI</span>
|
|
216
|
+
<button class="help-trigger" type="button" data-help-topic="activeCli"
|
|
217
|
+
data-i18n-aria="help.activeCli.aria" aria-label="활성 CLI 도움말">?</button>
|
|
218
|
+
</label>
|
|
207
219
|
<select id="selCli">
|
|
208
220
|
<option value="claude">Claude</option>
|
|
209
221
|
<option value="codex">Codex</option>
|
|
@@ -214,11 +226,19 @@
|
|
|
214
226
|
</div>
|
|
215
227
|
|
|
216
228
|
<div>
|
|
217
|
-
<label
|
|
229
|
+
<label class="label-with-help">
|
|
230
|
+
<span data-i18n="label.model">모델</span>
|
|
231
|
+
<button class="help-trigger" type="button" data-help-topic="model"
|
|
232
|
+
data-i18n-aria="help.model.aria" aria-label="모델 도움말">?</button>
|
|
233
|
+
</label>
|
|
218
234
|
<select id="selModel"></select>
|
|
219
235
|
</div>
|
|
220
236
|
<div>
|
|
221
|
-
<label
|
|
237
|
+
<label class="label-with-help">
|
|
238
|
+
<span data-i18n="label.effort">추론 강도</span>
|
|
239
|
+
<button class="help-trigger" type="button" data-help-topic="effort"
|
|
240
|
+
data-i18n-aria="help.effort.aria" aria-label="추론 강도 도움말">?</button>
|
|
241
|
+
</label>
|
|
222
242
|
<select id="selEffort">
|
|
223
243
|
<option value="">— none</option>
|
|
224
244
|
<option value="low">🟢 low</option>
|
|
@@ -228,7 +248,11 @@
|
|
|
228
248
|
</div>
|
|
229
249
|
|
|
230
250
|
<div>
|
|
231
|
-
<label
|
|
251
|
+
<label class="label-with-help">
|
|
252
|
+
<span data-i18n="label.permissions">권한</span>
|
|
253
|
+
<button class="help-trigger" type="button" data-help-topic="permissions"
|
|
254
|
+
data-i18n-aria="help.permissions.aria" aria-label="권한 도움말">?</button>
|
|
255
|
+
</label>
|
|
232
256
|
<div class="perm-toggle">
|
|
233
257
|
<span class="perm-btn active perm-auto" ><span data-icon="exec"></span> Auto</span>
|
|
234
258
|
</div>
|
|
@@ -244,6 +268,8 @@
|
|
|
244
268
|
<details id="flushAgentDetails" class="flush-agent-section">
|
|
245
269
|
<summary class="section-title" style="cursor:pointer; user-select:none;">
|
|
246
270
|
<span data-icon="lightbulb"></span> Flush Agent
|
|
271
|
+
<button class="help-trigger" type="button" data-help-topic="flushAgent"
|
|
272
|
+
data-i18n-aria="help.flushAgent.aria" aria-label="Flush Agent 도움말">?</button>
|
|
247
273
|
<span id="flushAgentBadge" class="text-xs text-dim" style="margin-left:4px"></span>
|
|
248
274
|
</summary>
|
|
249
275
|
<div class="flush-agent-fields" style="margin-top:6px">
|
|
@@ -270,7 +296,11 @@
|
|
|
270
296
|
<hr class="hr-divider">
|
|
271
297
|
|
|
272
298
|
<div class="flex justify-between items-center">
|
|
273
|
-
<div class="
|
|
299
|
+
<div class="label-with-help">
|
|
300
|
+
<div class="section-title" data-i18n="sidebar.employees">직원</div>
|
|
301
|
+
<button class="help-trigger" type="button" data-help-topic="employees"
|
|
302
|
+
data-i18n-aria="help.employees.aria" aria-label="직원 도움말">?</button>
|
|
303
|
+
</div>
|
|
274
304
|
<button class="btn-clear btn-action-sm" data-action="addEmployee"
|
|
275
305
|
data-i18n="btn.addEmployee">+ 추가</button>
|
|
276
306
|
</div>
|
|
@@ -281,6 +311,11 @@
|
|
|
281
311
|
|
|
282
312
|
<!-- Skills Tab -->
|
|
283
313
|
<div id="tabSkills" class="tab-content" role="tabpanel" aria-labelledby="tabBtnSkills">
|
|
314
|
+
<div class="section-title-row">
|
|
315
|
+
<div class="section-title" data-i18n="tab.skills">스킬</div>
|
|
316
|
+
<button class="help-trigger" type="button" data-help-topic="skills"
|
|
317
|
+
data-i18n-aria="help.skills.aria" aria-label="스킬 도움말">?</button>
|
|
318
|
+
</div>
|
|
284
319
|
<div class="flex gap-2 flex-wrap">
|
|
285
320
|
<button class="skill-filter active" data-filter="all" data-i18n="skill.filter.all">전체</button>
|
|
286
321
|
<button class="skill-filter" data-filter="installed" data-i18n="skill.filter.installed"><span data-icon="compacting"></span> 설치됨</button>
|
|
@@ -305,7 +340,11 @@
|
|
|
305
340
|
시스템 프롬프트 편집</button>
|
|
306
341
|
<!-- Active Channel Toggle -->
|
|
307
342
|
<div class="settings-group">
|
|
308
|
-
<h4
|
|
343
|
+
<h4 class="section-title-row">
|
|
344
|
+
<span><span data-icon="radio"></span> Active Channel</span>
|
|
345
|
+
<button class="help-trigger" type="button" data-help-topic="activeChannel"
|
|
346
|
+
data-i18n-aria="help.activeChannel.aria" aria-label="Active Channel 도움말">?</button>
|
|
347
|
+
</h4>
|
|
309
348
|
<div class="settings-row">
|
|
310
349
|
<label>Runtime</label>
|
|
311
350
|
<div>
|
|
@@ -317,7 +356,11 @@
|
|
|
317
356
|
|
|
318
357
|
<!-- Telegram Bot -->
|
|
319
358
|
<div id="channelTelegramSettings" class="settings-group">
|
|
320
|
-
<h4
|
|
359
|
+
<h4 class="section-title-row">
|
|
360
|
+
<span><span data-provider="telegram"></span> Telegram</span>
|
|
361
|
+
<button class="help-trigger" type="button" data-help-topic="telegram"
|
|
362
|
+
data-i18n-aria="help.telegram.aria" aria-label="Telegram 도움말">?</button>
|
|
363
|
+
</h4>
|
|
321
364
|
<div class="settings-row">
|
|
322
365
|
<label data-i18n="label.enabled">활성화</label>
|
|
323
366
|
<div>
|
|
@@ -352,7 +395,11 @@
|
|
|
352
395
|
|
|
353
396
|
<!-- Discord Bot -->
|
|
354
397
|
<div id="channelDiscordSettings" class="settings-group" style="display:none">
|
|
355
|
-
<h4
|
|
398
|
+
<h4 class="section-title-row">
|
|
399
|
+
<span><span data-provider="discord"></span> Discord</span>
|
|
400
|
+
<button class="help-trigger" type="button" data-help-topic="discord"
|
|
401
|
+
data-i18n-aria="help.discord.aria" aria-label="Discord 도움말">?</button>
|
|
402
|
+
</h4>
|
|
356
403
|
<div class="settings-row">
|
|
357
404
|
<label data-i18n="label.enabled">활성화</label>
|
|
358
405
|
<div>
|
|
@@ -514,7 +561,11 @@
|
|
|
514
561
|
|
|
515
562
|
<!-- Fallback Order -->
|
|
516
563
|
<div class="settings-group">
|
|
517
|
-
<h4
|
|
564
|
+
<h4 class="section-title-row">
|
|
565
|
+
<span><span data-icon="exec"></span> Fallback</span>
|
|
566
|
+
<button class="help-trigger" type="button" data-help-topic="fallbackOrder"
|
|
567
|
+
data-i18n-aria="help.fallbackOrder.aria" aria-label="Fallback 도움말">?</button>
|
|
568
|
+
</h4>
|
|
518
569
|
<p class="text-xs text-dim fallback-desc" data-i18n="fallback.desc">CLI 실패 시 자동
|
|
519
570
|
재시도 순서</p>
|
|
520
571
|
<div id="fallbackOrderList"></div>
|
|
@@ -522,7 +573,11 @@
|
|
|
522
573
|
|
|
523
574
|
<!-- MCP Servers -->
|
|
524
575
|
<div class="settings-group">
|
|
525
|
-
<h4
|
|
576
|
+
<h4 class="section-title-row">
|
|
577
|
+
<span><span data-icon="link"></span> MCP Servers</span>
|
|
578
|
+
<button class="help-trigger" type="button" data-help-topic="mcp"
|
|
579
|
+
data-i18n-aria="help.mcp.aria" aria-label="MCP 도움말">?</button>
|
|
580
|
+
</h4>
|
|
526
581
|
<div id="mcpServerList" class="text-status py-1">
|
|
527
582
|
Loading...
|
|
528
583
|
</div>
|
|
@@ -535,7 +590,11 @@
|
|
|
535
590
|
|
|
536
591
|
<!-- STT Settings -->
|
|
537
592
|
<div class="settings-group">
|
|
538
|
-
<h4
|
|
593
|
+
<h4 class="section-title-row">
|
|
594
|
+
<span><span data-icon="mic"></span> <span data-i18n="stt.title">음성 인식 (STT)</span></span>
|
|
595
|
+
<button class="help-trigger" type="button" data-help-topic="stt"
|
|
596
|
+
data-i18n-aria="help.stt.aria" aria-label="STT 도움말">?</button>
|
|
597
|
+
</h4>
|
|
539
598
|
<div class="settings-row">
|
|
540
599
|
<label data-i18n="stt.engine">엔진</label>
|
|
541
600
|
<select id="sttEngine">
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
type BadgeNavigator = Navigator & {
|
|
2
|
+
setAppBadge?: (contents?: number) => Promise<void>;
|
|
3
|
+
clearAppBadge?: () => Promise<void>;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
const COMPLETION_DEDUPE_MS = 800;
|
|
7
|
+
const BADGE_SIZE = 64;
|
|
8
|
+
const BASE_TITLE_FALLBACK = 'CLI-JAW';
|
|
9
|
+
|
|
10
|
+
let initialized = false;
|
|
11
|
+
let unreadCount = 0;
|
|
12
|
+
let baseTitle = BASE_TITLE_FALLBACK;
|
|
13
|
+
let faviconLink: HTMLLinkElement | null = null;
|
|
14
|
+
let originalFaviconHref = '';
|
|
15
|
+
let createdFaviconLink = false;
|
|
16
|
+
let lastNotifyAt = 0;
|
|
17
|
+
|
|
18
|
+
function getBadgeNavigator(): BadgeNavigator {
|
|
19
|
+
return navigator as BadgeNavigator;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function shouldCountUnread(): boolean {
|
|
23
|
+
return document.visibilityState !== 'visible' || !document.hasFocus();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isDuplicateCompletion(now: number): boolean {
|
|
27
|
+
return now - lastNotifyAt < COMPLETION_DEDUPE_MS;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getOrCreateFaviconLink(): HTMLLinkElement | null {
|
|
31
|
+
if (faviconLink) return faviconLink;
|
|
32
|
+
const existing = document.querySelector<HTMLLinkElement>('link[rel~="icon"]');
|
|
33
|
+
if (existing) {
|
|
34
|
+
faviconLink = existing;
|
|
35
|
+
originalFaviconHref = existing.href || existing.getAttribute('href') || '';
|
|
36
|
+
return existing;
|
|
37
|
+
}
|
|
38
|
+
const link = document.createElement('link');
|
|
39
|
+
link.rel = 'icon';
|
|
40
|
+
document.head.appendChild(link);
|
|
41
|
+
faviconLink = link;
|
|
42
|
+
createdFaviconLink = true;
|
|
43
|
+
return link;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function renderBadgeFavicon(count: number): string {
|
|
47
|
+
const canvas = document.createElement('canvas');
|
|
48
|
+
canvas.width = BADGE_SIZE;
|
|
49
|
+
canvas.height = BADGE_SIZE;
|
|
50
|
+
const ctx = canvas.getContext('2d');
|
|
51
|
+
if (!ctx) return originalFaviconHref;
|
|
52
|
+
|
|
53
|
+
ctx.clearRect(0, 0, BADGE_SIZE, BADGE_SIZE);
|
|
54
|
+
ctx.font = '44px "Apple Color Emoji", "Segoe UI Emoji", sans-serif';
|
|
55
|
+
ctx.textAlign = 'center';
|
|
56
|
+
ctx.textBaseline = 'middle';
|
|
57
|
+
ctx.fillText('🦈', 28, 36);
|
|
58
|
+
|
|
59
|
+
ctx.beginPath();
|
|
60
|
+
ctx.arc(50, 15, count > 1 ? 12 : 9, 0, Math.PI * 2);
|
|
61
|
+
ctx.fillStyle = '#ff335f';
|
|
62
|
+
ctx.fill();
|
|
63
|
+
ctx.lineWidth = 4;
|
|
64
|
+
ctx.strokeStyle = '#ffffff';
|
|
65
|
+
ctx.stroke();
|
|
66
|
+
|
|
67
|
+
if (count > 1) {
|
|
68
|
+
ctx.fillStyle = '#ffffff';
|
|
69
|
+
ctx.font = 'bold 15px sans-serif';
|
|
70
|
+
ctx.textAlign = 'center';
|
|
71
|
+
ctx.textBaseline = 'middle';
|
|
72
|
+
ctx.fillText(String(Math.min(count, 9)), 50, 16);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return canvas.toDataURL('image/png');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function setAppBadgeBestEffort(count: number): Promise<void> {
|
|
79
|
+
const nav = getBadgeNavigator();
|
|
80
|
+
if (typeof nav.setAppBadge !== 'function') return;
|
|
81
|
+
try {
|
|
82
|
+
await nav.setAppBadge(count);
|
|
83
|
+
} catch {
|
|
84
|
+
// Browser support varies; title/favicon remain the reliable baseline.
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function clearAppBadgeBestEffort(): Promise<void> {
|
|
89
|
+
const nav = getBadgeNavigator();
|
|
90
|
+
if (typeof nav.clearAppBadge !== 'function') return;
|
|
91
|
+
try {
|
|
92
|
+
await nav.clearAppBadge();
|
|
93
|
+
} catch {
|
|
94
|
+
// Best-effort only.
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function applyUnreadState(): void {
|
|
99
|
+
document.title = `(${unreadCount}) ${baseTitle}`;
|
|
100
|
+
const link = getOrCreateFaviconLink();
|
|
101
|
+
if (link) link.href = renderBadgeFavicon(unreadCount);
|
|
102
|
+
void setAppBadgeBestEffort(unreadCount);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function restoreTitle(): void {
|
|
106
|
+
document.title = baseTitle;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function restoreFavicon(): void {
|
|
110
|
+
if (!faviconLink) return;
|
|
111
|
+
if (createdFaviconLink) {
|
|
112
|
+
faviconLink.remove();
|
|
113
|
+
faviconLink = null;
|
|
114
|
+
createdFaviconLink = false;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
faviconLink.href = originalFaviconHref;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function initAttentionBadge(): void {
|
|
121
|
+
if (initialized) return;
|
|
122
|
+
initialized = true;
|
|
123
|
+
baseTitle = document.title || BASE_TITLE_FALLBACK;
|
|
124
|
+
getOrCreateFaviconLink();
|
|
125
|
+
document.addEventListener('visibilitychange', () => {
|
|
126
|
+
if (document.visibilityState === 'visible') clearUnreadResponses();
|
|
127
|
+
});
|
|
128
|
+
window.addEventListener('focus', () => clearUnreadResponses());
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function notifyUnreadResponse(): void {
|
|
132
|
+
if (!initialized || !shouldCountUnread()) return;
|
|
133
|
+
const now = Date.now();
|
|
134
|
+
if (isDuplicateCompletion(now)) return;
|
|
135
|
+
lastNotifyAt = now;
|
|
136
|
+
unreadCount += 1;
|
|
137
|
+
applyUnreadState();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function clearUnreadResponses(): void {
|
|
141
|
+
if (!initialized || unreadCount === 0) return;
|
|
142
|
+
unreadCount = 0;
|
|
143
|
+
lastNotifyAt = 0;
|
|
144
|
+
restoreTitle();
|
|
145
|
+
restoreFavicon();
|
|
146
|
+
void clearAppBadgeBestEffort();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function getUnreadResponseCount(): number {
|
|
150
|
+
return unreadCount;
|
|
151
|
+
}
|
|
@@ -9,6 +9,9 @@ import { escapeHtml, cancelPostRender } from '../render.js';
|
|
|
9
9
|
import { getVirtualScroll } from '../virtual-scroll.js';
|
|
10
10
|
import { clearCache, upsertMessage } from './idb-cache.js';
|
|
11
11
|
import { ICONS } from '../icons.js';
|
|
12
|
+
import { clearUnreadResponses } from './attention-badge.js';
|
|
13
|
+
import { syncOrchestrateSnapshot } from '../ws.js';
|
|
14
|
+
import { waitForSettingsSaveIdle } from './settings-core.js';
|
|
12
15
|
|
|
13
16
|
let activeObjectURLs: string[] = [];
|
|
14
17
|
|
|
@@ -20,6 +23,10 @@ function getCommandTimeoutMs(text: string): number {
|
|
|
20
23
|
return /^\/compact(?:\s|$)/i.test(String(text || '').trim()) ? 5 * 60 * 1000 : 10_000;
|
|
21
24
|
}
|
|
22
25
|
|
|
26
|
+
function isOrchestrateCommand(text: string): boolean {
|
|
27
|
+
return /^\/(?:orchestrate|pabcd)(?:\s|$)/i.test(String(text || '').trim());
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
// In-flight guard: prevents double-send from rapid clicks / Enter-bursts while the
|
|
24
31
|
// POST to /api/message is outstanding. Server-side dedup in gateway.ts is the
|
|
25
32
|
// second line of defense. See devlog/_plan/260417_message_duplication/.
|
|
@@ -43,6 +50,7 @@ export async function sendMessage(): Promise<void> {
|
|
|
43
50
|
|
|
44
51
|
const text = input.value.trim();
|
|
45
52
|
if (!text && !state.attachedFiles.length) return;
|
|
53
|
+
clearUnreadResponses();
|
|
46
54
|
|
|
47
55
|
// Mark in-flight AND disable send button for visual feedback.
|
|
48
56
|
__chatSending = true;
|
|
@@ -50,12 +58,15 @@ export async function sendMessage(): Promise<void> {
|
|
|
50
58
|
const prevDisabled = sendBtn.disabled;
|
|
51
59
|
sendBtn.disabled = true;
|
|
52
60
|
try {
|
|
61
|
+
await waitForSettingsSaveIdle();
|
|
62
|
+
|
|
53
63
|
// File paths like /Users/junny/... or /tmp/foo — not commands
|
|
54
64
|
const afterSlash = text.slice(1).trim();
|
|
55
65
|
const firstToken = afterSlash.split(/\s+/)[0] || '';
|
|
56
66
|
const isFilePath = firstToken.includes('/') || firstToken.includes('\\');
|
|
57
67
|
|
|
58
68
|
if (text.startsWith('/') && !state.attachedFiles.length && !isFilePath) {
|
|
69
|
+
const shouldSyncOrchestrate = isOrchestrateCommand(text);
|
|
59
70
|
input.value = '';
|
|
60
71
|
resetInputHeight();
|
|
61
72
|
slashCmd.close();
|
|
@@ -100,6 +111,10 @@ export async function sendMessage(): Promise<void> {
|
|
|
100
111
|
if (result?.text) addSystemMsg(escapeHtml(result.text), '', result.type);
|
|
101
112
|
} catch (err) {
|
|
102
113
|
addSystemMsg(t('chat.cmd.fail', { msg: (err as Error).message }), '', 'error');
|
|
114
|
+
} finally {
|
|
115
|
+
if (shouldSyncOrchestrate) {
|
|
116
|
+
syncOrchestrateSnapshot('command').catch(() => {});
|
|
117
|
+
}
|
|
103
118
|
}
|
|
104
119
|
return;
|
|
105
120
|
}
|
|
@@ -244,6 +259,7 @@ export async function clearChat(): Promise<void> {
|
|
|
244
259
|
const { cleanupToolActivity } = await import('../ui.js');
|
|
245
260
|
cleanupToolActivity();
|
|
246
261
|
clearCache().catch(() => {});
|
|
262
|
+
clearUnreadResponses();
|
|
247
263
|
}
|
|
248
264
|
|
|
249
265
|
// ── Auto-resize textarea (RAF-batched to avoid blocking input) ──
|