@2lab.ai/ui 0.0.0-stage → 0.2.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.
- package/README.md +55 -2
- package/dist/agent-inbox.js +1061 -0
- package/dist/agent-inbox.js.map +1 -0
- package/dist/agent-queue.js +1610 -0
- package/dist/agent-queue.js.map +1 -0
- package/dist/agent-run.js +859 -0
- package/dist/agent-run.js.map +1 -0
- package/dist/ai-answer.js +687 -0
- package/dist/ai-answer.js.map +1 -0
- package/dist/components/agent-inbox/agent-inbox.d.ts +158 -0
- package/dist/components/agent-inbox/index.d.ts +2 -0
- package/dist/components/agent-inbox/styles.d.ts +6 -0
- package/dist/components/agent-queue/agent-queue.d.ts +41 -0
- package/dist/components/agent-queue/index.d.ts +6 -0
- package/dist/components/agent-queue/logic.d.ts +261 -0
- package/dist/components/agent-queue/strings.d.ts +473 -0
- package/dist/components/agent-queue/styles.d.ts +8 -0
- package/dist/components/agent-run/agent-run.d.ts +273 -0
- package/dist/components/agent-run/index.d.ts +2 -0
- package/dist/components/agent-run/styles.d.ts +6 -0
- package/dist/components/ai-answer/ai-answer.d.ts +92 -0
- package/dist/components/ai-answer/index.d.ts +2 -0
- package/dist/components/ai-answer/styles.d.ts +2 -0
- package/dist/components/context-window/context-window.d.ts +156 -0
- package/dist/components/context-window/index.d.ts +2 -0
- package/dist/components/context-window/styles.d.ts +2 -0
- package/dist/components/memory-panel/index.d.ts +2 -0
- package/dist/components/memory-panel/memory-panel.d.ts +101 -0
- package/dist/components/memory-panel/styles.d.ts +2 -0
- package/dist/components/model-picker/index.d.ts +2 -0
- package/dist/components/model-picker/model-picker.d.ts +136 -0
- package/dist/components/model-picker/morph.d.ts +9 -0
- package/dist/components/model-picker/styles.d.ts +6 -0
- package/dist/components/tool-call-card/index.d.ts +7 -0
- package/dist/components/tool-call-card/logic.d.ts +87 -0
- package/dist/components/tool-call-card/strings.d.ts +94 -0
- package/dist/components/tool-call-card/styles.d.ts +5 -0
- package/dist/components/tool-call-card/tool-call-card.d.ts +49 -0
- package/dist/components/tool-call-card/tool-call-group.d.ts +32 -0
- package/dist/context-window.js +384 -0
- package/dist/context-window.js.map +1 -0
- package/dist/core/element.d.ts +41 -0
- package/dist/core/format.d.ts +14 -0
- package/dist/core/html.d.ts +10 -0
- package/dist/core/tokens.d.ts +14 -0
- package/dist/format-BBGWXPzk.js +51 -0
- package/dist/format-BBGWXPzk.js.map +1 -0
- package/dist/html-elD6Bb_u.js +175 -0
- package/dist/html-elD6Bb_u.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +129 -0
- package/dist/index.js.map +1 -0
- package/dist/memory-panel.js +1255 -0
- package/dist/memory-panel.js.map +1 -0
- package/dist/model-picker.js +858 -0
- package/dist/model-picker.js.map +1 -0
- package/dist/tokens.css +4 -0
- package/dist/tool-call-card.js +1046 -0
- package/dist/tool-call-card.js.map +1 -0
- package/package.json +79 -4
|
@@ -0,0 +1,859 @@
|
|
|
1
|
+
import { d as define, U as UiElement, f as escAttr, e as esc } from "./html-elD6Bb_u.js";
|
|
2
|
+
import { b as fmtDuration, a as fmtClock } from "./format-BBGWXPzk.js";
|
|
3
|
+
const styles = `
|
|
4
|
+
:host { --ar-wait-text: color-mix(in srgb, var(--st-wait) 74%, var(--text)); }
|
|
5
|
+
.ar { --ar-pad: 16px; container: ar / inline-size; position: relative; overflow: hidden; overflow: clip; border: 1px solid var(--border); border-radius: 16px; background: var(--surface); color: var(--text); font-size: 13px; line-height: 1.45; }
|
|
6
|
+
|
|
7
|
+
/* ── Head: title · subtitle · status pill · started + live elapsed · Stop ── */
|
|
8
|
+
.ar-head { padding: 14px var(--ar-pad) 12px; }
|
|
9
|
+
.ar-title { margin: 0; font-size: 15px; font-weight: 650; line-height: 22px; letter-spacing: -0.005em; color: var(--text); overflow-wrap: anywhere; }
|
|
10
|
+
.ar-title:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; border-radius: 4px; }
|
|
11
|
+
.ar-sub { margin: 2px 0 0; font-size: 12px; line-height: 17px; color: var(--text-secondary); overflow-wrap: anywhere; }
|
|
12
|
+
.ps-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; min-width: 0; margin-top: 10px; font-size: 12.5px; color: var(--text-secondary); }
|
|
13
|
+
.ps-chip { --ag-c: var(--text-tertiary); --ag-t: var(--ag-c); display: inline-flex; align-items: center; gap: 5px; min-width: 0; height: 24px; padding: 0 10px 0 8px; border-radius: 99px; background: color-mix(in srgb, var(--ag-c) 13%, transparent); white-space: nowrap; }
|
|
14
|
+
.ps-chip.is-running { --ag-c: var(--st-run); }
|
|
15
|
+
.ps-chip.is-waiting { --ag-c: var(--st-wait); --ag-t: var(--ar-wait-text); }
|
|
16
|
+
.ps-chip.is-done { --ag-c: var(--st-done); }
|
|
17
|
+
.ps-chip.is-failed { --ag-c: var(--st-fail); --ag-t: var(--st-fail-text); }
|
|
18
|
+
.ps-label { font-size: 11px; font-weight: 700; letter-spacing: 0.02em; color: var(--text-tertiary); }
|
|
19
|
+
.ps-icon { display: inline-grid; place-items: center; width: 14px; height: 14px; color: var(--ag-c); }
|
|
20
|
+
.ps-icon svg { width: 12px; height: 12px; }
|
|
21
|
+
.ps-icon .ar-spin { width: 10px; height: 10px; }
|
|
22
|
+
.ps-word { font-weight: 650; color: var(--ag-t); }
|
|
23
|
+
.ps-time { display: inline-flex; align-items: center; gap: 5px; min-width: 0; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
24
|
+
.ps-clock { display: inline-grid; flex: none; color: var(--text-tertiary); }
|
|
25
|
+
.ps-clock svg { width: 13px; height: 13px; }
|
|
26
|
+
.ps-dot { color: var(--text-tertiary); }
|
|
27
|
+
.ps-actions { display: inline-flex; flex: none; align-items: center; gap: 6px; margin-left: auto; }
|
|
28
|
+
.ar-stop { display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 30px; padding: 3px 12px; border: 1px solid var(--ib-card-border); border-radius: 8px; background: var(--ib-card); color: var(--st-fail-text); font-size: 12.5px; font-weight: 500; white-space: nowrap; transition: border-color 150ms ease, transform var(--dur-press) var(--ease-out); }
|
|
29
|
+
.ar-stop:disabled { cursor: not-allowed; opacity: 0.5; }
|
|
30
|
+
.ar-stop:active:not(:disabled) { transform: scale(0.97); }
|
|
31
|
+
|
|
32
|
+
/* ── Bands: one column (plan → needs input → details); from 760px two — plan + details | side ── */
|
|
33
|
+
.ar-body { display: grid; grid-template-columns: minmax(0, 1fr); align-items: start; border-top: 1px solid var(--border); }
|
|
34
|
+
.ar-band { min-width: 0; padding: 10px var(--ar-pad) 12px; }
|
|
35
|
+
.ar-band + .ar-band { border-top: 1px solid var(--border); }
|
|
36
|
+
.ar-sec-h { margin: 0 0 6px; font-size: 12px; font-weight: 700; line-height: 18px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-secondary); }
|
|
37
|
+
@container ar (min-width: 760px) {
|
|
38
|
+
.ar-head, .ar-body { --ar-pad: 20px; }
|
|
39
|
+
.ar-head { padding-top: 16px; padding-bottom: 14px; }
|
|
40
|
+
.ar-band { padding-top: 14px; padding-bottom: 14px; }
|
|
41
|
+
.ar-body[data-side] { grid-template-columns: minmax(0, 1fr) minmax(340px, 440px); grid-template-rows: auto 1fr; }
|
|
42
|
+
.ar-body[data-side] > .ar-band { border-top: 0; }
|
|
43
|
+
.ar-body[data-side] > .ar-plan { grid-area: 1 / 1; }
|
|
44
|
+
.ar-body[data-side="needs"] > .ar-needs { grid-area: 1 / 2 / 3 / 3; align-self: stretch; border-left: 1px solid var(--border); }
|
|
45
|
+
.ar-body[data-side="needs"] > .ar-details { grid-area: 2 / 1; }
|
|
46
|
+
.ar-body[data-side="needs"] > .ar-plan ~ .ar-details { border-top: 1px solid var(--border); }
|
|
47
|
+
.ar-body[data-side="details"] > .ar-details { grid-area: 1 / 2 / 3 / 3; align-self: stretch; border-left: 1px solid var(--border); }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ── PLAN: a marker per step (check · spinner · ring · ✕ · dash) on one rail, time on the right, phase foot ── */
|
|
51
|
+
.pl-list { position: relative; margin: 0; padding: 0; list-style: none; }
|
|
52
|
+
.pl-empty { margin: 0; padding: 2px 0 4px; font-size: 12px; color: var(--text-tertiary); }
|
|
53
|
+
.pl-step { position: relative; display: grid; grid-template-columns: 18px minmax(0, 1fr) auto; column-gap: 10px; align-items: start; padding: 5px 0; font-size: 12.5px; line-height: 18px; }
|
|
54
|
+
.pl-marker { position: relative; z-index: 1; display: grid; place-items: center; width: 18px; height: 18px; border-radius: 50%; background: var(--surface); box-shadow: inset 0 0 0 1px var(--ib-card-border); color: var(--text-secondary); }
|
|
55
|
+
.pl-step[data-step="done"] .pl-marker { background: var(--ib-pill); box-shadow: none; color: var(--text); }
|
|
56
|
+
.pl-step[data-step="running"] .pl-marker { box-shadow: none; color: var(--st-run); }
|
|
57
|
+
.pl-step[data-step="failed"] .pl-marker { background: color-mix(in srgb, var(--st-fail) 16%, var(--surface)); box-shadow: none; color: var(--st-fail-text); }
|
|
58
|
+
.pl-step[data-step="skipped"] .pl-marker { box-shadow: none; border: 1px dashed var(--text-tertiary); color: var(--text-tertiary); }
|
|
59
|
+
.pl-marker svg { width: 10px; height: 10px; }
|
|
60
|
+
.pl-marker .ar-spin { width: 11px; height: 11px; }
|
|
61
|
+
.pl-step.pl-pop .pl-marker > * { animation: pl-pop var(--dur-move) var(--ease-out) both; }
|
|
62
|
+
@keyframes pl-pop { from { opacity: 0; transform: scale(0.6); } }
|
|
63
|
+
.pl-list > .pl-step:not(:last-child)::before { content: ""; position: absolute; left: 8.5px; top: 25px; bottom: -3px; width: 1px; background: var(--ib-card-border); }
|
|
64
|
+
.pl-list > .pl-step[data-step="done"]:not(:last-child)::after { content: ""; position: absolute; left: 8.5px; top: 25px; bottom: -3px; width: 1px; background: var(--text-tertiary); transform-origin: top; }
|
|
65
|
+
.pl-list > .pl-step.pl-pop[data-step="done"]:not(:last-child)::after { animation: pl-rail 560ms var(--ease-out) both; }
|
|
66
|
+
@keyframes pl-rail { from { transform: scaleY(0); } }
|
|
67
|
+
.pl-main { min-width: 0; }
|
|
68
|
+
.pl-line { display: block; overflow-wrap: anywhere; }
|
|
69
|
+
.pl-ref { margin-right: 6px; font-size: 11px; color: var(--text-tertiary); font-variant-numeric: tabular-nums; }
|
|
70
|
+
.pl-text { color: var(--text-secondary); }
|
|
71
|
+
.pl-step[data-step="running"] .pl-text { font-weight: 600; color: var(--text); }
|
|
72
|
+
.pl-step[data-step="failed"] .pl-text { color: var(--text); }
|
|
73
|
+
.pl-step[data-step="skipped"] .pl-text { color: var(--text-tertiary); }
|
|
74
|
+
.pl-chip { display: inline-block; margin-left: 6px; padding: 0 6px; border-radius: 6px; background: var(--ib-pill); color: var(--text-secondary); font-size: 11px; font-weight: 400; line-height: 16px; white-space: nowrap; font-variant-numeric: tabular-nums; vertical-align: 1px; }
|
|
75
|
+
.pl-sub { display: block; margin-top: 1px; overflow: hidden; font-size: 12px; color: var(--text-tertiary); text-overflow: ellipsis; white-space: nowrap; }
|
|
76
|
+
.pl-step[data-step="failed"] .pl-sub { color: var(--st-fail-text); white-space: normal; overflow-wrap: anywhere; }
|
|
77
|
+
.pl-aside { min-height: 18px; font-size: 12px; color: var(--text-tertiary); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
78
|
+
.pl-step[data-step="running"] .pl-aside { color: var(--text); }
|
|
79
|
+
.pl-fold { padding: 0 0 0 28px; }
|
|
80
|
+
.pl-fold-btn { min-height: 28px; padding: 4px 0; border: 0; background: none; color: var(--accent); font-size: 11.5px; font-weight: 600; }
|
|
81
|
+
.pl-fold-btn:hover { text-decoration: underline; }
|
|
82
|
+
.pl-foot { position: sticky; bottom: 0; z-index: 2; display: flex; align-items: center; gap: 8px; margin: 6px calc(-1 * var(--ar-pad)) -12px; padding: 8px var(--ar-pad) 10px; border-top: 1px solid var(--border); background: var(--surface); font-size: 12.5px; color: var(--text-secondary); }
|
|
83
|
+
.pl-foot-st { display: inline-flex; align-items: center; gap: 7px; min-width: 0; color: var(--text); font-variant-numeric: tabular-nums; }
|
|
84
|
+
.pl-foot-icon { display: inline-grid; place-items: center; width: 14px; height: 14px; color: var(--text-tertiary); }
|
|
85
|
+
.pl-foot[data-phase="running"] .pl-foot-icon { color: var(--st-run); }
|
|
86
|
+
.pl-foot[data-phase="done"] .pl-foot-icon { color: var(--st-done); }
|
|
87
|
+
.pl-foot[data-phase="failed"] .pl-foot-icon { color: var(--st-fail-text); }
|
|
88
|
+
.pl-foot-icon svg { width: 12px; height: 12px; }
|
|
89
|
+
.pl-foot-icon .ar-spin { width: 11px; height: 11px; }
|
|
90
|
+
.pl-foot-time { margin-left: auto; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
91
|
+
@container ar (min-width: 760px) { .pl-foot { margin-bottom: -14px; } }
|
|
92
|
+
|
|
93
|
+
/* ── 입력 필요 / Needs input: a head with the count, then cards in a tray ── */
|
|
94
|
+
.ar-needs { padding-top: 4px; }
|
|
95
|
+
.ar-needs-head { display: flex; align-items: center; gap: 8px; margin: 0; padding: 8px 0; font-size: 13px; font-weight: 650; line-height: 18px; color: var(--text); }
|
|
96
|
+
.ar-needs-head:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; border-radius: 6px; }
|
|
97
|
+
.pn-count { display: inline-grid; place-items: center; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 999px; background: color-mix(in srgb, var(--st-wait) 24%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--st-wait) 55%, transparent); color: var(--text); font-size: 11px; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; }
|
|
98
|
+
.pn-count.is-pop { animation: pn-pop var(--dur-move) var(--ease-out) both; }
|
|
99
|
+
@keyframes pn-pop { from { opacity: 0; transform: scale(0.6); } }
|
|
100
|
+
.ap-list { display: flex; flex-direction: column; gap: 10px; }
|
|
101
|
+
.ap-card { container: ap / inline-size; position: relative; padding: 4px; border: 1px solid var(--ib-card-border); border-radius: 14px; background: var(--ib-col); color: var(--ib-text); }
|
|
102
|
+
.ap-card:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; }
|
|
103
|
+
.ap-label { padding: 4px 8px 6px; overflow: hidden; font-size: 12px; line-height: 16px; color: var(--ib-muted); white-space: nowrap; text-overflow: ellipsis; font-variant-numeric: tabular-nums; }
|
|
104
|
+
.ap-body { padding: 14px; border-radius: 11px; background: var(--ib-card); box-shadow: 0 0 0 1px var(--ib-card-border); }
|
|
105
|
+
.ap-head { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: start; gap: 10px; }
|
|
106
|
+
.ap-icon { display: grid; place-items: center; width: 32px; height: 32px; border-radius: 8px; background: var(--ib-pill); color: var(--ib-text); }
|
|
107
|
+
.ap-icon svg { width: 16px; height: 16px; }
|
|
108
|
+
.ap-titles { min-width: 0; padding-top: 1px; }
|
|
109
|
+
.ap-title { overflow: hidden; font-size: 14px; font-weight: 600; line-height: 20px; color: var(--ib-text); white-space: nowrap; text-overflow: ellipsis; }
|
|
110
|
+
.ap-question .ap-title { overflow: visible; white-space: pre-line; overflow-wrap: anywhere; }
|
|
111
|
+
.ap-desc { margin-top: 1px; overflow: hidden; font-size: 13px; line-height: 18px; color: var(--ib-text-2); white-space: nowrap; text-overflow: ellipsis; }
|
|
112
|
+
.ap-qbody { margin-top: 4px; font-size: 13px; line-height: 18px; color: var(--ib-text-2); white-space: pre-line; overflow-wrap: anywhere; }
|
|
113
|
+
.ap-pill { display: inline-flex; align-items: center; gap: 6px; height: 22px; margin-top: 1px; padding: 0 9px 0 8px; border-radius: 999px; background: var(--ib-pill); color: var(--ib-text-2); font-size: 12px; font-weight: 500; line-height: 1; white-space: nowrap; }
|
|
114
|
+
.ap-pill::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: 0.8; }
|
|
115
|
+
.ap-pill[data-kind="rule"] { background: color-mix(in srgb, var(--st-fail) 14%, transparent); color: var(--st-fail-text); }
|
|
116
|
+
.ap-pill[data-kind="question"] { background: color-mix(in srgb, var(--st-wait) 16%, transparent); color: var(--ar-wait-text); }
|
|
117
|
+
.ap-details { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 6px 16px; max-height: 320px; overflow: auto; margin: 12px 0 0; padding: 10px 12px; border-radius: 9px; background: var(--ib-col); box-shadow: inset 0 0 0 1px var(--ib-card-border); font-size: 12.5px; line-height: 18px; }
|
|
118
|
+
.ap-detail { display: contents; }
|
|
119
|
+
.ap-detail dt { color: var(--ib-muted); }
|
|
120
|
+
.ap-detail dd { min-width: 0; margin: 0; color: var(--ib-text); font-family: var(--mono); font-size: 12px; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
121
|
+
.ap-raw { max-height: 320px; overflow: auto; margin: 12px 0 0; padding: 10px 12px; border-radius: 9px; background: var(--ib-col); box-shadow: inset 0 0 0 1px var(--ib-card-border); color: var(--ib-text); font-family: var(--mono); font-size: 12px; line-height: 18px; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
122
|
+
.ap-truncated { margin: 4px 0 0; font-size: 11.5px; color: var(--ib-muted); }
|
|
123
|
+
.ap-rules { display: flex; flex-direction: column; gap: 3px; margin: 10px 0 0; font-size: 12px; line-height: 17px; color: var(--ib-text); overflow-wrap: anywhere; }
|
|
124
|
+
.ap-rules-label { font-weight: 600; color: var(--ib-text-2); }
|
|
125
|
+
.ap-rule code { padding: 1px 5px; border-radius: 4px; background: color-mix(in srgb, var(--orange) 14%, transparent); color: var(--orange); font-family: var(--mono); font-size: 0.95em; }
|
|
126
|
+
.ap-alts { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 10px; }
|
|
127
|
+
.ap-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin-top: 8px; }
|
|
128
|
+
.ap-expires { font-size: 12.5px; color: var(--ib-muted); font-variant-numeric: tabular-nums; white-space: nowrap; transition: color var(--dur-fade) linear; }
|
|
129
|
+
.ap-expires.is-low { color: var(--ar-wait-text); }
|
|
130
|
+
.ap-actions { display: flex; flex: 0 0 auto; gap: 4px; margin-left: auto; }
|
|
131
|
+
.ap-btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 34px; padding: 0 12px; border: 0; border-radius: 10px; font-size: 13px; font-weight: 500; line-height: 1; white-space: nowrap; transition: background-color 150ms ease, opacity 150ms ease, transform var(--dur-press) var(--ease-out); }
|
|
132
|
+
.ap-ghost { min-height: 28px; padding: 0 10px; border-radius: 8px; background: transparent; color: var(--ib-text-2); font-size: 12.5px; box-shadow: inset 0 0 0 1px var(--ib-card-border); }
|
|
133
|
+
.ap-quiet { background: transparent; color: var(--ib-text); box-shadow: inset 0 0 0 1px var(--ib-card-border); }
|
|
134
|
+
.ap-primary { background: var(--ib-text); color: var(--ib-card); }
|
|
135
|
+
.ap-btn:disabled, .ap-choice:disabled { cursor: not-allowed; opacity: 0.5; }
|
|
136
|
+
.ap-btn.is-busy, .ap-choice.is-busy { opacity: 1; cursor: progress; }
|
|
137
|
+
.ap-btn:active:not(:disabled), .ap-choice:active:not(:disabled) { transform: scale(0.97); }
|
|
138
|
+
.ap-kbd { display: inline-grid; place-items: center; min-width: 20px; height: 20px; margin-right: -4px; padding: 0 5px; border-radius: 6px; background: color-mix(in srgb, currentColor 12%, transparent); font-size: 11px; font-weight: 500; opacity: 0.8; }
|
|
139
|
+
.ap-btn:disabled .ap-kbd, .ap-choice:disabled .ap-kbd { display: none; }
|
|
140
|
+
.ap-choices { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }
|
|
141
|
+
.ap-choice { position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 2px; width: 100%; min-height: 36px; padding: 8px 12px; border: 0; border-radius: 10px; background: transparent; color: var(--ib-text); box-shadow: inset 0 0 0 1px var(--ib-card-border); font-size: 13px; line-height: 18px; text-align: left; transition: background-color 150ms ease, opacity 150ms ease, transform var(--dur-press) var(--ease-out); }
|
|
142
|
+
.ap-rec { padding-right: 52px; background: var(--ib-text); color: var(--ib-card); box-shadow: none; font-weight: 500; }
|
|
143
|
+
.ap-rec .ap-kbd { position: absolute; top: 8px; right: 8px; margin: 0; background: color-mix(in srgb, currentColor 14%, transparent); }
|
|
144
|
+
.ap-choice-label { overflow-wrap: anywhere; }
|
|
145
|
+
.ap-choice-label .ar-spin { margin-right: 6px; }
|
|
146
|
+
.ap-choice-tag { margin-left: 8px; padding: 1px 6px; border-radius: 6px; background: color-mix(in srgb, currentColor 14%, transparent); font-size: 11px; font-weight: 500; white-space: nowrap; }
|
|
147
|
+
.ap-choice-desc { font-size: 12px; font-weight: 400; opacity: 0.75; overflow-wrap: anywhere; }
|
|
148
|
+
.ap-lock { margin: 8px 0 0; font-size: 12px; color: var(--ib-muted); overflow-wrap: anywhere; }
|
|
149
|
+
.ap-note { margin: 8px 0 0; font-size: 12px; color: var(--st-fail-text); overflow-wrap: anywhere; }
|
|
150
|
+
.ap-resolved { display: flex; align-items: center; gap: 10px; min-height: 44px; padding: 10px 12px; border-radius: 11px; background: var(--ib-card); box-shadow: 0 0 0 1px var(--ib-card-border); }
|
|
151
|
+
.ap-resolved.is-enter { animation: ap-in var(--dur-move) var(--ease-out) both; }
|
|
152
|
+
@keyframes ap-in { from { opacity: 0; transform: translateY(4px); } }
|
|
153
|
+
.ap-mark { display: grid; flex: none; place-items: center; width: 22px; height: 22px; border-radius: 50%; background: color-mix(in srgb, var(--st-done) 16%, transparent); color: var(--st-done); font-size: 12px; font-weight: 700; }
|
|
154
|
+
.ap-mark svg { width: 12px; height: 12px; }
|
|
155
|
+
.ap-resolved[data-decision="deny"] .ap-mark, .ap-resolved[data-decision="explain"] .ap-mark { background: var(--ib-pill); color: var(--ib-text-2); }
|
|
156
|
+
.ap-row-title { flex: 1 1 auto; min-width: 0; overflow: hidden; font-size: 13.5px; font-weight: 500; color: var(--ib-text); white-space: nowrap; text-overflow: ellipsis; }
|
|
157
|
+
.ap-row-status { flex: 0 1 auto; min-width: 0; overflow: hidden; font-size: 12.5px; color: var(--ib-muted); white-space: nowrap; text-overflow: ellipsis; }
|
|
158
|
+
/* An answered question keeps its whole answer readable: the answer goes on a second line under the question. */
|
|
159
|
+
.ap-resolved[data-decision="answer"] { display: grid; grid-template-columns: 22px minmax(0, 1fr); column-gap: 10px; row-gap: 1px; }
|
|
160
|
+
.ap-resolved[data-decision="answer"] .ap-row-status { grid-column: 2; white-space: normal; overflow-wrap: anywhere; }
|
|
161
|
+
@container ap (max-width: 379px) {
|
|
162
|
+
.ap-actions { flex: 1 1 100%; margin-left: 0; }
|
|
163
|
+
.ap-actions .ap-btn { flex: 1 1 0; min-width: 0; }
|
|
164
|
+
.ap-kbd { display: none; }
|
|
165
|
+
.ap-rec { padding-right: 12px; }
|
|
166
|
+
}
|
|
167
|
+
@container ap (max-width: 319px) {
|
|
168
|
+
.ap-body { padding: 12px; }
|
|
169
|
+
.ap-pill-word { display: none; }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* ── 세부 / Details: label left, value right ── */
|
|
173
|
+
.pi-details { display: grid; gap: 7px; margin: 0; }
|
|
174
|
+
.pi-details > div { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; min-width: 0; }
|
|
175
|
+
.pi-details dt { flex: 0 1 auto; min-width: 0; font-size: 12px; color: var(--text-tertiary); }
|
|
176
|
+
.pi-details dd { min-width: 0; margin: 0; font-size: 12.5px; font-weight: 600; color: var(--text); text-align: right; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
|
|
177
|
+
|
|
178
|
+
/* ── Spinner ── */
|
|
179
|
+
.ar-spin { display: inline-block; flex: none; width: 12px; height: 12px; border: 2px solid var(--ib-pill); border-top-color: var(--st-run); border-right-color: var(--st-run); border-radius: 50%; vertical-align: -1px; animation: ar-spin 0.9s linear infinite; }
|
|
180
|
+
.ap-btn .ar-spin, .ap-choice .ar-spin, .ar-stop .ar-spin { width: 11px; height: 11px; border-width: 1.5px; border-color: color-mix(in srgb, currentColor 25%, transparent); border-top-color: currentColor; border-right-color: currentColor; }
|
|
181
|
+
@keyframes ar-spin { to { transform: rotate(360deg); } }
|
|
182
|
+
|
|
183
|
+
/* Key hints only with a hover pointer; quiet hovers; 44px answers on touch. */
|
|
184
|
+
@media (hover: none) {
|
|
185
|
+
.ap-kbd { display: none; }
|
|
186
|
+
.ap-rec { padding-right: 12px; }
|
|
187
|
+
}
|
|
188
|
+
@media (hover: hover) and (pointer: fine) {
|
|
189
|
+
.ap-ghost:not(:disabled):hover, .ap-quiet:not(:disabled):hover, .ap-choice:not(.ap-rec):not(:disabled):hover { background: var(--ib-pill); }
|
|
190
|
+
.ap-primary:not(:disabled):hover, .ap-rec:not(:disabled):hover { opacity: 0.88; }
|
|
191
|
+
.ar-stop:not(:disabled):hover { border-color: var(--st-fail); }
|
|
192
|
+
}
|
|
193
|
+
@media (pointer: coarse) {
|
|
194
|
+
.ap-btn, .ap-choice, .ar-stop, .pl-fold-btn { min-height: 44px; }
|
|
195
|
+
}
|
|
196
|
+
`;
|
|
197
|
+
const STRINGS = {
|
|
198
|
+
en: {
|
|
199
|
+
chip: "Run",
|
|
200
|
+
status: {
|
|
201
|
+
running: "Running",
|
|
202
|
+
waiting: "Needs input",
|
|
203
|
+
idle: "Idle",
|
|
204
|
+
done: "Done",
|
|
205
|
+
failed: "Failed",
|
|
206
|
+
stopped: "Stopped"
|
|
207
|
+
},
|
|
208
|
+
statusSay: (w) => `Status: ${w}`,
|
|
209
|
+
started: (t) => `Started ${t}`,
|
|
210
|
+
liveTitle: "This run: started · elapsed",
|
|
211
|
+
ranTitle: "Started · run time",
|
|
212
|
+
active: "Active",
|
|
213
|
+
activeTitle: "Active time",
|
|
214
|
+
stop: "Stop",
|
|
215
|
+
stopLabel: "Stop — stop the run",
|
|
216
|
+
plan: "Plan",
|
|
217
|
+
planSteps: "Plan steps",
|
|
218
|
+
noSteps: "No steps yet",
|
|
219
|
+
stepSr: {
|
|
220
|
+
pending: "pending",
|
|
221
|
+
running: "in progress",
|
|
222
|
+
done: "done",
|
|
223
|
+
failed: "failed",
|
|
224
|
+
skipped: "skipped"
|
|
225
|
+
},
|
|
226
|
+
calls: (n) => n === 1 ? "1 tool call" : `${n} tool calls`,
|
|
227
|
+
foldShow: (n) => `Show ${n} more completed`,
|
|
228
|
+
foldHide: "Hide completed",
|
|
229
|
+
phase: { running: "Running", done: "Done", failed: "Failed", idle: "Idle" },
|
|
230
|
+
stepDone: (l) => `Step done: ${l}`,
|
|
231
|
+
stepFailed: (l) => `Step failed: ${l}`,
|
|
232
|
+
needs: "Needs input",
|
|
233
|
+
apTitle: (tool) => `${tool} approval`,
|
|
234
|
+
requested: (t) => `requested ${t}`,
|
|
235
|
+
risk: "Risk",
|
|
236
|
+
riskWord: " rules",
|
|
237
|
+
toolPill: "Tool approval",
|
|
238
|
+
questionPill: "Question",
|
|
239
|
+
rules: (n) => n > 1 ? "Matched dangerous-command rules" : "Matched dangerous-command rule",
|
|
240
|
+
truncated: "Showing the beginning only",
|
|
241
|
+
btn: { explain: "Explain", approve_disable_rule: "Approve & disable rule", deny: "Deny", approve: "Approve" },
|
|
242
|
+
expiresIn: (c) => `Expires in ${c}`,
|
|
243
|
+
expired: "Expired",
|
|
244
|
+
expiresAt: (t) => `Expires at ${t}`,
|
|
245
|
+
answered: {
|
|
246
|
+
approve: "Approved",
|
|
247
|
+
deny: "Denied",
|
|
248
|
+
explain: "Explanation requested",
|
|
249
|
+
approve_disable_rule: "Approved · rule disabled"
|
|
250
|
+
},
|
|
251
|
+
sent: {
|
|
252
|
+
approve: "Approval sent",
|
|
253
|
+
deny: "Denial sent",
|
|
254
|
+
explain: "Explanation requested",
|
|
255
|
+
approve_disable_rule: "Approval sent · rule disabled"
|
|
256
|
+
},
|
|
257
|
+
arrived: (tool) => `Approval request — ${tool}`,
|
|
258
|
+
recommended: "Recommended",
|
|
259
|
+
asked: (title) => `Question — ${title}`,
|
|
260
|
+
answeredQ: "Answered",
|
|
261
|
+
answerSent: (label) => `Answer sent — ${label}`,
|
|
262
|
+
details: "Details"
|
|
263
|
+
},
|
|
264
|
+
ko: {
|
|
265
|
+
chip: "세션",
|
|
266
|
+
status: {
|
|
267
|
+
running: "실행 중",
|
|
268
|
+
waiting: "입력 대기",
|
|
269
|
+
idle: "대기",
|
|
270
|
+
done: "완료",
|
|
271
|
+
failed: "실패",
|
|
272
|
+
stopped: "중단"
|
|
273
|
+
},
|
|
274
|
+
statusSay: (w) => `상태: ${w}`,
|
|
275
|
+
started: (t) => `시작 ${t}`,
|
|
276
|
+
liveTitle: "이번 턴 시작 · 경과",
|
|
277
|
+
ranTitle: "시작 · 실행 시간",
|
|
278
|
+
active: "활성",
|
|
279
|
+
activeTitle: "활성 시간",
|
|
280
|
+
stop: "Stop",
|
|
281
|
+
stopLabel: "Stop — 세션 중지",
|
|
282
|
+
plan: "계획",
|
|
283
|
+
planSteps: "계획 단계",
|
|
284
|
+
noSteps: "아직 작업 목록이 없습니다",
|
|
285
|
+
stepSr: {
|
|
286
|
+
pending: "대기",
|
|
287
|
+
running: "진행 중",
|
|
288
|
+
done: "완료",
|
|
289
|
+
failed: "실패",
|
|
290
|
+
skipped: "건너뜀"
|
|
291
|
+
},
|
|
292
|
+
calls: (n) => `툴 ${n}회`,
|
|
293
|
+
foldShow: (n) => `완료 ${n}개 더 보기`,
|
|
294
|
+
foldHide: "완료 숨기기",
|
|
295
|
+
phase: { running: "실행 중", done: "완료", failed: "실패", idle: "대기" },
|
|
296
|
+
stepDone: (l) => `단계 완료: ${l}`,
|
|
297
|
+
stepFailed: (l) => `단계 실패: ${l}`,
|
|
298
|
+
needs: "입력 필요",
|
|
299
|
+
apTitle: (tool) => `${tool} 실행 승인`,
|
|
300
|
+
requested: (t) => `${t} 요청`,
|
|
301
|
+
risk: "위험",
|
|
302
|
+
riskWord: " 규칙",
|
|
303
|
+
toolPill: "도구 승인",
|
|
304
|
+
questionPill: "질문",
|
|
305
|
+
rules: (n) => n > 1 ? "Matched dangerous-command rules" : "Matched dangerous-command rule",
|
|
306
|
+
truncated: "앞부분만 표시합니다",
|
|
307
|
+
btn: { explain: "Explain", approve_disable_rule: "Approve & disable rule", deny: "Deny", approve: "Approve" },
|
|
308
|
+
expiresIn: (c) => `만료까지 ${c}`,
|
|
309
|
+
expired: "만료됨",
|
|
310
|
+
expiresAt: (t) => `${t}에 만료`,
|
|
311
|
+
answered: {
|
|
312
|
+
approve: "승인함",
|
|
313
|
+
deny: "거부함",
|
|
314
|
+
explain: "설명 요청함",
|
|
315
|
+
approve_disable_rule: "승인함 · 규칙 끔"
|
|
316
|
+
},
|
|
317
|
+
sent: {
|
|
318
|
+
approve: "승인을 보냈습니다",
|
|
319
|
+
deny: "거부를 보냈습니다",
|
|
320
|
+
explain: "설명을 요청했습니다",
|
|
321
|
+
approve_disable_rule: "승인을 보냈습니다 · 규칙 끔"
|
|
322
|
+
},
|
|
323
|
+
arrived: (tool) => `승인 요청 — ${tool}`,
|
|
324
|
+
recommended: "추천",
|
|
325
|
+
asked: (title) => `질문 — ${title}`,
|
|
326
|
+
answeredQ: "답함",
|
|
327
|
+
answerSent: (label) => `답을 보냈습니다 — ${label}`,
|
|
328
|
+
details: "세부"
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
const ICON = {
|
|
332
|
+
check: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 8.5l3 3 6-7"/></svg>',
|
|
333
|
+
cross: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4.5 4.5l7 7M11.5 4.5l-7 7"/></svg>',
|
|
334
|
+
stop: '<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><rect x="4.5" y="4.5" width="7" height="7" rx="1.2"/></svg>',
|
|
335
|
+
clock: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="8" cy="8" r="5.5"/><path d="M8 5v3.2l2 1.3"/></svg>',
|
|
336
|
+
dot: '<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><circle cx="8" cy="8" r="3"/></svg>',
|
|
337
|
+
dash: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M5 8h6"/></svg>',
|
|
338
|
+
spin: '<span class="ar-spin" aria-hidden="true"></span>',
|
|
339
|
+
lock: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="7" width="9" height="6.5" rx="1.5"/><path d="M5.5 7V5a2.5 2.5 0 0 1 5 0v2"/></svg>',
|
|
340
|
+
ask: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="8" cy="8" r="6"/><path d="M6.3 6.3a1.8 1.8 0 0 1 3.4.7c0 1.2-1.7 1.5-1.7 2.6M8 11.3v.1"/></svg>'
|
|
341
|
+
};
|
|
342
|
+
const STATUS_ICON = {
|
|
343
|
+
running: ICON.spin,
|
|
344
|
+
waiting: ICON.clock,
|
|
345
|
+
idle: ICON.dot,
|
|
346
|
+
done: ICON.check,
|
|
347
|
+
failed: ICON.cross,
|
|
348
|
+
stopped: ICON.stop
|
|
349
|
+
};
|
|
350
|
+
const LIVE = /* @__PURE__ */ new Set(["running", "waiting"]);
|
|
351
|
+
const FOLD_LIMIT = 3;
|
|
352
|
+
const num = (v) => {
|
|
353
|
+
const n = Number(v);
|
|
354
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
355
|
+
};
|
|
356
|
+
function runPlanPhase(steps) {
|
|
357
|
+
let done = 0;
|
|
358
|
+
let finished = 0;
|
|
359
|
+
let running = false;
|
|
360
|
+
let failed = false;
|
|
361
|
+
let first = 0;
|
|
362
|
+
let last = 0;
|
|
363
|
+
for (const s of steps) {
|
|
364
|
+
if (s.status === "done") done++;
|
|
365
|
+
if (s.status === "done" || s.status === "skipped") finished++;
|
|
366
|
+
if (s.status === "running") running = true;
|
|
367
|
+
if (s.status === "failed") failed = true;
|
|
368
|
+
const st = num(s.startedAt);
|
|
369
|
+
if (st && (!first || st < first)) first = st;
|
|
370
|
+
const en = num(s.endedAt);
|
|
371
|
+
if (en > last) last = en;
|
|
372
|
+
}
|
|
373
|
+
const total = steps.length;
|
|
374
|
+
const phase = running ? "running" : failed ? "failed" : total > 0 && finished === total ? "done" : "idle";
|
|
375
|
+
const tookMs = (phase === "done" || phase === "failed") && first && last > first ? last - first : 0;
|
|
376
|
+
return { phase, done, total, since: phase === "running" ? first : 0, tookMs };
|
|
377
|
+
}
|
|
378
|
+
function runFoldedSteps(steps, limit = FOLD_LIMIT) {
|
|
379
|
+
const doneAt = [];
|
|
380
|
+
steps.forEach((s, i) => {
|
|
381
|
+
if (s.status === "done") doneAt.push(i);
|
|
382
|
+
});
|
|
383
|
+
return doneAt.slice(0, Math.max(0, doneAt.length - limit));
|
|
384
|
+
}
|
|
385
|
+
function runApprovalFields(input, truncated = false) {
|
|
386
|
+
if (truncated) return null;
|
|
387
|
+
let obj;
|
|
388
|
+
try {
|
|
389
|
+
obj = JSON.parse(input);
|
|
390
|
+
} catch {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return null;
|
|
394
|
+
const rec = obj;
|
|
395
|
+
const summary = typeof rec.description === "string" ? rec.description : "";
|
|
396
|
+
const fields = Object.keys(rec).filter((k) => !(k === "description" && summary)).map((k) => [k, runFieldText(rec[k])]);
|
|
397
|
+
return { summary, fields };
|
|
398
|
+
}
|
|
399
|
+
function runFieldText(v) {
|
|
400
|
+
if (typeof v === "string") return v;
|
|
401
|
+
const s = JSON.stringify(v, null, 2);
|
|
402
|
+
return s === void 0 ? String(v) : s;
|
|
403
|
+
}
|
|
404
|
+
function runChoiceOrder(choices) {
|
|
405
|
+
const rec = choices.find((c) => c.recommended);
|
|
406
|
+
return rec ? [rec, ...choices.filter((c) => c !== rec)] : choices.slice();
|
|
407
|
+
}
|
|
408
|
+
function runQuestionKey(q) {
|
|
409
|
+
return `q|${JSON.stringify([
|
|
410
|
+
q.id,
|
|
411
|
+
q.title,
|
|
412
|
+
q.body ?? "",
|
|
413
|
+
(q.choices ?? []).map((c) => [c.id, c.label, c.description ?? "", !!c.recommended])
|
|
414
|
+
])}`;
|
|
415
|
+
}
|
|
416
|
+
function runNeedsCount(d) {
|
|
417
|
+
return (d.approvals ?? []).filter((a) => !a.answered).length + (d.questions ?? []).filter((q) => !q.answered).length;
|
|
418
|
+
}
|
|
419
|
+
function runTimeOfDay(ts, lang = "en") {
|
|
420
|
+
if (!num(ts)) return "";
|
|
421
|
+
return lang === "ko" ? new Date(ts).toLocaleTimeString("ko-KR", { hour: "2-digit", minute: "2-digit" }) : new Date(ts).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });
|
|
422
|
+
}
|
|
423
|
+
const answerable = (v) => v === void 0 || v === true;
|
|
424
|
+
const lockReason = (v) => typeof v === "string" ? v : "";
|
|
425
|
+
const approvalKey = (id) => `ap|${id}`;
|
|
426
|
+
function runDomKey(identity) {
|
|
427
|
+
return identity.replace(/[^A-Za-z0-9]/g, (ch) => `_${ch.charCodeAt(0).toString(36)}-`);
|
|
428
|
+
}
|
|
429
|
+
const domKey = (...parts) => runDomKey(JSON.stringify(parts));
|
|
430
|
+
const KBD_APPROVE = typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/.test(String(navigator.platform || navigator.userAgent)) ? "⌘↵" : "Ctrl ↵";
|
|
431
|
+
class UiAgentRun extends UiElement {
|
|
432
|
+
static styles = [styles];
|
|
433
|
+
static get observedAttributes() {
|
|
434
|
+
return ["lang"];
|
|
435
|
+
}
|
|
436
|
+
#live;
|
|
437
|
+
#timer = 0;
|
|
438
|
+
#expanded = false;
|
|
439
|
+
/** Answers this element emitted and the host has not settled yet (key → decision / choice id). */
|
|
440
|
+
#sent = /* @__PURE__ */ new Map();
|
|
441
|
+
#stopSent = false;
|
|
442
|
+
// What the last data said, to announce and animate only what changed.
|
|
443
|
+
#seen;
|
|
444
|
+
#prevStatus = "";
|
|
445
|
+
#prevSteps = null;
|
|
446
|
+
#prevAps = null;
|
|
447
|
+
#prevQs = null;
|
|
448
|
+
#prevCount = 0;
|
|
449
|
+
#pop = /* @__PURE__ */ new Set();
|
|
450
|
+
#enter = /* @__PURE__ */ new Set();
|
|
451
|
+
#badgePop = false;
|
|
452
|
+
constructor() {
|
|
453
|
+
super();
|
|
454
|
+
this.#live = document.createElement("div");
|
|
455
|
+
this.#live.className = "sr-only";
|
|
456
|
+
this.#live.setAttribute("role", "status");
|
|
457
|
+
this.#live.setAttribute("aria-live", "polite");
|
|
458
|
+
this.root.append(this.#live);
|
|
459
|
+
this.root.addEventListener("click", (e) => this.#onClick(e));
|
|
460
|
+
this.root.addEventListener("keydown", (e) => this.#onKeydown(e));
|
|
461
|
+
}
|
|
462
|
+
attributeChangedCallback() {
|
|
463
|
+
this.requestRender();
|
|
464
|
+
}
|
|
465
|
+
disconnectedCallback() {
|
|
466
|
+
this.#stopTimer();
|
|
467
|
+
}
|
|
468
|
+
get #lang() {
|
|
469
|
+
return this.getAttribute("lang") === "ko" ? "ko" : "en";
|
|
470
|
+
}
|
|
471
|
+
get #t() {
|
|
472
|
+
return STRINGS[this.#lang];
|
|
473
|
+
}
|
|
474
|
+
// ── Intent ────────────────────────────────────────────────────────────────
|
|
475
|
+
#onClick(e) {
|
|
476
|
+
const btn = e.target?.closest?.("button");
|
|
477
|
+
if (!btn || btn.disabled) return;
|
|
478
|
+
const card = btn.closest(".ap-card");
|
|
479
|
+
if (btn.dataset.decision && card?.dataset.id !== void 0) {
|
|
480
|
+
this.#approve(card.dataset.id, btn.dataset.decision);
|
|
481
|
+
} else if (btn.dataset.choice !== void 0 && card?.dataset.id !== void 0) {
|
|
482
|
+
this.#answer(card.dataset.id, btn.dataset.choice);
|
|
483
|
+
} else if (btn.classList.contains("ar-stop")) {
|
|
484
|
+
this.#stop();
|
|
485
|
+
} else if (btn.classList.contains("pl-fold-btn")) {
|
|
486
|
+
this.#expanded = !this.#expanded;
|
|
487
|
+
this.requestRender();
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/** ⌘/Ctrl+Enter anywhere in a card answers with its primary; Esc denies only from one of the card's buttons. */
|
|
491
|
+
#onKeydown(e) {
|
|
492
|
+
if (e.defaultPrevented) return;
|
|
493
|
+
const target = e.target;
|
|
494
|
+
const card = target?.closest?.(".ap-card");
|
|
495
|
+
if (!card || !target) return;
|
|
496
|
+
let btn = null;
|
|
497
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
498
|
+
btn = card.querySelector(card.dataset.kind === "question" ? ".ap-rec" : ".ap-approve");
|
|
499
|
+
} else if (e.key === "Escape" && card.dataset.kind === "perm") {
|
|
500
|
+
const from = target.closest("button");
|
|
501
|
+
if (from && card.contains(from)) btn = card.querySelector(".ap-deny");
|
|
502
|
+
}
|
|
503
|
+
if (!btn || btn.disabled) return;
|
|
504
|
+
e.preventDefault();
|
|
505
|
+
e.stopPropagation();
|
|
506
|
+
btn.click();
|
|
507
|
+
}
|
|
508
|
+
#approve(id, decision) {
|
|
509
|
+
const a = this.data?.approvals?.find((x) => x.id === id);
|
|
510
|
+
const key = approvalKey(id);
|
|
511
|
+
if (!a || a.answered || !answerable(a.canAnswer) || this.#sent.has(key)) return;
|
|
512
|
+
if (decision === "approve_disable_rule" && !a.canDisableRule) return;
|
|
513
|
+
if (this.emit("ui-approve", { id, decision })) {
|
|
514
|
+
this.#sent.set(key, decision);
|
|
515
|
+
this.requestRender();
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
#answer(questionId, choiceId) {
|
|
519
|
+
const q = this.data?.questions?.find((x) => x.id === questionId);
|
|
520
|
+
const key = `q|${questionId}`;
|
|
521
|
+
if (!q || q.answered || !answerable(q.canAnswer) || this.#sent.has(key)) return;
|
|
522
|
+
if (!q.choices?.some((c) => c.id === choiceId)) return;
|
|
523
|
+
if (this.emit("ui-answer", { questionId, choiceId })) {
|
|
524
|
+
this.#sent.set(key, choiceId);
|
|
525
|
+
this.requestRender();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
#stop() {
|
|
529
|
+
const d = this.data;
|
|
530
|
+
if (!d || d.canStop !== true || d.stopping || this.#stopSent || !LIVE.has(d.status)) return;
|
|
531
|
+
if (this.emit("ui-stop", {})) {
|
|
532
|
+
this.#stopSent = true;
|
|
533
|
+
this.requestRender();
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Keyboard landing (the dashboard's panel opening): focus the first unanswered card — the card, not a button, so
|
|
538
|
+
* Esc still reaches the page and ⌘/Ctrl+Enter is a deliberate second step. Returns false when nothing waits.
|
|
539
|
+
*/
|
|
540
|
+
focusNeeds() {
|
|
541
|
+
if (this.data) this.renderNow();
|
|
542
|
+
const card = this.$(".ap-card:not(.is-resolved)");
|
|
543
|
+
card?.focus();
|
|
544
|
+
return !!card;
|
|
545
|
+
}
|
|
546
|
+
// ── Render cycle ──────────────────────────────────────────────────────────
|
|
547
|
+
update() {
|
|
548
|
+
const d = this.data;
|
|
549
|
+
if (d && d !== this.#seen) this.#observe(d);
|
|
550
|
+
const active = this.root.activeElement;
|
|
551
|
+
const cardKey = active?.closest?.(".ap-card")?.dataset.key ?? "";
|
|
552
|
+
const fromNeeds = !!active?.closest?.(".ar-needs");
|
|
553
|
+
super.update();
|
|
554
|
+
this.#pop.clear();
|
|
555
|
+
this.#enter.clear();
|
|
556
|
+
this.#badgePop = false;
|
|
557
|
+
if (active && !this.root.activeElement) this.#safeFocus(cardKey, fromNeeds);
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Focus whose node left the page lands where it answers nothing: its own card (now a resolved row), else the
|
|
561
|
+
* "Needs input" head, else the title — never a decision button (a following ⌘↵ must not answer another request).
|
|
562
|
+
*/
|
|
563
|
+
#safeFocus(cardKey, fromNeeds) {
|
|
564
|
+
const card = cardKey ? this.$$(".ap-card").find((c) => c.dataset.key === cardKey) : void 0;
|
|
565
|
+
const to = card ?? (fromNeeds ? this.$(".ar-needs-head") : null) ?? this.$(".ar-title");
|
|
566
|
+
to?.focus({ preventScroll: true });
|
|
567
|
+
}
|
|
568
|
+
/** Compare new data with the last: settle sent answers, collect what to announce and what to animate. */
|
|
569
|
+
#observe(d) {
|
|
570
|
+
const t = this.#t;
|
|
571
|
+
const first = this.#seen === void 0;
|
|
572
|
+
const said = [];
|
|
573
|
+
if (!first && this.#prevStatus !== d.status) said.push(t.statusSay(t.status[d.status] ?? d.status));
|
|
574
|
+
this.#prevStatus = d.status;
|
|
575
|
+
this.#stopSent = false;
|
|
576
|
+
const steps = d.plan?.steps ?? [];
|
|
577
|
+
const prevSteps = this.#prevSteps;
|
|
578
|
+
const nowSteps = /* @__PURE__ */ new Map();
|
|
579
|
+
for (const s of steps) {
|
|
580
|
+
nowSteps.set(s.id, s.status);
|
|
581
|
+
const was = prevSteps?.get(s.id);
|
|
582
|
+
if (!was || was === s.status) continue;
|
|
583
|
+
this.#pop.add(s.id);
|
|
584
|
+
if (s.status === "done") said.push(t.stepDone(s.label));
|
|
585
|
+
else if (s.status === "failed") said.push(t.stepFailed(s.label));
|
|
586
|
+
}
|
|
587
|
+
this.#prevSteps = nowSteps;
|
|
588
|
+
const aps = d.approvals ?? [];
|
|
589
|
+
const nowAps = /* @__PURE__ */ new Map();
|
|
590
|
+
for (const a of aps) {
|
|
591
|
+
const was = this.#prevAps?.get(a.id);
|
|
592
|
+
const seen = { answered: a.answered ?? "", error: a.error ?? "" };
|
|
593
|
+
nowAps.set(a.id, seen);
|
|
594
|
+
if (this.#prevAps && !was && !a.answered) said.push(t.arrived(a.tool));
|
|
595
|
+
if (was && a.answered && was.answered !== a.answered) {
|
|
596
|
+
said.push(`${t.sent[a.answered] ?? a.answered} — ${a.tool}`);
|
|
597
|
+
this.#enter.add(approvalKey(a.id));
|
|
598
|
+
}
|
|
599
|
+
if (a.error && (!was || was.error !== a.error) && !first) said.push(a.error);
|
|
600
|
+
if (a.answered || a.error) this.#sent.delete(approvalKey(a.id));
|
|
601
|
+
}
|
|
602
|
+
this.#prevAps = nowAps;
|
|
603
|
+
const qs = d.questions ?? [];
|
|
604
|
+
const nowQs = /* @__PURE__ */ new Map();
|
|
605
|
+
for (const q of qs) {
|
|
606
|
+
const was = this.#prevQs?.get(q.id);
|
|
607
|
+
nowQs.set(q.id, { answered: q.answered ?? "", error: q.error ?? "" });
|
|
608
|
+
if (this.#prevQs && !was && !q.answered) said.push(t.asked(q.title));
|
|
609
|
+
if (was && q.answered && was.answered !== q.answered) {
|
|
610
|
+
const c = q.choices?.find((x) => x.id === q.answered);
|
|
611
|
+
said.push(t.answerSent(c?.label ?? q.answered));
|
|
612
|
+
this.#enter.add(`q|${q.id}`);
|
|
613
|
+
}
|
|
614
|
+
if (q.error && (!was || was.error !== q.error) && !first) said.push(q.error);
|
|
615
|
+
if (q.answered || q.error) this.#sent.delete(`q|${q.id}`);
|
|
616
|
+
}
|
|
617
|
+
this.#prevQs = nowQs;
|
|
618
|
+
for (const key of Array.from(this.#sent.keys())) {
|
|
619
|
+
const id = key.slice(key.indexOf("|") + 1);
|
|
620
|
+
const gone = key.startsWith("ap|") ? !nowAps.has(id) : !nowQs.has(id);
|
|
621
|
+
if (gone) this.#sent.delete(key);
|
|
622
|
+
}
|
|
623
|
+
const count = runNeedsCount(d);
|
|
624
|
+
this.#badgePop = count > this.#prevCount;
|
|
625
|
+
this.#prevCount = count;
|
|
626
|
+
this.#seen = d;
|
|
627
|
+
if (said.length) this.#live.textContent = said.join(". ");
|
|
628
|
+
}
|
|
629
|
+
updated() {
|
|
630
|
+
this.#tick();
|
|
631
|
+
const live = !!this.$("[data-since], [data-until]");
|
|
632
|
+
if (live && !this.#timer && this.isConnected) this.#timer = setInterval(() => this.#tick(), 1e3);
|
|
633
|
+
else if (!live) this.#stopTimer();
|
|
634
|
+
}
|
|
635
|
+
#stopTimer() {
|
|
636
|
+
if (this.#timer) clearInterval(this.#timer);
|
|
637
|
+
this.#timer = 0;
|
|
638
|
+
}
|
|
639
|
+
/** 1 Hz: every clock is a text node filled from its data-* attributes — the markup never carries `now`. */
|
|
640
|
+
#tick() {
|
|
641
|
+
const now = Date.now();
|
|
642
|
+
const t = this.#t;
|
|
643
|
+
for (const el of this.$$("[data-since]")) {
|
|
644
|
+
const since = Number(el.dataset.since) || now;
|
|
645
|
+
const txt = fmtDuration(now - since + (Number(el.dataset.add) || 0));
|
|
646
|
+
if (el.textContent !== txt) el.textContent = txt;
|
|
647
|
+
}
|
|
648
|
+
for (const el of this.$$("[data-until]")) {
|
|
649
|
+
const left = (Number(el.dataset.until) || 0) - now;
|
|
650
|
+
const txt = left > 0 ? t.expiresIn(fmtClock(left)) : t.expired;
|
|
651
|
+
if (el.textContent !== txt) el.textContent = txt;
|
|
652
|
+
el.classList.toggle("is-low", left <= 6e4);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
render() {
|
|
656
|
+
const d = this.data;
|
|
657
|
+
if (!d) return "";
|
|
658
|
+
const t = this.#t;
|
|
659
|
+
const plan = d.plan ? this.#plan(d.plan.steps ?? [], t) : "";
|
|
660
|
+
const needs = this.#needs(d, t);
|
|
661
|
+
const details = this.#details(d, t);
|
|
662
|
+
const bands = [plan, needs, details].filter(Boolean).length;
|
|
663
|
+
const side = bands > 1 ? needs ? "needs" : "details" : "";
|
|
664
|
+
return `<section class="ar" aria-labelledby="ar-title" data-status="${escAttr(d.status)}">
|
|
665
|
+
${this.#head(d, t)}${bands ? `<div class="ar-body"${side ? ` data-side="${side}"` : ""}>${plan}${needs}${details}</div>` : ""}
|
|
666
|
+
</section>`;
|
|
667
|
+
}
|
|
668
|
+
#head(d, t) {
|
|
669
|
+
const st = STATUS_ICON[d.status] ? d.status : "idle";
|
|
670
|
+
const live = LIVE.has(st);
|
|
671
|
+
const start = num(d.startedAt);
|
|
672
|
+
const end = num(d.endedAt);
|
|
673
|
+
let time = "";
|
|
674
|
+
if (live && start) {
|
|
675
|
+
time = `<span class="ps-time" title="${escAttr(t.liveTitle)}"><span class="ps-clock">${ICON.clock}</span>${esc(
|
|
676
|
+
t.started(runTimeOfDay(start, this.#lang))
|
|
677
|
+
)}<span class="ps-dot" aria-hidden="true">·</span><span class="ps-tick" data-since="${start}"></span></span>`;
|
|
678
|
+
} else if (start && end > start) {
|
|
679
|
+
time = `<span class="ps-time" title="${escAttr(t.ranTitle)}"><span class="ps-clock">${ICON.clock}</span>${esc(
|
|
680
|
+
t.started(runTimeOfDay(start, this.#lang))
|
|
681
|
+
)}<span class="ps-dot" aria-hidden="true">·</span>${esc(fmtDuration(end - start))}</span>`;
|
|
682
|
+
} else if (num(d.activeMs)) {
|
|
683
|
+
time = `<span class="ps-time" title="${escAttr(t.activeTitle)}">${esc(t.active)} ${esc(fmtDuration(num(d.activeMs)))}</span>`;
|
|
684
|
+
}
|
|
685
|
+
let stop = "";
|
|
686
|
+
if (live && d.canStop !== void 0 && d.canStop !== false) {
|
|
687
|
+
const why = lockReason(d.canStop);
|
|
688
|
+
const busy = !!d.stopping || this.#stopSent;
|
|
689
|
+
stop = `<span class="ps-actions"><button type="button" class="ar-stop" data-key="ar-stop" aria-label="${escAttr(
|
|
690
|
+
t.stopLabel
|
|
691
|
+
)}"${why || busy ? " disabled" : ""}${busy ? ' aria-busy="true"' : ""}${why ? ` title="${escAttr(why)}"` : ""}>${busy ? ICON.spin : ""}${esc(t.stop)}</button></span>`;
|
|
692
|
+
}
|
|
693
|
+
return `<header class="ar-head">
|
|
694
|
+
<h2 class="ar-title" id="ar-title" tabindex="-1" data-key="ar-title">${esc(d.title)}</h2>${d.subtitle ? `<p class="ar-sub">${esc(d.subtitle)}</p>` : ""}
|
|
695
|
+
<div class="ps-row"><span class="ps-chip is-${st}"><span class="ps-label">${esc(t.chip)}</span><span class="ps-icon" aria-hidden="true">${STATUS_ICON[st]}</span><span class="ps-word">${esc(t.status[st])}</span></span>${time}${stop}</div>
|
|
696
|
+
</header>`;
|
|
697
|
+
}
|
|
698
|
+
// ── Plan ──────────────────────────────────────────────────────────────────
|
|
699
|
+
#plan(steps, t) {
|
|
700
|
+
const head = `<h3 class="ar-sec-h" id="ar-plan-h">${esc(t.plan)}</h3>`;
|
|
701
|
+
if (!steps.length)
|
|
702
|
+
return `<section class="ar-band ar-plan" aria-labelledby="ar-plan-h">${head}<p class="pl-empty">${esc(t.noSteps)}</p></section>`;
|
|
703
|
+
const fold = runFoldedSteps(steps);
|
|
704
|
+
const hidden = new Set(this.#expanded ? [] : fold);
|
|
705
|
+
let rows = "";
|
|
706
|
+
steps.forEach((s, i) => {
|
|
707
|
+
if (fold.length && i === fold[0]) {
|
|
708
|
+
rows += `<li class="pl-fold"><button type="button" class="pl-fold-btn" data-key="pl-fold" aria-expanded="${this.#expanded}">${esc(this.#expanded ? t.foldHide : t.foldShow(fold.length))}</button></li>`;
|
|
709
|
+
}
|
|
710
|
+
if (!hidden.has(i)) rows += this.#step(s, t);
|
|
711
|
+
});
|
|
712
|
+
return `<section class="ar-band ar-plan" aria-labelledby="ar-plan-h">${head}<ol class="pl-list" aria-label="${escAttr(
|
|
713
|
+
t.planSteps
|
|
714
|
+
)}">${rows}</ol>${this.#foot(steps, t)}</section>`;
|
|
715
|
+
}
|
|
716
|
+
#step(s, t) {
|
|
717
|
+
const st = t.stepSr[s.status] ? s.status : "pending";
|
|
718
|
+
const marker = st === "done" ? ICON.check : st === "running" ? ICON.spin : st === "failed" ? ICON.cross : st === "skipped" ? ICON.dash : "";
|
|
719
|
+
const calls = st !== "pending" && s.toolCalls !== void 0 ? `<span class="pl-chip">${esc(t.calls(Math.round(num(s.toolCalls))))}</span>` : "";
|
|
720
|
+
const start = num(s.startedAt);
|
|
721
|
+
const end = num(s.endedAt);
|
|
722
|
+
let aside = "";
|
|
723
|
+
if (st === "running" && start) aside = `<span data-since="${start}"></span>`;
|
|
724
|
+
else if (st !== "running" && start && end > start) aside = esc(fmtDuration(end - start));
|
|
725
|
+
const times = [start ? runTimeOfDay(start, this.#lang) : "", end ? runTimeOfDay(end, this.#lang) : ""].filter(Boolean).join(" → ");
|
|
726
|
+
return `<li class="pl-step${this.#pop.has(s.id) ? " pl-pop" : ""}" data-step="${st}"${st === "running" ? ' aria-current="step"' : ""}${times ? ` title="${escAttr(times)}"` : ""}><span class="pl-marker" aria-hidden="true">${marker}</span><span class="pl-main"><span class="pl-line">${s.ref ? `<span class="pl-ref">${esc(s.ref)}</span>` : ""}<span class="pl-text">${esc(s.label)}</span>${calls}</span>${s.detail ? `<span class="pl-sub" title="${escAttr(s.detail)}">${esc(s.detail)}</span>` : ""}<span class="sr-only">, ${esc(t.stepSr[st])}</span></span><span class="pl-aside">${aside}</span></li>`;
|
|
727
|
+
}
|
|
728
|
+
#foot(steps, t) {
|
|
729
|
+
const p = runPlanPhase(steps);
|
|
730
|
+
const icon = p.phase === "running" ? ICON.spin : p.phase === "done" ? ICON.check : p.phase === "failed" ? ICON.cross : ICON.dot;
|
|
731
|
+
const time = p.phase === "running" && p.since ? `<span data-since="${p.since}"></span>` : p.tookMs ? esc(fmtDuration(p.tookMs)) : "";
|
|
732
|
+
return `<div class="pl-foot" data-phase="${p.phase}"><span class="pl-foot-st"><span class="pl-foot-icon" aria-hidden="true">${icon}</span>${esc(
|
|
733
|
+
t.phase[p.phase]
|
|
734
|
+
)} · ${p.done}/${p.total}</span>${time ? `<span class="pl-foot-time" aria-hidden="true">${time}</span>` : ""}</div>`;
|
|
735
|
+
}
|
|
736
|
+
// ── Needs input ───────────────────────────────────────────────────────────
|
|
737
|
+
#needs(d, t) {
|
|
738
|
+
const aps = d.approvals ?? [];
|
|
739
|
+
const qs = (d.questions ?? []).filter((q) => q && Array.isArray(q.choices));
|
|
740
|
+
if (!aps.length && !qs.length) return "";
|
|
741
|
+
const n = runNeedsCount({ approvals: aps, questions: qs });
|
|
742
|
+
const cards = aps.map((a, i) => this.#approval(a, i, t)).join("") + qs.map((q, i) => this.#question(q, i, t)).join("");
|
|
743
|
+
return `<section class="ar-band ar-needs" aria-labelledby="ar-needs-h"><h3 class="ar-needs-head" id="ar-needs-h" tabindex="-1" data-key="ar-needs-head">${esc(
|
|
744
|
+
t.needs
|
|
745
|
+
)}${n ? `<span class="pn-count${this.#badgePop ? " is-pop" : ""}">${n}</span>` : ""}</h3><div class="ap-list">${cards}</div></section>`;
|
|
746
|
+
}
|
|
747
|
+
#resolved(r) {
|
|
748
|
+
return `<div class="ap-card is-resolved" role="group" tabindex="-1" data-kind="${r.kind}" data-id="${escAttr(
|
|
749
|
+
r.id
|
|
750
|
+
)}" data-key="${r.key}" aria-labelledby="${r.titleId}"><div class="ap-resolved${this.#enter.has(r.enterKey) ? " is-enter" : ""}" data-decision="${escAttr(r.decision)}"><span class="ap-mark" aria-hidden="true">${r.mark}</span><span class="ap-row-title" id="${r.titleId}">${esc(r.title)}</span><span class="ap-row-status">${esc(r.status)}</span></div></div>`;
|
|
751
|
+
}
|
|
752
|
+
#approval(a, i, t) {
|
|
753
|
+
const key = approvalKey(a.id);
|
|
754
|
+
const dk = domKey("ap", a.id);
|
|
755
|
+
const tool = a.tool || "tool";
|
|
756
|
+
const title = a.title || t.apTitle(tool);
|
|
757
|
+
const ids = { t: `ar-ap${i}-t`, d: `ar-ap${i}-d`, x: `ar-ap${i}-x` };
|
|
758
|
+
if (a.answered) {
|
|
759
|
+
const mark = a.answered === "deny" ? ICON.cross : a.answered === "explain" ? "?" : ICON.check;
|
|
760
|
+
return this.#resolved({
|
|
761
|
+
key: dk,
|
|
762
|
+
enterKey: key,
|
|
763
|
+
kind: "perm",
|
|
764
|
+
id: a.id,
|
|
765
|
+
titleId: ids.t,
|
|
766
|
+
title,
|
|
767
|
+
status: t.answered[a.answered] ?? a.answered,
|
|
768
|
+
mark,
|
|
769
|
+
decision: a.answered
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
const why = lockReason(a.canAnswer);
|
|
773
|
+
const sending = this.#sent.get(key) ?? "";
|
|
774
|
+
const off = !answerable(a.canAnswer) || !!sending;
|
|
775
|
+
const rules = a.rules ?? [];
|
|
776
|
+
const pill = rules.length ? `<span class="ap-pill" data-kind="rule">${esc(t.risk)}<span class="ap-pill-word">${esc(t.riskWord)}</span> ${rules.length}</span>` : `<span class="ap-pill" data-kind="tool">${esc(t.toolPill)}</span>`;
|
|
777
|
+
let body = "";
|
|
778
|
+
if (a.fields?.length) {
|
|
779
|
+
body = `<dl class="ap-details">${a.fields.map(([k, v]) => `<div class="ap-detail"><dt>${esc(k)}</dt><dd>${esc(runFieldText(v))}</dd></div>`).join("")}</dl>`;
|
|
780
|
+
} else if (a.raw !== void 0 && a.raw !== "") {
|
|
781
|
+
body = `<pre class="ap-raw">${esc(a.raw)}</pre>${a.truncated ? `<p class="ap-truncated">${esc(t.truncated)}</p>` : ""}`;
|
|
782
|
+
}
|
|
783
|
+
const rulesHtml = rules.length ? `<div class="ap-rules"><span class="ap-rules-label">${esc(t.rules(rules.length))}</span>${rules.map(
|
|
784
|
+
(r) => `<span class="ap-rule"><code>${esc(r.id)}</code> — ${esc(r.label)}${r.description ? `: ${esc(r.description)}` : ""}</span>`
|
|
785
|
+
).join("")}</div>` : "";
|
|
786
|
+
const btn = (decision, cls, extra = "", kbd = "") => {
|
|
787
|
+
const busy = sending === decision;
|
|
788
|
+
return `<button type="button" class="ap-btn ${cls}${busy ? " is-busy" : ""}" data-decision="${decision}" data-key="${domKey("ap", a.id, decision)}"${off ? " disabled" : ""}${busy ? ' aria-busy="true"' : ""}${why ? ` title="${escAttr(why)}"` : ""}${extra}>${busy ? ICON.spin : ""}${esc(t.btn[decision])}${kbd ? `<span class="ap-kbd" aria-hidden="true">${esc(kbd)}</span>` : ""}</button>`;
|
|
789
|
+
};
|
|
790
|
+
const alts = btn("explain", "ap-ghost") + (a.canDisableRule ? btn("approve_disable_rule", "ap-ghost") : "");
|
|
791
|
+
const main = btn("deny", "ap-quiet ap-deny", ' aria-keyshortcuts="Escape"', "Esc") + btn("approve", "ap-primary ap-approve", ' aria-keyshortcuts="Meta+Enter Control+Enter"', KBD_APPROVE);
|
|
792
|
+
const until = num(a.expiresAt);
|
|
793
|
+
const expires = until ? `<span class="ap-expires" data-until="${until}" aria-hidden="true"></span><span class="sr-only" id="${ids.x}">${esc(
|
|
794
|
+
t.expiresAt(runTimeOfDay(until, this.#lang))
|
|
795
|
+
)}</span>` : "";
|
|
796
|
+
const described = [a.summary ? ids.d : "", until ? ids.x : ""].filter(Boolean).join(" ");
|
|
797
|
+
const label = `${esc(tool)}${num(a.requestedAt) ? ` · ${esc(t.requested(runTimeOfDay(num(a.requestedAt), this.#lang)))}` : ""}`;
|
|
798
|
+
return `<div class="ap-card" role="group" tabindex="-1" data-kind="perm" data-id="${escAttr(a.id)}" data-key="${dk}" aria-labelledby="${ids.t}"${described ? ` aria-describedby="${described}"` : ""}><div class="ap-label">${label}</div><div class="ap-body"><div class="ap-head"><span class="ap-icon" aria-hidden="true">${ICON.lock}</span><div class="ap-titles"><div class="ap-title" id="${ids.t}">${esc(title)}</div>${a.summary ? `<div class="ap-desc" id="${ids.d}" title="${escAttr(a.summary)}">${esc(a.summary)}</div>` : ""}</div>${pill}</div>${body}${rulesHtml}<div class="ap-alts">${alts}</div><div class="ap-foot">${expires}<div class="ap-actions">${main}</div></div>${why ? `<p class="ap-lock">${esc(why)}</p>` : ""}${a.error ? `<p class="ap-note">${esc(a.error)}</p>` : ""}</div></div>`;
|
|
799
|
+
}
|
|
800
|
+
#question(q, i, t) {
|
|
801
|
+
const meaning = runQuestionKey(q);
|
|
802
|
+
const dk = domKey("q", meaning);
|
|
803
|
+
const titleId = `ar-q${i}-t`;
|
|
804
|
+
if (q.answered) {
|
|
805
|
+
const c = q.choices.find((x) => x.id === q.answered);
|
|
806
|
+
return this.#resolved({
|
|
807
|
+
key: dk,
|
|
808
|
+
enterKey: `q|${q.id}`,
|
|
809
|
+
kind: "question",
|
|
810
|
+
id: q.id,
|
|
811
|
+
titleId,
|
|
812
|
+
title: q.title,
|
|
813
|
+
status: `${t.answeredQ} · ${c?.label ?? q.answered}`,
|
|
814
|
+
mark: ICON.check,
|
|
815
|
+
decision: "answer"
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
const why = lockReason(q.canAnswer);
|
|
819
|
+
const sending = this.#sent.get(`q|${q.id}`) ?? "";
|
|
820
|
+
const off = !answerable(q.canAnswer) || !!sending;
|
|
821
|
+
const ordered = runChoiceOrder(q.choices);
|
|
822
|
+
const recId = ordered[0]?.recommended ? ordered[0].id : "";
|
|
823
|
+
const choices = ordered.map((c) => {
|
|
824
|
+
const rec = c.id === recId;
|
|
825
|
+
const busy = sending === c.id;
|
|
826
|
+
return `<button type="button" class="ap-choice${rec ? " ap-rec" : ""}${busy ? " is-busy" : ""}" data-choice="${escAttr(
|
|
827
|
+
c.id
|
|
828
|
+
)}" data-key="${domKey("q", meaning, c.id)}"${off ? " disabled" : ""}${busy ? ' aria-busy="true"' : ""}${why ? ` title="${escAttr(why)}"` : ""}${rec ? ' aria-keyshortcuts="Meta+Enter Control+Enter"' : ""}><span class="ap-choice-label">${busy ? ICON.spin : ""}${esc(
|
|
829
|
+
c.label
|
|
830
|
+
)}${rec ? `<span class="ap-choice-tag">${esc(t.recommended)}</span>` : ""}</span>${c.description ? `<span class="ap-choice-desc">${esc(c.description)}</span>` : ""}${rec ? `<span class="ap-kbd" aria-hidden="true">${esc(KBD_APPROVE)}</span>` : ""}</button>`;
|
|
831
|
+
}).join("");
|
|
832
|
+
return `<div class="ap-card ap-question" role="group" tabindex="-1" data-kind="question" data-id="${escAttr(q.id)}" data-key="${dk}" aria-labelledby="${titleId}"><div class="ap-body"><div class="ap-head"><span class="ap-icon" aria-hidden="true">${ICON.ask}</span><div class="ap-titles"><div class="ap-title" id="${titleId}">${esc(q.title)}</div>${q.body ? `<div class="ap-qbody">${esc(q.body)}</div>` : ""}</div><span class="ap-pill" data-kind="question">${esc(t.questionPill)}</span></div><div class="ap-choices">${choices}</div>${why ? `<p class="ap-lock">${esc(why)}</p>` : ""}${q.error ? `<p class="ap-note">${esc(q.error)}</p>` : ""}</div></div>`;
|
|
833
|
+
}
|
|
834
|
+
// ── Details ───────────────────────────────────────────────────────────────
|
|
835
|
+
#details(d, t) {
|
|
836
|
+
const rows = (d.details ?? []).filter((r) => Array.isArray(r) && r[1] !== void 0 && r[1] !== null && r[1] !== "").map(([label, v]) => {
|
|
837
|
+
const value = typeof v !== "object" ? esc(String(v)) : num(v.since) ? `<span data-since="${num(v.since)}" data-add="${num(v.addMs)}"></span>` : esc(fmtDuration(num(v.addMs)));
|
|
838
|
+
return `<div><dt>${esc(label)}</dt><dd>${value}</dd></div>`;
|
|
839
|
+
}).join("");
|
|
840
|
+
if (!rows) return "";
|
|
841
|
+
return `<section class="ar-band ar-details" aria-labelledby="ar-det-h"><h3 class="ar-sec-h" id="ar-det-h">${esc(
|
|
842
|
+
t.details
|
|
843
|
+
)}</h3><dl class="pi-details">${rows}</dl></section>`;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
define("ui-agent-run", UiAgentRun);
|
|
847
|
+
export {
|
|
848
|
+
UiAgentRun,
|
|
849
|
+
runApprovalFields,
|
|
850
|
+
runChoiceOrder,
|
|
851
|
+
runDomKey,
|
|
852
|
+
runFieldText,
|
|
853
|
+
runFoldedSteps,
|
|
854
|
+
runNeedsCount,
|
|
855
|
+
runPlanPhase,
|
|
856
|
+
runQuestionKey,
|
|
857
|
+
runTimeOfDay
|
|
858
|
+
};
|
|
859
|
+
//# sourceMappingURL=agent-run.js.map
|