cli-jaw 2.2.25 → 2.3.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 +5 -2
- package/dist/bin/cli-jaw.js +6 -1
- package/dist/bin/cli-jaw.js.map +1 -1
- package/dist/bin/commands/doctor.js +39 -18
- package/dist/bin/commands/doctor.js.map +1 -1
- package/dist/bin/commands/init.js +82 -23
- package/dist/bin/commands/init.js.map +1 -1
- package/dist/bin/commands/messaging.js +262 -0
- package/dist/bin/commands/messaging.js.map +1 -0
- package/dist/server.js +30 -6
- package/dist/server.js.map +1 -1
- package/dist/src/cli/commands.js +6 -2
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/handlers/remote-session-commands.js +94 -0
- package/dist/src/cli/handlers/remote-session-commands.js.map +1 -0
- package/dist/src/command-contract/catalog.js +1 -1
- package/dist/src/command-contract/catalog.js.map +1 -1
- package/dist/src/core/chat-sessions.js +4 -0
- package/dist/src/core/chat-sessions.js.map +1 -1
- package/dist/src/core/config.js +63 -22
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/db.js +6 -0
- package/dist/src/core/db.js.map +1 -1
- package/dist/src/core/dispatch-approval-ingress.js +45 -3
- package/dist/src/core/dispatch-approval-ingress.js.map +1 -1
- package/dist/src/core/dispatch-approval.js +46 -0
- package/dist/src/core/dispatch-approval.js.map +1 -1
- package/dist/src/core/logger.js +67 -0
- package/dist/src/core/logger.js.map +1 -1
- package/dist/src/core/session-generation.js +74 -0
- package/dist/src/core/session-generation.js.map +1 -0
- package/dist/src/discord/bot.js +91 -5
- package/dist/src/discord/bot.js.map +1 -1
- package/dist/src/discord/commands.js +34 -1
- package/dist/src/discord/commands.js.map +1 -1
- package/dist/src/discord/forwarder.js +4 -1
- package/dist/src/discord/forwarder.js.map +1 -1
- package/dist/src/discord/send-only-client.js +14 -7
- package/dist/src/discord/send-only-client.js.map +1 -1
- package/dist/src/messaging/access-policy.js +26 -0
- package/dist/src/messaging/access-policy.js.map +1 -0
- package/dist/src/messaging/approval-presentation.js +89 -0
- package/dist/src/messaging/approval-presentation.js.map +1 -0
- package/dist/src/messaging/channel-adapter.js +47 -0
- package/dist/src/messaging/channel-adapter.js.map +1 -0
- package/dist/src/messaging/channel-capabilities.js +47 -15
- package/dist/src/messaging/channel-capabilities.js.map +1 -1
- package/dist/src/messaging/channel-health.js +23 -3
- package/dist/src/messaging/channel-health.js.map +1 -1
- package/dist/src/messaging/delivery-outcome.js +12 -0
- package/dist/src/messaging/delivery-outcome.js.map +1 -1
- package/dist/src/messaging/durable-ingress.js +458 -0
- package/dist/src/messaging/durable-ingress.js.map +1 -0
- package/dist/src/messaging/effect-once.js +204 -0
- package/dist/src/messaging/effect-once.js.map +1 -0
- package/dist/src/messaging/inbound-envelope.js +168 -0
- package/dist/src/messaging/inbound-envelope.js.map +1 -0
- package/dist/src/messaging/ingress-audit.js +41 -0
- package/dist/src/messaging/ingress-audit.js.map +1 -0
- package/dist/src/messaging/ingress-generation.js +22 -0
- package/dist/src/messaging/ingress-generation.js.map +1 -0
- package/dist/src/messaging/metrics.js +69 -0
- package/dist/src/messaging/metrics.js.map +1 -0
- package/dist/src/messaging/outbound-outbox.js +184 -0
- package/dist/src/messaging/outbound-outbox.js.map +1 -0
- package/dist/src/messaging/remote-command-context.js +30 -0
- package/dist/src/messaging/remote-command-context.js.map +1 -0
- package/dist/src/messaging/retry.js +11 -2
- package/dist/src/messaging/retry.js.map +1 -1
- package/dist/src/messaging/runtime.js +129 -30
- package/dist/src/messaging/runtime.js.map +1 -1
- package/dist/src/messaging/send.js +31 -6
- package/dist/src/messaging/send.js.map +1 -1
- package/dist/src/messaging/trace-context.js +56 -0
- package/dist/src/messaging/trace-context.js.map +1 -0
- package/dist/src/messaging/types.js +39 -1
- package/dist/src/messaging/types.js.map +1 -1
- package/dist/src/routes/orchestrate.js +10 -3
- package/dist/src/routes/orchestrate.js.map +1 -1
- package/dist/src/routes/settings.js +26 -1
- package/dist/src/routes/settings.js.map +1 -1
- package/dist/src/slack/bot.js +182 -70
- package/dist/src/slack/bot.js.map +1 -1
- package/dist/src/slack/commands.js +16 -1
- package/dist/src/slack/commands.js.map +1 -1
- package/dist/src/slack/format.js +4 -1
- package/dist/src/slack/format.js.map +1 -1
- package/dist/src/slack/manifest.js +5 -0
- package/dist/src/slack/manifest.js.map +1 -1
- package/dist/src/slack/send-handler.js +26 -4
- package/dist/src/slack/send-handler.js.map +1 -1
- package/dist/src/slack/send-only-client.js +26 -2
- package/dist/src/slack/send-only-client.js.map +1 -1
- package/dist/src/slack/socket.js +36 -13
- package/dist/src/slack/socket.js.map +1 -1
- package/dist/src/telegram/bot.js +122 -14
- package/dist/src/telegram/bot.js.map +1 -1
- package/package.json +4 -1
- package/public/dist/assets/ChannelEnablementControl-Da_t_jq6.js +1 -0
- package/public/dist/assets/ChannelsDiscord-kNjCCJcA.js +1 -0
- package/public/dist/assets/ChannelsSlack-BOuoCfeN.js +1 -0
- package/public/dist/assets/ChannelsTelegram-DBIFDz4R.js +1 -0
- package/public/dist/assets/{app-DUbf-v7B.js → app-TWwXw3iD.js} +4 -4
- package/public/dist/assets/bgtask-badge-BhzSqQHG.js +1 -0
- package/public/dist/assets/{bgtask-badge-jIH69Dp3.js → bgtask-badge-DaAUnoKD.js} +1 -1
- package/public/dist/assets/{employees-BNy8a6XX.js → employees-BFKkaVAe.js} +1 -1
- package/public/dist/assets/iframe-renderer-BxEzdKIA.js +1 -0
- package/public/dist/assets/{iframe-renderer-C6Zwg9w4.js → iframe-renderer-CvnKi77F.js} +2 -2
- package/public/dist/assets/{manager-DzCR6Pam.js → manager-BTpvAFwX.js} +1 -1
- package/public/dist/assets/memory-DHOtCiw5.js +1 -0
- package/public/dist/assets/{memory-C4O44HMr.js → memory-db_s-38T.js} +1 -1
- package/public/dist/assets/{message-history-DCxD21J1.js → message-history-BX9shNKA.js} +1 -1
- package/public/dist/assets/message-history-DaSxkyy0.js +1 -0
- package/public/dist/assets/{render-A--hkukv.js → render-DZnFK8Kv.js} +1 -1
- package/public/dist/assets/{settings-3ZdoKHIf.js → settings-BuFMKcQ-.js} +1 -1
- package/public/dist/assets/settings-DotRDITm.js +1 -0
- package/public/dist/assets/{settings-core-CQe1uP6x.js → settings-core-3fj0DVoa.js} +17 -17
- package/public/dist/assets/settings-core-DohSnMad.js +1 -0
- package/public/dist/assets/{sidebar-DxFp_449.js → sidebar-D5TP7TCO.js} +3 -3
- package/public/dist/assets/{skills-BU4q8Q8n.js → skills-CQzHFYJS.js} +1 -1
- package/public/dist/assets/skills-DZHEvl1x.js +1 -0
- package/public/dist/assets/{slash-commands-BiXOgZ5B.js → slash-commands-BWEWWf-c.js} +1 -1
- package/public/dist/assets/slash-commands-CuJdloNC.js +1 -0
- package/public/dist/assets/{trace-drawer-2BTQCor6.js → trace-drawer-CBR-PZB4.js} +1 -1
- package/public/dist/assets/ui-CWl27eMl.js +1 -0
- package/public/dist/assets/{ui-Csy42uNW.js → ui-Ca1Q-VFQ.js} +1 -1
- package/public/dist/index.html +1 -1
- package/public/dist/manager/index.html +1 -1
- package/public/js/features/settings-channel.ts +35 -5
- package/public/js/features/settings-types.ts +7 -0
- package/public/js/features/transport-status-row.ts +23 -4
- package/public/manager/src/settings/pages/ChannelsDiscord.tsx +39 -11
- package/public/manager/src/settings/pages/ChannelsSlack.tsx +24 -7
- package/public/manager/src/settings/pages/ChannelsTelegram.tsx +38 -10
- package/public/manager/src/settings/pages/components/ChannelEnablementControl.tsx +98 -0
- package/public/manager/src/settings/pages/components/TransportStatusChips.tsx +23 -2
- package/scripts/docs/extract-commands.mts +3 -0
- package/scripts/generate-channel-capability-table.mts +161 -0
- package/scripts/promote-to-main.sh +175 -0
- package/scripts/release-gates.mjs +57 -3
- package/scripts/release-preview.sh +19 -0
- package/scripts/require-release-evidence.mjs +35 -1
- package/scripts/validate-messaging-certification.mts +83 -0
- package/scripts/write-messaging-certification.mts +65 -0
- package/public/dist/assets/ChannelsDiscord-D4fD-9hP.js +0 -1
- package/public/dist/assets/ChannelsSlack-_KBwRonM.js +0 -1
- package/public/dist/assets/ChannelsTelegram-tihzSWVE.js +0 -1
- package/public/dist/assets/TransportStatusChips-B0NR0ljO.js +0 -1
- package/public/dist/assets/bgtask-badge-B8KhuBa7.js +0 -1
- package/public/dist/assets/iframe-renderer-DBmV8gCQ.js +0 -1
- package/public/dist/assets/memory-Dt_Ji6aP.js +0 -1
- package/public/dist/assets/message-history-DyISauiv.js +0 -1
- package/public/dist/assets/settings-C6Pku2GC.js +0 -1
- package/public/dist/assets/settings-core-DqwLJkgP.js +0 -1
- package/public/dist/assets/skills-x2gH-1Ry.js +0 -1
- package/public/dist/assets/slash-commands-s8eR7Kd1.js +0 -1
- package/public/dist/assets/ui-CB0L33o0.js +0 -1
- package/scripts/release.sh +0 -224
- /package/public/dist/assets/{HealthBadge-CUaJ1PQH.js → HealthBadge-CiNMS-1k.js} +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/vendor-render-Bjnw0wQ6.css"])))=>i.map(i=>d[i]);
|
|
2
|
-
import{t as e}from"./state-DxpgDKMO.js";import{t}from"./preload-helper-DFYRRz9w.js";import{N as n,T as r,j as i,k as a,t as o}from"./render-A--hkukv.js";import{A as s,C as c,D as l,N as u,O as d,S as f,_ as ee,b as te,k as p,m,t as ne,v as h,w as g,x as re}from"./iframe-renderer-C6Zwg9w4.js";import{C as _,D as v,E as y,S as b,_ as x,b as S,f as ie,g as C,h as w,l as T,m as E,p as D,u as ae,v as oe,w as se,x as O,y as k}from"./message-history-DCxD21J1.js";import{s as ce}from"./idb-cache-lOntwU3S.js";var A=2e3,j=80,le=400;function ue(e){if(e<=A)return j;let t=e/A*j;return Math.min(le,Math.round(t))}function M(e){return{chunks:[],fullText:``,textDirty:!1,element:e,pendingRAF:null,isFinalized:!1,lastRenderTime:0}}function N(e,t=``){let n=M(e);return t?(n.chunks=[t],n.fullText=t,n.textDirty=!1,n.element.innerHTML=o(t,!0)+`<span class="stream-cursor" aria-hidden="true"></span>`):n.element.innerHTML=`<span class="stream-cursor" aria-hidden="true"></span>`,n}function P(e){return e.textDirty&&=(e.fullText=e.chunks.join(``),!1),e.fullText}function F(e,t){e.chunks.push(t),e.textDirty=!0,!e.pendingRAF&&!e.isFinalized&&(e.pendingRAF=requestAnimationFrame(()=>{if(e.pendingRAF=null,e.isFinalized)return;let t=performance.now(),n=P(e);n.length<A||t-e.lastRenderTime>ue(n.length)?(e.element.innerHTML=o(n,!0)+`<span class="stream-cursor" aria-hidden="true"></span>`,e.lastRenderTime=t):e.pendingRAF=requestAnimationFrame(()=>{e.pendingRAF=null,!e.isFinalized&&(e.element.innerHTML=o(P(e),!0)+`<span class="stream-cursor" aria-hidden="true"></span>`,e.lastRenderTime=performance.now())})}))}function I(e,t=!1){e.isFinalized=!0,e.pendingRAF&&=(cancelAnimationFrame(e.pendingRAF),null);let n=P(e);return t||(e.element.innerHTML=o(n)),n}function L(e){return typeof e.traceSeq==`number`&&Number.isFinite(e.traceSeq)&&e.traceSeq>0}function R(e,t){return!!t.traceRunId&&!!e.traceRunId&&t.traceRunId===e.traceRunId&&L(t)&&L(e)&&t.traceSeq===e.traceSeq&&t.type===e.type&&t.label===e.label&&!!t.isEmployee==!!e.isEmployee}function z(e,t){return t.stepRef&&e.stepRef===t.stepRef?!0:R(e,t)}function B(e,t,n={}){return[...e].reverse().filter(e=>n.includeDone||e.status===`running`).find(e=>z(e,t))??null}function V(e,t){let n=e.filter(e=>e.status===`running`&&!e.stepRef&&e.label===t.label&&e.type===t.type&&!!e.isEmployee==!!t.isEmployee);return n.length===1?n[0]:null}function H(e,t){let n=[...e].reverse().filter(e=>e.status===`running`);return B(n,t)||V(n,t)}function U(){e.currentProcessBlock&&l(e.currentProcessBlock),e.currentAgentDiv instanceof HTMLElement&&l(e.currentAgentDiv),v(),s(),e.currentAgentDiv=null,e.currentProcessBlock=null,q=null}var W=0;function de(){W=Date.now()}function fe(){W=0}function pe(){return Date.now()-W<8e3}function G(e){for(let t of document.querySelectorAll(`.msg-user`))if(e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING)return!0;return!1}function K(){let t=e.currentAgentDiv&&e.currentAgentDiv.isConnected?e.currentAgentDiv:null;return t?G(t)?(e.currentAgentDiv=null,e.currentProcessBlock=null,q=null,null):t:null}function me(){let e=Array.from(document.querySelectorAll(`.msg-agent`)).at(-1)??null;return!e||G(e)?null:e}function he(t,n){k();let r=K();if(r||(r=x(`agent`,``),e.currentAgentDiv=r,e.currentProcessBlock=null),b(r),!e.currentProcessBlock){let t=r.querySelector(`.agent-body`);t&&(e.currentProcessBlock=S(r)),!e.currentProcessBlock&&t&&(_(r),e.currentProcessBlock=f(t))}if(n&&e.currentProcessBlock&&!e.currentProcessBlock.startedAt&&(e.currentProcessBlock.startedAt=n),e.currentProcessBlock){let n=t.rawIcon||t.icon,r=t.status||a(t.icon)||`running`;if(r===`done`||r===`error`){let a=H(e.currentProcessBlock.steps,t);if(a){t.icon=i(t.icon);let o=t.type===`thinking`?p(a.id,t.detail):g(a.id,t.detail);d(e.currentProcessBlock,a.id,{...a,...t,id:a.id,rawIcon:n,detail:o,detailPreview:o,label:t.label||a.label,status:r,startTime:a.startTime}),m();return}if(t.stepRef&&(r===`done`||r===`error`)){let a=[...e.currentProcessBlock.steps].reverse().find(e=>e.stepRef===t.stepRef&&(e.status===`done`||e.status===`error`));if(a){t.icon=i(t.icon);let o=t.type===`thinking`?p(a.id,t.detail):g(a.id,t.detail);d(e.currentProcessBlock,a.id,{...a,...t,id:a.id,rawIcon:n,status:r,detail:o,detailPreview:o,startTime:a.startTime}),m();return}}}if(t.stepRef&&r===`running`){let r=[...e.currentProcessBlock.steps].reverse().find(e=>e.stepRef===t.stepRef&&e.status===`running`);if(r){t.rawIcon=n,t.icon=i(t.icon);let a=p(r.id,t.detail);d(e.currentProcessBlock,r.id,{...r,...t,id:r.id,rawIcon:n,status:`running`,detail:a,detailPreview:a,label:t.label||r.label,startTime:r.startTime}),m();return}}let o=B(e.currentProcessBlock.steps,t,{includeDone:!0});if(o){t.rawIcon=n,t.icon=i(t.icon);let a=t.detail||``,s=c(o.id)||o.detail||o.detailPreview||``,l=a.length>=s.length?a:s,u=p(o.id,l),f=(o.status===`done`||o.status===`error`)&&r===`running`?o.status:r;d(e.currentProcessBlock,o.id,{...o,...t,id:o.id,rawIcon:n,status:f,detail:u,detailPreview:u,label:t.label||o.label,startTime:o.startTime}),m();return}if(t.detail){let n=[...e.currentProcessBlock.steps].reverse().find(e=>e.status===`running`&&e.label===t.label&&e.type===t.type&&!!e.isEmployee==!!t.isEmployee&&!e.detail);if(n){d(e.currentProcessBlock,n.id,t),m();return}}t.rawIcon=n,t.icon=i(t.icon),h(e.currentProcessBlock,t)}m()}var q=null,J=`data-active-run-hydrated`;function Y(e){document.querySelectorAll(`[${J}="true"]`).forEach(t=>{e&&t===e||t.remove()})}function ge(e){let t=K()??me();Y(t);let n=t||x(`agent`,``,e||null);return n.setAttribute(J,`true`),n}function _e(e,t){let n=c(e.id)||e.detail||e.detailPreview||``,r=t.detail||t.detailPreview||``;return r.length>=n.length?r:n}function X(e){return e.stepRef?!0:!!e.traceRunId&&typeof e.traceSeq==`number`&&Number.isFinite(e.traceSeq)&&e.traceSeq>0}function ve(e,t){let n=e.steps.filter(e=>!X(e)),r=0,i=!0;for(let a of t){let t=e.steps.find(e=>z(e,a))??null;if(!t&&!X(a)&&i){let e=n[r]??null;e?e.type===a.type&&!!e.isEmployee==!!a.isEmployee?(t=e,r++):i=!1:r++}if(!t){let n=e.steps.filter(e=>e.label===a.label&&e.type===a.type&&!!e.isEmployee==!!a.isEmployee);t=n.length===1?n[0]:null}if(!t){if(h(e,a),!X(a)&&i){let t=e.steps[e.steps.length-1];t&&n.push(t)}continue}let o=p(t.id,_e(t,a));d(e,t.id,{...t,...a,id:t.id,detail:o,detailPreview:o,label:a.label||t.label})}}function ye(e=[]){document.querySelectorAll(`[data-queued-overlay="true"]`).forEach(e=>e.remove())}var Z=``;function be(e){let t=Array.isArray(e.toolLog)?e.toolLog:[],n=t.map(e=>`${e.stepRef||``}~${e.traceRunId||``}~${e.traceSeq??``}~${e.status||``}~${e.label||``}~${(e.detail||``).length}`).join(`|`);return`${e.traceRunId||``}:${e.cli||``}:${(e.text||``).length}:${t.length}:${n}`}function xe(t){if(!t?.running){Z=``,Y();return}let n=be(t);if(n===Z&&K())return;v(),k(),e.currentAgentDiv=ge(t.cli||null);let r=e.currentAgentDiv.querySelector(`.agent-body`),i=E(t.toolLog||[]);b(e.currentAgentDiv),e.currentProcessBlock=S(e.currentAgentDiv),r&&i.length?(e.currentProcessBlock||=(_(e.currentAgentDiv),f(r)),t.startedAt&&(e.currentProcessBlock.startedAt=t.startedAt),ve(e.currentProcessBlock,C(i,t.startedAt))):e.currentProcessBlock=S(e.currentAgentDiv);let a=e.currentAgentDiv.querySelector(`.msg-content`);a&&(q=N(a,t.text||``)),Z=n}function Q(t){if(!t)return;k(),K()||(e.currentAgentDiv=x(`agent`,``),q=null);let n=e.currentAgentDiv?.querySelector(`.msg-content`);n&&(q||=M(n),F(q,t)),m()}var $=0;function Se(e){e.querySelectorAll(`.mermaid-pending`).forEach(e=>{delete e.dataset.mermaidQueued,delete e.dataset.mermaidQueuedAt})}function Ce(t,i){let a=Date.now();if(!e.currentAgentDiv&&a-$<500)return;Z=``,v(),k(),e.currentAgentDiv&&b(e.currentAgentDiv);let s=se(e.currentProcessBlock??e.currentAgentDiv,!0),c=E(D(Array.isArray(i)?i:[],s)),d=w(c),f=!!e.currentProcessBlock||!!(e.currentAgentDiv&&O(e.currentAgentDiv));e.currentProcessBlock&&=(re(e.currentProcessBlock),null);let p=c.length>0;if(t||p){(!e.currentAgentDiv||!e.currentAgentDiv.isConnected)&&(e.currentAgentDiv=x(`agent`,``)),e.currentAgentDiv.removeAttribute(J);let i=e.currentAgentDiv?.querySelector(`.msg-content`),a=q?I(q,!0):``,s=t||a;if(q=null,i&&(i.innerHTML=o(s)),p&&e.currentAgentDiv&&!f&&!O(e.currentAgentDiv)){let t=e.currentAgentDiv.querySelector(`.msg-content`);t&&t.insertAdjacentHTML(`beforebegin`,te(C(c),!0))}e.currentAgentDiv&&b(e.currentAgentDiv),i&&i.setAttribute(`data-raw`,n(s)),i&&ne(i);let m=ee(),h=!!(m.active&&e.currentAgentDiv&&e.currentAgentDiv.isConnected);if(i&&!h&&r(i,{immediate:!0}),h){let t=e.currentAgentDiv;Se(t),t.querySelectorAll(`.diagram-widget`).forEach(e=>{let t=e,n=t.dataset.widgetHtml,r=t.dataset.widgetId;if(!n&&!r)return;let i=document.createElement(`div`);i.className=`diagram-widget-pending`,r?i.dataset.widgetId=r:n&&(i.dataset.diagramHtml=n),e.replaceWith(i)}),d?(m.appendItem(ie({role:`assistant`,content:s,cli:null,tool_log:d},m.count)),l(t),m.scrollToBottom()):(t.dataset.turnIndex=String(m.count),t.dataset.messageId||(t.dataset.messageId=u()),m.appendLiveItem(t)),t.remove()}s&&ce({role:`assistant`,content:s,tool_log:d,timestamp:Date.now()}).catch(()=>{})}q=null,e.currentAgentDiv=null,$=Date.now(),ae(`idle`),T()}function we(e,n){document.querySelectorAll(`.tab-btn`).forEach(e=>{e.classList.remove(`active`),e.setAttribute(`aria-selected`,`false`)}),document.querySelectorAll(`.tab-content`).forEach(e=>e.classList.remove(`active`)),document.getElementById({agents:`tabAgents`,settings:`tabSettings`,skills:`tabSkills`}[e])?.classList.add(`active`),n&&(n.classList.add(`active`),n.setAttribute(`aria-selected`,`true`)),e===`settings`&&t(()=>import(`./settings-C6Pku2GC.js`).then(e=>e.loadSettings()),__vite__mapDeps([0])),e===`agents`&&t(()=>import(`./employees-BNy8a6XX.js`).then(e=>e.loadEmployees()),__vite__mapDeps([0])),e===`skills`&&t(()=>import(`./skills-x2gH-1Ry.js`).then(e=>e.loadSkills()),__vite__mapDeps([0]))}function Te(){let e=document.getElementById(`tabSettings`)?.classList.contains(`active`);t(()=>import(`./settings-C6Pku2GC.js`).then(t=>e?t.savePerCli():t.updateSettings()),__vite__mapDeps([0]))}function Ee(){y({onStatus:oe})}export{Ce as a,Ee as c,he as d,we as f,fe as i,pe as l,ye as n,Te as o,U as r,xe as s,Q as t,de as u};
|
|
2
|
+
import{t as e}from"./state-DxpgDKMO.js";import{t}from"./preload-helper-DFYRRz9w.js";import{N as n,T as r,j as i,k as a,t as o}from"./render-DZnFK8Kv.js";import{A as s,C as c,D as l,N as u,O as d,S as f,_ as ee,b as te,k as p,m,t as ne,v as h,w as g,x as re}from"./iframe-renderer-CvnKi77F.js";import{C as _,D as v,E as y,S as b,_ as x,b as S,f as ie,g as C,h as w,l as T,m as E,p as D,u as ae,v as oe,w as se,x as O,y as k}from"./message-history-BX9shNKA.js";import{s as ce}from"./idb-cache-lOntwU3S.js";var A=2e3,j=80,le=400;function ue(e){if(e<=A)return j;let t=e/A*j;return Math.min(le,Math.round(t))}function M(e){return{chunks:[],fullText:``,textDirty:!1,element:e,pendingRAF:null,isFinalized:!1,lastRenderTime:0}}function N(e,t=``){let n=M(e);return t?(n.chunks=[t],n.fullText=t,n.textDirty=!1,n.element.innerHTML=o(t,!0)+`<span class="stream-cursor" aria-hidden="true"></span>`):n.element.innerHTML=`<span class="stream-cursor" aria-hidden="true"></span>`,n}function P(e){return e.textDirty&&=(e.fullText=e.chunks.join(``),!1),e.fullText}function F(e,t){e.chunks.push(t),e.textDirty=!0,!e.pendingRAF&&!e.isFinalized&&(e.pendingRAF=requestAnimationFrame(()=>{if(e.pendingRAF=null,e.isFinalized)return;let t=performance.now(),n=P(e);n.length<A||t-e.lastRenderTime>ue(n.length)?(e.element.innerHTML=o(n,!0)+`<span class="stream-cursor" aria-hidden="true"></span>`,e.lastRenderTime=t):e.pendingRAF=requestAnimationFrame(()=>{e.pendingRAF=null,!e.isFinalized&&(e.element.innerHTML=o(P(e),!0)+`<span class="stream-cursor" aria-hidden="true"></span>`,e.lastRenderTime=performance.now())})}))}function I(e,t=!1){e.isFinalized=!0,e.pendingRAF&&=(cancelAnimationFrame(e.pendingRAF),null);let n=P(e);return t||(e.element.innerHTML=o(n)),n}function L(e){return typeof e.traceSeq==`number`&&Number.isFinite(e.traceSeq)&&e.traceSeq>0}function R(e,t){return!!t.traceRunId&&!!e.traceRunId&&t.traceRunId===e.traceRunId&&L(t)&&L(e)&&t.traceSeq===e.traceSeq&&t.type===e.type&&t.label===e.label&&!!t.isEmployee==!!e.isEmployee}function z(e,t){return t.stepRef&&e.stepRef===t.stepRef?!0:R(e,t)}function B(e,t,n={}){return[...e].reverse().filter(e=>n.includeDone||e.status===`running`).find(e=>z(e,t))??null}function V(e,t){let n=e.filter(e=>e.status===`running`&&!e.stepRef&&e.label===t.label&&e.type===t.type&&!!e.isEmployee==!!t.isEmployee);return n.length===1?n[0]:null}function H(e,t){let n=[...e].reverse().filter(e=>e.status===`running`);return B(n,t)||V(n,t)}function U(){e.currentProcessBlock&&l(e.currentProcessBlock),e.currentAgentDiv instanceof HTMLElement&&l(e.currentAgentDiv),v(),s(),e.currentAgentDiv=null,e.currentProcessBlock=null,q=null}var W=0;function de(){W=Date.now()}function fe(){W=0}function pe(){return Date.now()-W<8e3}function G(e){for(let t of document.querySelectorAll(`.msg-user`))if(e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING)return!0;return!1}function K(){let t=e.currentAgentDiv&&e.currentAgentDiv.isConnected?e.currentAgentDiv:null;return t?G(t)?(e.currentAgentDiv=null,e.currentProcessBlock=null,q=null,null):t:null}function me(){let e=Array.from(document.querySelectorAll(`.msg-agent`)).at(-1)??null;return!e||G(e)?null:e}function he(t,n){k();let r=K();if(r||(r=x(`agent`,``),e.currentAgentDiv=r,e.currentProcessBlock=null),b(r),!e.currentProcessBlock){let t=r.querySelector(`.agent-body`);t&&(e.currentProcessBlock=S(r)),!e.currentProcessBlock&&t&&(_(r),e.currentProcessBlock=f(t))}if(n&&e.currentProcessBlock&&!e.currentProcessBlock.startedAt&&(e.currentProcessBlock.startedAt=n),e.currentProcessBlock){let n=t.rawIcon||t.icon,r=t.status||a(t.icon)||`running`;if(r===`done`||r===`error`){let a=H(e.currentProcessBlock.steps,t);if(a){t.icon=i(t.icon);let o=t.type===`thinking`?p(a.id,t.detail):g(a.id,t.detail);d(e.currentProcessBlock,a.id,{...a,...t,id:a.id,rawIcon:n,detail:o,detailPreview:o,label:t.label||a.label,status:r,startTime:a.startTime}),m();return}if(t.stepRef&&(r===`done`||r===`error`)){let a=[...e.currentProcessBlock.steps].reverse().find(e=>e.stepRef===t.stepRef&&(e.status===`done`||e.status===`error`));if(a){t.icon=i(t.icon);let o=t.type===`thinking`?p(a.id,t.detail):g(a.id,t.detail);d(e.currentProcessBlock,a.id,{...a,...t,id:a.id,rawIcon:n,status:r,detail:o,detailPreview:o,startTime:a.startTime}),m();return}}}if(t.stepRef&&r===`running`){let r=[...e.currentProcessBlock.steps].reverse().find(e=>e.stepRef===t.stepRef&&e.status===`running`);if(r){t.rawIcon=n,t.icon=i(t.icon);let a=p(r.id,t.detail);d(e.currentProcessBlock,r.id,{...r,...t,id:r.id,rawIcon:n,status:`running`,detail:a,detailPreview:a,label:t.label||r.label,startTime:r.startTime}),m();return}}let o=B(e.currentProcessBlock.steps,t,{includeDone:!0});if(o){t.rawIcon=n,t.icon=i(t.icon);let a=t.detail||``,s=c(o.id)||o.detail||o.detailPreview||``,l=a.length>=s.length?a:s,u=p(o.id,l),f=(o.status===`done`||o.status===`error`)&&r===`running`?o.status:r;d(e.currentProcessBlock,o.id,{...o,...t,id:o.id,rawIcon:n,status:f,detail:u,detailPreview:u,label:t.label||o.label,startTime:o.startTime}),m();return}if(t.detail){let n=[...e.currentProcessBlock.steps].reverse().find(e=>e.status===`running`&&e.label===t.label&&e.type===t.type&&!!e.isEmployee==!!t.isEmployee&&!e.detail);if(n){d(e.currentProcessBlock,n.id,t),m();return}}t.rawIcon=n,t.icon=i(t.icon),h(e.currentProcessBlock,t)}m()}var q=null,J=`data-active-run-hydrated`;function Y(e){document.querySelectorAll(`[${J}="true"]`).forEach(t=>{e&&t===e||t.remove()})}function ge(e){let t=K()??me();Y(t);let n=t||x(`agent`,``,e||null);return n.setAttribute(J,`true`),n}function _e(e,t){let n=c(e.id)||e.detail||e.detailPreview||``,r=t.detail||t.detailPreview||``;return r.length>=n.length?r:n}function X(e){return e.stepRef?!0:!!e.traceRunId&&typeof e.traceSeq==`number`&&Number.isFinite(e.traceSeq)&&e.traceSeq>0}function ve(e,t){let n=e.steps.filter(e=>!X(e)),r=0,i=!0;for(let a of t){let t=e.steps.find(e=>z(e,a))??null;if(!t&&!X(a)&&i){let e=n[r]??null;e?e.type===a.type&&!!e.isEmployee==!!a.isEmployee?(t=e,r++):i=!1:r++}if(!t){let n=e.steps.filter(e=>e.label===a.label&&e.type===a.type&&!!e.isEmployee==!!a.isEmployee);t=n.length===1?n[0]:null}if(!t){if(h(e,a),!X(a)&&i){let t=e.steps[e.steps.length-1];t&&n.push(t)}continue}let o=p(t.id,_e(t,a));d(e,t.id,{...t,...a,id:t.id,detail:o,detailPreview:o,label:a.label||t.label})}}function ye(e=[]){document.querySelectorAll(`[data-queued-overlay="true"]`).forEach(e=>e.remove())}var Z=``;function be(e){let t=Array.isArray(e.toolLog)?e.toolLog:[],n=t.map(e=>`${e.stepRef||``}~${e.traceRunId||``}~${e.traceSeq??``}~${e.status||``}~${e.label||``}~${(e.detail||``).length}`).join(`|`);return`${e.traceRunId||``}:${e.cli||``}:${(e.text||``).length}:${t.length}:${n}`}function xe(t){if(!t?.running){Z=``,Y();return}let n=be(t);if(n===Z&&K())return;v(),k(),e.currentAgentDiv=ge(t.cli||null);let r=e.currentAgentDiv.querySelector(`.agent-body`),i=E(t.toolLog||[]);b(e.currentAgentDiv),e.currentProcessBlock=S(e.currentAgentDiv),r&&i.length?(e.currentProcessBlock||=(_(e.currentAgentDiv),f(r)),t.startedAt&&(e.currentProcessBlock.startedAt=t.startedAt),ve(e.currentProcessBlock,C(i,t.startedAt))):e.currentProcessBlock=S(e.currentAgentDiv);let a=e.currentAgentDiv.querySelector(`.msg-content`);a&&(q=N(a,t.text||``)),Z=n}function Q(t){if(!t)return;k(),K()||(e.currentAgentDiv=x(`agent`,``),q=null);let n=e.currentAgentDiv?.querySelector(`.msg-content`);n&&(q||=M(n),F(q,t)),m()}var $=0;function Se(e){e.querySelectorAll(`.mermaid-pending`).forEach(e=>{delete e.dataset.mermaidQueued,delete e.dataset.mermaidQueuedAt})}function Ce(t,i){let a=Date.now();if(!e.currentAgentDiv&&a-$<500)return;Z=``,v(),k(),e.currentAgentDiv&&b(e.currentAgentDiv);let s=se(e.currentProcessBlock??e.currentAgentDiv,!0),c=E(D(Array.isArray(i)?i:[],s)),d=w(c),f=!!e.currentProcessBlock||!!(e.currentAgentDiv&&O(e.currentAgentDiv));e.currentProcessBlock&&=(re(e.currentProcessBlock),null);let p=c.length>0;if(t||p){(!e.currentAgentDiv||!e.currentAgentDiv.isConnected)&&(e.currentAgentDiv=x(`agent`,``)),e.currentAgentDiv.removeAttribute(J);let i=e.currentAgentDiv?.querySelector(`.msg-content`),a=q?I(q,!0):``,s=t||a;if(q=null,i&&(i.innerHTML=o(s)),p&&e.currentAgentDiv&&!f&&!O(e.currentAgentDiv)){let t=e.currentAgentDiv.querySelector(`.msg-content`);t&&t.insertAdjacentHTML(`beforebegin`,te(C(c),!0))}e.currentAgentDiv&&b(e.currentAgentDiv),i&&i.setAttribute(`data-raw`,n(s)),i&&ne(i);let m=ee(),h=!!(m.active&&e.currentAgentDiv&&e.currentAgentDiv.isConnected);if(i&&!h&&r(i,{immediate:!0}),h){let t=e.currentAgentDiv;Se(t),t.querySelectorAll(`.diagram-widget`).forEach(e=>{let t=e,n=t.dataset.widgetHtml,r=t.dataset.widgetId;if(!n&&!r)return;let i=document.createElement(`div`);i.className=`diagram-widget-pending`,r?i.dataset.widgetId=r:n&&(i.dataset.diagramHtml=n),e.replaceWith(i)}),d?(m.appendItem(ie({role:`assistant`,content:s,cli:null,tool_log:d},m.count)),l(t),m.scrollToBottom()):(t.dataset.turnIndex=String(m.count),t.dataset.messageId||(t.dataset.messageId=u()),m.appendLiveItem(t)),t.remove()}s&&ce({role:`assistant`,content:s,tool_log:d,timestamp:Date.now()}).catch(()=>{})}q=null,e.currentAgentDiv=null,$=Date.now(),ae(`idle`),T()}function we(e,n){document.querySelectorAll(`.tab-btn`).forEach(e=>{e.classList.remove(`active`),e.setAttribute(`aria-selected`,`false`)}),document.querySelectorAll(`.tab-content`).forEach(e=>e.classList.remove(`active`)),document.getElementById({agents:`tabAgents`,settings:`tabSettings`,skills:`tabSkills`}[e])?.classList.add(`active`),n&&(n.classList.add(`active`),n.setAttribute(`aria-selected`,`true`)),e===`settings`&&t(()=>import(`./settings-DotRDITm.js`).then(e=>e.loadSettings()),__vite__mapDeps([0])),e===`agents`&&t(()=>import(`./employees-BFKkaVAe.js`).then(e=>e.loadEmployees()),__vite__mapDeps([0])),e===`skills`&&t(()=>import(`./skills-DZHEvl1x.js`).then(e=>e.loadSkills()),__vite__mapDeps([0]))}function Te(){let e=document.getElementById(`tabSettings`)?.classList.contains(`active`);t(()=>import(`./settings-DotRDITm.js`).then(t=>e?t.savePerCli():t.updateSettings()),__vite__mapDeps([0]))}function Ee(){y({onStatus:oe})}export{Ce as a,Ee as c,he as d,we as f,fe as i,pe as l,ye as n,Te as o,U as r,xe as s,Q as t,de as u};
|
package/public/dist/index.html
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@400;500;600;700&family=Outfit:wght@400;500;600;700&display=swap"
|
|
32
32
|
rel="stylesheet">
|
|
33
33
|
<!-- Vite handles module bundling in dev (HMR) and production (build) -->
|
|
34
|
-
<script type="module" crossorigin src="/dist/assets/app-
|
|
34
|
+
<script type="module" crossorigin src="/dist/assets/app-TWwXw3iD.js"></script>
|
|
35
35
|
<link rel="stylesheet" crossorigin href="/dist/assets/vendor-render-Bjnw0wQ6.css">
|
|
36
36
|
<link rel="stylesheet" crossorigin href="/dist/assets/app-DuyLYRC6.css">
|
|
37
37
|
</head>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<link rel="icon" type="image/png" href="/dist/assets/icon-192-CwXPIYF5.png">
|
|
7
7
|
<title>Jaw Manager</title>
|
|
8
8
|
<script src="/manager/theme-boot.js"></script>
|
|
9
|
-
<script type="module" crossorigin src="/dist/assets/manager-
|
|
9
|
+
<script type="module" crossorigin src="/dist/assets/manager-BTpvAFwX.js"></script>
|
|
10
10
|
<link rel="stylesheet" crossorigin href="/dist/assets/vendor-render-Bjnw0wQ6.css">
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/dist/assets/manager-LkGs6qEJ.css">
|
|
12
12
|
</head>
|
|
@@ -8,22 +8,52 @@ import { refreshTransportStatusRow } from './transport-status-row.js';
|
|
|
8
8
|
import { openSetupGuideIfUnconfigured } from './channel-setup-guide.js';
|
|
9
9
|
import type { SettingsData } from './settings-types.js';
|
|
10
10
|
|
|
11
|
-
export
|
|
11
|
+
export type MessengerChannel = 'telegram' | 'discord' | 'slack';
|
|
12
|
+
const CHANNELS: MessengerChannel[] = ['telegram', 'discord', 'slack'];
|
|
13
|
+
|
|
14
|
+
function readEnabledChannelsFromUi(): MessengerChannel[] {
|
|
15
|
+
// Classic UI does not yet expose a unified enabled-channel list.
|
|
16
|
+
// Returning [] means enabling a channel sends a singleton enabled set,
|
|
17
|
+
// which preserves the legacy single-active-channel behavior until the
|
|
18
|
+
// Classic UI gains per-channel enabled checkboxes.
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function setChannelEnabled(ch: MessengerChannel, enabled: boolean): Promise<void> {
|
|
23
|
+
const current = readEnabledChannelsFromUi();
|
|
24
|
+
const enabledChannels = enabled
|
|
25
|
+
? [...new Set([...current, ch])]
|
|
26
|
+
: current.filter(item => item !== ch);
|
|
27
|
+
await apiJson('/api/settings', 'PUT', { messaging: { enabledChannels } });
|
|
28
|
+
await refreshTransportStatusRow();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function setHomeChannel(ch: MessengerChannel): Promise<void> {
|
|
12
32
|
document.getElementById('chTelegram')?.classList.toggle('active', ch === 'telegram');
|
|
13
33
|
document.getElementById('chDiscord')?.classList.toggle('active', ch === 'discord');
|
|
14
34
|
document.getElementById('chSlack')?.classList.toggle('active', ch === 'slack');
|
|
15
35
|
document.getElementById('channelTelegramSettings')?.style.setProperty('display', ch === 'telegram' ? '' : 'none');
|
|
16
36
|
document.getElementById('channelDiscordSettings')?.style.setProperty('display', ch === 'discord' ? '' : 'none');
|
|
17
37
|
document.getElementById('channelSlackSettings')?.style.setProperty('display', ch === 'slack' ? '' : 'none');
|
|
18
|
-
await apiJson('/api/settings', 'PUT', {
|
|
38
|
+
await apiJson('/api/settings', 'PUT', { messaging: { homeChannel: ch } });
|
|
19
39
|
await refreshTransportStatusRow();
|
|
20
|
-
|
|
21
|
-
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @deprecated Use setChannelEnabled + setHomeChannel. Kept temporarily for
|
|
44
|
+
* classic entry points that still bind the channel tabs.
|
|
45
|
+
*/
|
|
46
|
+
export async function setActiveChannel(ch: MessengerChannel): Promise<void> {
|
|
47
|
+
await setChannelEnabled(ch, true);
|
|
48
|
+
await setHomeChannel(ch);
|
|
22
49
|
openSetupGuideIfUnconfigured(ch);
|
|
23
50
|
}
|
|
24
51
|
|
|
25
52
|
export function loadActiveChannel(s: SettingsData): void {
|
|
26
|
-
const
|
|
53
|
+
const messaging = s.messaging;
|
|
54
|
+
const ch: MessengerChannel = messaging?.homeChannel && CHANNELS.includes(messaging.homeChannel)
|
|
55
|
+
? messaging.homeChannel
|
|
56
|
+
: 'telegram';
|
|
27
57
|
document.getElementById('chTelegram')?.classList.toggle('active', ch === 'telegram');
|
|
28
58
|
document.getElementById('chDiscord')?.classList.toggle('active', ch === 'discord');
|
|
29
59
|
document.getElementById('chSlack')?.classList.toggle('active', ch === 'slack');
|
|
@@ -115,6 +115,11 @@ export function describeCliProbeAvailability(
|
|
|
115
115
|
export function shouldHydrateRuntimeMigrationResponse(status: number): boolean {
|
|
116
116
|
return status >= 200 && status < 300 || status === 409;
|
|
117
117
|
}
|
|
118
|
+
export interface MessagingConfig {
|
|
119
|
+
enabledChannels?: ('telegram' | 'discord' | 'slack')[];
|
|
120
|
+
homeChannel?: 'telegram' | 'discord' | 'slack';
|
|
121
|
+
}
|
|
122
|
+
|
|
118
123
|
export interface SettingsData {
|
|
119
124
|
cli: string; workingDir: string; permissions: string; locale?: string; showReasoning?: boolean;
|
|
120
125
|
perCli?: Record<string, PerCliConfig>;
|
|
@@ -123,7 +128,9 @@ export interface SettingsData {
|
|
|
123
128
|
discord?: DiscordConfig;
|
|
124
129
|
slack?: SlackConfig;
|
|
125
130
|
slackEnvironmentVariables?: string[];
|
|
131
|
+
/** @deprecated Use messaging.homeChannel (response alias). */
|
|
126
132
|
channel?: 'telegram' | 'discord' | 'slack';
|
|
133
|
+
messaging?: MessagingConfig;
|
|
127
134
|
fallbackOrder?: string[];
|
|
128
135
|
memory?: { cli?: string };
|
|
129
136
|
projectDirs?: string[] | null;
|
|
@@ -2,6 +2,8 @@ import { api } from '../api.js';
|
|
|
2
2
|
import { escapeHtml } from '../render.js';
|
|
3
3
|
import { t } from './i18n.js';
|
|
4
4
|
|
|
5
|
+
export type MessengerChannel = 'telegram' | 'discord' | 'slack';
|
|
6
|
+
|
|
5
7
|
export type TransportStatus = {
|
|
6
8
|
configured: boolean;
|
|
7
9
|
activeInbound: boolean;
|
|
@@ -10,7 +12,9 @@ export type TransportStatus = {
|
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
export type ChannelHealth = {
|
|
13
|
-
|
|
15
|
+
/** @deprecated Use activeInboundChannels for the actual running set. */
|
|
16
|
+
activeInbound: MessengerChannel;
|
|
17
|
+
activeInboundChannels: MessengerChannel[];
|
|
14
18
|
telegram: TransportStatus;
|
|
15
19
|
discord: TransportStatus;
|
|
16
20
|
slack: TransportStatus;
|
|
@@ -24,15 +28,29 @@ function isTransportStatus(value: unknown): value is TransportStatus {
|
|
|
24
28
|
&& typeof row['sendCapable'] === 'boolean';
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
function isMessengerChannel(value: unknown): value is MessengerChannel {
|
|
32
|
+
return value === 'telegram' || value === 'discord' || value === 'slack';
|
|
33
|
+
}
|
|
34
|
+
|
|
27
35
|
export function parseChannelHealth(payload: unknown): ChannelHealth | null {
|
|
28
36
|
if (!payload || typeof payload !== 'object') return null;
|
|
29
37
|
const channels = (payload as { channels?: unknown }).channels;
|
|
30
38
|
if (!channels || typeof channels !== 'object') return null;
|
|
31
39
|
const row = channels as Record<string, unknown>;
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
const rawActiveChannels = row['activeInboundChannels'];
|
|
41
|
+
let activeInboundChannels: MessengerChannel[] | null;
|
|
42
|
+
if (rawActiveChannels !== undefined) {
|
|
43
|
+
activeInboundChannels = Array.isArray(rawActiveChannels)
|
|
44
|
+
&& rawActiveChannels.every(isMessengerChannel)
|
|
45
|
+
? [...new Set(rawActiveChannels)]
|
|
46
|
+
: null;
|
|
47
|
+
} else {
|
|
48
|
+
const legacyActive = row['activeInbound'];
|
|
49
|
+
activeInboundChannels = isMessengerChannel(legacyActive) ? [legacyActive] : null;
|
|
50
|
+
}
|
|
51
|
+
if (!activeInboundChannels) return null;
|
|
34
52
|
const active = row['activeInbound'];
|
|
35
|
-
if (active
|
|
53
|
+
if (!isMessengerChannel(active)) return null;
|
|
36
54
|
if (!isTransportStatus(row['telegram']) || !isTransportStatus(row['discord'])) return null;
|
|
37
55
|
// Slack is tolerated as absent: a newer bundle can be served against an
|
|
38
56
|
// older running server during a rolling update, and rejecting the whole
|
|
@@ -42,6 +60,7 @@ export function parseChannelHealth(payload: unknown): ChannelHealth | null {
|
|
|
42
60
|
: { configured: false, activeInbound: false, sendCapable: false, reason: 'unavailable' };
|
|
43
61
|
return {
|
|
44
62
|
activeInbound: active,
|
|
63
|
+
activeInboundChannels,
|
|
45
64
|
telegram: row['telegram'],
|
|
46
65
|
discord: row['discord'],
|
|
47
66
|
slack,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Phase 3 — Channels (Discord) page.
|
|
2
2
|
//
|
|
3
3
|
// Settings keys:
|
|
4
|
-
//
|
|
4
|
+
// messaging.enabledChannels
|
|
5
|
+
// messaging.homeChannel
|
|
5
6
|
// discord.enabled
|
|
6
7
|
// discord.token (SecretField, masked, never logged)
|
|
7
8
|
// discord.guildId
|
|
@@ -21,10 +22,15 @@ import {
|
|
|
21
22
|
usePageSnapshot,
|
|
22
23
|
} from './page-shell';
|
|
23
24
|
import { expandPatch } from './path-utils';
|
|
24
|
-
import { ActiveChannelToggle } from './components/ActiveChannelToggle';
|
|
25
|
-
import type { ActiveChannel } from './components/ActiveChannelToggle';
|
|
26
25
|
import { HealthBadge, interpretDiscordHealth } from './components/HealthBadge';
|
|
26
|
+
import type { MessengerChannel } from './components/ChannelEnablementControl';
|
|
27
27
|
import { TransportStatusChips } from './components/TransportStatusChips';
|
|
28
|
+
import { ChannelEnablementControl } from './components/ChannelEnablementControl';
|
|
29
|
+
|
|
30
|
+
type MessagingBlock = {
|
|
31
|
+
enabledChannels?: MessengerChannel[];
|
|
32
|
+
homeChannel?: MessengerChannel;
|
|
33
|
+
};
|
|
28
34
|
|
|
29
35
|
type DiscordBlock = {
|
|
30
36
|
enabled?: boolean;
|
|
@@ -37,13 +43,15 @@ type DiscordBlock = {
|
|
|
37
43
|
};
|
|
38
44
|
|
|
39
45
|
type DiscordSnapshot = {
|
|
40
|
-
channel?:
|
|
46
|
+
channel?: MessengerChannel;
|
|
41
47
|
discord?: DiscordBlock;
|
|
48
|
+
messaging?: MessagingBlock;
|
|
42
49
|
[key: string]: unknown;
|
|
43
50
|
};
|
|
44
51
|
|
|
45
52
|
const DISCORD_KEYS = [
|
|
46
|
-
'
|
|
53
|
+
'messaging.enabledChannels',
|
|
54
|
+
'messaging.homeChannel',
|
|
47
55
|
'discord.enabled',
|
|
48
56
|
'discord.token',
|
|
49
57
|
'discord.guildId',
|
|
@@ -62,6 +70,8 @@ export function isValidSnowflake(chip: string): boolean {
|
|
|
62
70
|
export default function ChannelsDiscord({ port, client, dirty, registerSave }: SettingsPageProps) {
|
|
63
71
|
const { state, refresh, setData } = usePageSnapshot<DiscordSnapshot>(client, '/api/settings');
|
|
64
72
|
|
|
73
|
+
const [enabledChannels, setEnabledChannels] = useState<MessengerChannel[]>([]);
|
|
74
|
+
const [homeChannel, setHomeChannel] = useState<MessengerChannel>('telegram');
|
|
65
75
|
const [enabled, setEnabled] = useState(false);
|
|
66
76
|
const [token, setToken] = useState('');
|
|
67
77
|
const [guildId, setGuildId] = useState('');
|
|
@@ -72,6 +82,10 @@ export default function ChannelsDiscord({ port, client, dirty, registerSave }: S
|
|
|
72
82
|
|
|
73
83
|
useEffect(() => {
|
|
74
84
|
if (state.kind !== 'ready') return;
|
|
85
|
+
const messaging = state.data.messaging || {};
|
|
86
|
+
const rawEnabled = Array.isArray(messaging.enabledChannels) ? messaging.enabledChannels : [];
|
|
87
|
+
setEnabledChannels(rawEnabled.filter(isMessengerChannel) as MessengerChannel[]);
|
|
88
|
+
setHomeChannel(isMessengerChannel(messaging.homeChannel) ? messaging.homeChannel : 'telegram');
|
|
75
89
|
const dc = state.data.discord || {};
|
|
76
90
|
setEnabled(Boolean(dc.enabled));
|
|
77
91
|
setToken('');
|
|
@@ -98,8 +112,6 @@ export default function ChannelsDiscord({ port, client, dirty, registerSave }: S
|
|
|
98
112
|
return state.data.discord || {};
|
|
99
113
|
}, [state]);
|
|
100
114
|
|
|
101
|
-
const originalChannel = state.kind === 'ready' ? state.data.channel : undefined;
|
|
102
|
-
|
|
103
115
|
const onSave = useCallback(async () => {
|
|
104
116
|
const bundle = dirty.saveBundle();
|
|
105
117
|
if (Object.keys(bundle).length === 0) return;
|
|
@@ -110,6 +122,13 @@ export default function ChannelsDiscord({ port, client, dirty, registerSave }: S
|
|
|
110
122
|
: updated) as DiscordSnapshot;
|
|
111
123
|
dirty.clear();
|
|
112
124
|
setData(fresh);
|
|
125
|
+
const freshMessaging = fresh.messaging || {};
|
|
126
|
+
setEnabledChannels(
|
|
127
|
+
Array.isArray(freshMessaging.enabledChannels)
|
|
128
|
+
? freshMessaging.enabledChannels.filter(isMessengerChannel) as MessengerChannel[]
|
|
129
|
+
: [],
|
|
130
|
+
);
|
|
131
|
+
setHomeChannel(isMessengerChannel(freshMessaging.homeChannel) ? freshMessaging.homeChannel : 'telegram');
|
|
113
132
|
const dc = fresh.discord || {};
|
|
114
133
|
setEnabled(Boolean(dc.enabled));
|
|
115
134
|
setToken('');
|
|
@@ -151,12 +170,17 @@ export default function ChannelsDiscord({ port, client, dirty, registerSave }: S
|
|
|
151
170
|
>
|
|
152
171
|
<SettingsSection
|
|
153
172
|
title="Channels"
|
|
154
|
-
hint="Choose which
|
|
173
|
+
hint="Choose which channels receive inbound chat. Outbound send can still work on any configured channel."
|
|
155
174
|
>
|
|
156
|
-
<
|
|
157
|
-
|
|
175
|
+
<ChannelEnablementControl
|
|
176
|
+
pageChannel="discord"
|
|
177
|
+
snapshot={state.kind === 'ready' ? state.data : {}}
|
|
178
|
+
enabledChannels={enabledChannels}
|
|
179
|
+
homeChannel={homeChannel}
|
|
180
|
+
setEnabledChannels={setEnabledChannels}
|
|
181
|
+
setHomeChannel={setHomeChannel}
|
|
158
182
|
dirty={dirty}
|
|
159
|
-
idPrefix="
|
|
183
|
+
idPrefix="di-channel"
|
|
160
184
|
/>
|
|
161
185
|
<TransportStatusChips client={client} channel="discord" />
|
|
162
186
|
</SettingsSection>
|
|
@@ -281,3 +305,7 @@ export default function ChannelsDiscord({ port, client, dirty, registerSave }: S
|
|
|
281
305
|
</form>
|
|
282
306
|
);
|
|
283
307
|
}
|
|
308
|
+
|
|
309
|
+
function isMessengerChannel(value: unknown): value is MessengerChannel {
|
|
310
|
+
return value === 'telegram' || value === 'discord' || value === 'slack';
|
|
311
|
+
}
|
|
@@ -23,9 +23,9 @@ import {
|
|
|
23
23
|
usePageSnapshot,
|
|
24
24
|
} from './page-shell';
|
|
25
25
|
import { expandPatch } from './path-utils';
|
|
26
|
-
import {
|
|
27
|
-
import type { ActiveChannel } from './components/ActiveChannelToggle';
|
|
26
|
+
import type { MessengerChannel } from './components/ChannelEnablementControl';
|
|
28
27
|
import { TransportStatusChips } from './components/TransportStatusChips';
|
|
28
|
+
import { ChannelEnablementControl } from './components/ChannelEnablementControl';
|
|
29
29
|
|
|
30
30
|
type SlackBlock = {
|
|
31
31
|
enabled?: boolean;
|
|
@@ -40,7 +40,10 @@ type SlackBlock = {
|
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
type SlackSnapshot = {
|
|
43
|
-
|
|
43
|
+
messaging?: {
|
|
44
|
+
enabledChannels?: MessengerChannel[];
|
|
45
|
+
homeChannel?: MessengerChannel;
|
|
46
|
+
};
|
|
44
47
|
slack?: SlackBlock;
|
|
45
48
|
slackEnvironmentVariables?: string[];
|
|
46
49
|
[key: string]: unknown;
|
|
@@ -72,6 +75,8 @@ export function isValidSlackConversationId(chip: string): boolean {
|
|
|
72
75
|
export default function ChannelsSlack({ port, client, dirty, registerSave }: SettingsPageProps) {
|
|
73
76
|
const { state, refresh, setData } = usePageSnapshot<SlackSnapshot>(client, '/api/settings');
|
|
74
77
|
|
|
78
|
+
const [enabledChannels, setEnabledChannels] = useState<MessengerChannel[]>([]);
|
|
79
|
+
const [homeChannel, setHomeChannel] = useState<MessengerChannel>('telegram');
|
|
75
80
|
const [enabled, setEnabled] = useState(false);
|
|
76
81
|
const [botToken, setBotToken] = useState('');
|
|
77
82
|
const [appToken, setAppToken] = useState('');
|
|
@@ -98,6 +103,10 @@ export default function ChannelsSlack({ port, client, dirty, registerSave }: Set
|
|
|
98
103
|
|
|
99
104
|
useEffect(() => {
|
|
100
105
|
if (state.kind !== 'ready') return;
|
|
106
|
+
const messaging = state.data.messaging || {};
|
|
107
|
+
const rawEnabled = Array.isArray(messaging.enabledChannels) ? messaging.enabledChannels : [];
|
|
108
|
+
setEnabledChannels(rawEnabled.filter(isMessengerChannel) as MessengerChannel[]);
|
|
109
|
+
setHomeChannel(isMessengerChannel(messaging.homeChannel) ? messaging.homeChannel : 'telegram');
|
|
101
110
|
applySnapshot(state.data.slack || {});
|
|
102
111
|
}, [state, applySnapshot]);
|
|
103
112
|
|
|
@@ -117,7 +126,6 @@ export default function ChannelsSlack({ port, client, dirty, registerSave }: Set
|
|
|
117
126
|
return state.data.slack || {};
|
|
118
127
|
}, [state]);
|
|
119
128
|
|
|
120
|
-
const originalChannel = state.kind === 'ready' ? state.data.channel : undefined;
|
|
121
129
|
const environmentVariables = state.kind === 'ready' && Array.isArray(state.data.slackEnvironmentVariables)
|
|
122
130
|
? state.data.slackEnvironmentVariables
|
|
123
131
|
: [];
|
|
@@ -178,10 +186,15 @@ export default function ChannelsSlack({ port, client, dirty, registerSave }: Set
|
|
|
178
186
|
>
|
|
179
187
|
<SettingsSection
|
|
180
188
|
title="Channels"
|
|
181
|
-
hint="Choose which
|
|
189
|
+
hint="Choose which channels receive inbound chat. Outbound send can still work on any configured channel."
|
|
182
190
|
>
|
|
183
|
-
<
|
|
184
|
-
|
|
191
|
+
<ChannelEnablementControl
|
|
192
|
+
pageChannel="slack"
|
|
193
|
+
snapshot={state.kind === 'ready' ? state.data : {}}
|
|
194
|
+
enabledChannels={enabledChannels}
|
|
195
|
+
homeChannel={homeChannel}
|
|
196
|
+
setEnabledChannels={setEnabledChannels}
|
|
197
|
+
setHomeChannel={setHomeChannel}
|
|
185
198
|
dirty={dirty}
|
|
186
199
|
idPrefix="sl-channel"
|
|
187
200
|
/>
|
|
@@ -333,3 +346,7 @@ export default function ChannelsSlack({ port, client, dirty, registerSave }: Set
|
|
|
333
346
|
</form>
|
|
334
347
|
);
|
|
335
348
|
}
|
|
349
|
+
|
|
350
|
+
function isMessengerChannel(value: unknown): value is MessengerChannel {
|
|
351
|
+
return value === 'telegram' || value === 'discord' || value === 'slack';
|
|
352
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Phase 3 — Channels (Telegram) page.
|
|
2
2
|
//
|
|
3
3
|
// Settings keys:
|
|
4
|
-
//
|
|
4
|
+
// messaging.enabledChannels
|
|
5
|
+
// messaging.homeChannel
|
|
5
6
|
// telegram.enabled
|
|
6
7
|
// telegram.token (SecretField, masked, never logged)
|
|
7
8
|
// telegram.allowedChatIds (number[], rendered as numeric chips)
|
|
@@ -19,10 +20,15 @@ import {
|
|
|
19
20
|
usePageSnapshot,
|
|
20
21
|
} from './page-shell';
|
|
21
22
|
import { expandPatch } from './path-utils';
|
|
22
|
-
import { ActiveChannelToggle } from './components/ActiveChannelToggle';
|
|
23
|
-
import type { ActiveChannel } from './components/ActiveChannelToggle';
|
|
24
23
|
import { HealthBadge, interpretTelegramProbe } from './components/HealthBadge';
|
|
24
|
+
import type { MessengerChannel } from './components/ChannelEnablementControl';
|
|
25
25
|
import { TransportStatusChips } from './components/TransportStatusChips';
|
|
26
|
+
import { ChannelEnablementControl } from './components/ChannelEnablementControl';
|
|
27
|
+
|
|
28
|
+
type MessagingBlock = {
|
|
29
|
+
enabledChannels?: MessengerChannel[];
|
|
30
|
+
homeChannel?: MessengerChannel;
|
|
31
|
+
};
|
|
26
32
|
|
|
27
33
|
type TelegramBlock = {
|
|
28
34
|
enabled?: boolean;
|
|
@@ -33,13 +39,15 @@ type TelegramBlock = {
|
|
|
33
39
|
};
|
|
34
40
|
|
|
35
41
|
type TelegramSnapshot = {
|
|
36
|
-
channel?:
|
|
42
|
+
channel?: MessengerChannel;
|
|
37
43
|
telegram?: TelegramBlock;
|
|
44
|
+
messaging?: MessagingBlock;
|
|
38
45
|
[key: string]: unknown;
|
|
39
46
|
};
|
|
40
47
|
|
|
41
48
|
const TELEGRAM_KEYS = [
|
|
42
|
-
'
|
|
49
|
+
'messaging.enabledChannels',
|
|
50
|
+
'messaging.homeChannel',
|
|
43
51
|
'telegram.enabled',
|
|
44
52
|
'telegram.token',
|
|
45
53
|
'telegram.allowedChatIds',
|
|
@@ -91,6 +99,8 @@ export function chipsToChatIds(chips: ReadonlyArray<string>): number[] {
|
|
|
91
99
|
export default function ChannelsTelegram({ port, client, dirty, registerSave }: SettingsPageProps) {
|
|
92
100
|
const { state, refresh, setData } = usePageSnapshot<TelegramSnapshot>(client, '/api/settings');
|
|
93
101
|
|
|
102
|
+
const [enabledChannels, setEnabledChannels] = useState<MessengerChannel[]>([]);
|
|
103
|
+
const [homeChannel, setHomeChannel] = useState<MessengerChannel>('telegram');
|
|
94
104
|
const [enabled, setEnabled] = useState(false);
|
|
95
105
|
const [token, setToken] = useState('');
|
|
96
106
|
const [chips, setChips] = useState<string[]>([]);
|
|
@@ -99,6 +109,10 @@ export default function ChannelsTelegram({ port, client, dirty, registerSave }:
|
|
|
99
109
|
|
|
100
110
|
useEffect(() => {
|
|
101
111
|
if (state.kind !== 'ready') return;
|
|
112
|
+
const messaging = state.data.messaging || {};
|
|
113
|
+
const rawEnabled = Array.isArray(messaging.enabledChannels) ? messaging.enabledChannels : [];
|
|
114
|
+
setEnabledChannels(rawEnabled.filter(isMessengerChannel) as MessengerChannel[]);
|
|
115
|
+
setHomeChannel(isMessengerChannel(messaging.homeChannel) ? messaging.homeChannel : 'telegram');
|
|
102
116
|
const tg = state.data.telegram || {};
|
|
103
117
|
setEnabled(Boolean(tg.enabled));
|
|
104
118
|
// Token field starts empty — we never seed the actual secret into the
|
|
@@ -126,8 +140,6 @@ export default function ChannelsTelegram({ port, client, dirty, registerSave }:
|
|
|
126
140
|
return state.data.telegram || {};
|
|
127
141
|
}, [state]);
|
|
128
142
|
|
|
129
|
-
const originalChannel = state.kind === 'ready' ? state.data.channel : undefined;
|
|
130
|
-
|
|
131
143
|
const onSave = useCallback(async () => {
|
|
132
144
|
const bundle = dirty.saveBundle();
|
|
133
145
|
if (Object.keys(bundle).length === 0) return;
|
|
@@ -138,6 +150,13 @@ export default function ChannelsTelegram({ port, client, dirty, registerSave }:
|
|
|
138
150
|
: updated) as TelegramSnapshot;
|
|
139
151
|
dirty.clear();
|
|
140
152
|
setData(fresh);
|
|
153
|
+
const freshMessaging = fresh.messaging || {};
|
|
154
|
+
setEnabledChannels(
|
|
155
|
+
Array.isArray(freshMessaging.enabledChannels)
|
|
156
|
+
? freshMessaging.enabledChannels.filter(isMessengerChannel) as MessengerChannel[]
|
|
157
|
+
: [],
|
|
158
|
+
);
|
|
159
|
+
setHomeChannel(isMessengerChannel(freshMessaging.homeChannel) ? freshMessaging.homeChannel : 'telegram');
|
|
141
160
|
const tg = fresh.telegram || {};
|
|
142
161
|
setEnabled(Boolean(tg.enabled));
|
|
143
162
|
setToken('');
|
|
@@ -174,10 +193,15 @@ export default function ChannelsTelegram({ port, client, dirty, registerSave }:
|
|
|
174
193
|
>
|
|
175
194
|
<SettingsSection
|
|
176
195
|
title="Channels"
|
|
177
|
-
hint="Choose which
|
|
196
|
+
hint="Choose which channels receive inbound chat. Outbound send can still work on any configured channel."
|
|
178
197
|
>
|
|
179
|
-
<
|
|
180
|
-
|
|
198
|
+
<ChannelEnablementControl
|
|
199
|
+
pageChannel="telegram"
|
|
200
|
+
snapshot={state.kind === 'ready' ? state.data : {}}
|
|
201
|
+
enabledChannels={enabledChannels}
|
|
202
|
+
homeChannel={homeChannel}
|
|
203
|
+
setEnabledChannels={setEnabledChannels}
|
|
204
|
+
setHomeChannel={setHomeChannel}
|
|
181
205
|
dirty={dirty}
|
|
182
206
|
idPrefix="tg-channel"
|
|
183
207
|
/>
|
|
@@ -277,3 +301,7 @@ export default function ChannelsTelegram({ port, client, dirty, registerSave }:
|
|
|
277
301
|
</form>
|
|
278
302
|
);
|
|
279
303
|
}
|
|
304
|
+
|
|
305
|
+
function isMessengerChannel(value: unknown): value is MessengerChannel {
|
|
306
|
+
return value === 'telegram' || value === 'discord' || value === 'slack';
|
|
307
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
import type { DirtyStore } from '../../types';
|
|
3
|
+
import { ToggleField } from '../../fields';
|
|
4
|
+
|
|
5
|
+
export type MessengerChannel = 'telegram' | 'discord' | 'slack';
|
|
6
|
+
|
|
7
|
+
type MessagingBlock = {
|
|
8
|
+
enabledChannels?: MessengerChannel[];
|
|
9
|
+
homeChannel?: MessengerChannel;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type Props = {
|
|
13
|
+
pageChannel: MessengerChannel;
|
|
14
|
+
snapshot: { messaging?: MessagingBlock };
|
|
15
|
+
enabledChannels: MessengerChannel[];
|
|
16
|
+
homeChannel: MessengerChannel;
|
|
17
|
+
setEnabledChannels: (next: MessengerChannel[]) => void;
|
|
18
|
+
setHomeChannel: (next: MessengerChannel) => void;
|
|
19
|
+
dirty: DirtyStore;
|
|
20
|
+
idPrefix?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function ChannelEnablementControl({
|
|
24
|
+
pageChannel,
|
|
25
|
+
snapshot,
|
|
26
|
+
enabledChannels,
|
|
27
|
+
homeChannel,
|
|
28
|
+
setEnabledChannels,
|
|
29
|
+
setHomeChannel,
|
|
30
|
+
dirty,
|
|
31
|
+
idPrefix = 'channel',
|
|
32
|
+
}: Props) {
|
|
33
|
+
const messaging = snapshot.messaging || {};
|
|
34
|
+
const originalEnabled = Array.isArray(messaging.enabledChannels) ? messaging.enabledChannels : [];
|
|
35
|
+
const originalHome = messaging.homeChannel || 'telegram';
|
|
36
|
+
|
|
37
|
+
const setDirtyEnabled = useCallback((next: MessengerChannel[]) => {
|
|
38
|
+
dirty.set('messaging.enabledChannels', {
|
|
39
|
+
value: next,
|
|
40
|
+
original: originalEnabled,
|
|
41
|
+
valid: true,
|
|
42
|
+
});
|
|
43
|
+
}, [dirty, originalEnabled]);
|
|
44
|
+
|
|
45
|
+
const setDirtyHome = useCallback((next: MessengerChannel) => {
|
|
46
|
+
dirty.set('messaging.homeChannel', {
|
|
47
|
+
value: next,
|
|
48
|
+
original: originalHome,
|
|
49
|
+
valid: true,
|
|
50
|
+
});
|
|
51
|
+
}, [dirty, originalHome]);
|
|
52
|
+
|
|
53
|
+
const isEnabled = enabledChannels.includes(pageChannel);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<>
|
|
57
|
+
<ToggleField
|
|
58
|
+
id={`${idPrefix}-enabled`}
|
|
59
|
+
label={`Receive inbound messages on ${capitalize(pageChannel)}`}
|
|
60
|
+
value={isEnabled}
|
|
61
|
+
onChange={(next) => {
|
|
62
|
+
const nextSet = next
|
|
63
|
+
? [...new Set([...enabledChannels, pageChannel])]
|
|
64
|
+
: enabledChannels.filter(c => c !== pageChannel);
|
|
65
|
+
setEnabledChannels(nextSet);
|
|
66
|
+
setDirtyEnabled(nextSet);
|
|
67
|
+
if (next && enabledChannels.length === 0) {
|
|
68
|
+
setHomeChannel(pageChannel);
|
|
69
|
+
setDirtyHome(pageChannel);
|
|
70
|
+
}
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
73
|
+
<fieldset className="settings-field">
|
|
74
|
+
<legend className="settings-field-label">Home channel (fallback for proactive sends)</legend>
|
|
75
|
+
<div className="settings-active-channel-options" role="radiogroup" aria-label="Home channel">
|
|
76
|
+
{(['telegram', 'discord', 'slack'] as const).map((ch) => (
|
|
77
|
+
<label key={ch} className="settings-radio">
|
|
78
|
+
<input
|
|
79
|
+
type="radio"
|
|
80
|
+
name={`${idPrefix}-homeChannel`}
|
|
81
|
+
checked={homeChannel === ch}
|
|
82
|
+
onChange={() => {
|
|
83
|
+
setHomeChannel(ch);
|
|
84
|
+
setDirtyHome(ch);
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
<span>{capitalize(ch)}</span>
|
|
88
|
+
</label>
|
|
89
|
+
))}
|
|
90
|
+
</div>
|
|
91
|
+
</fieldset>
|
|
92
|
+
</>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function capitalize(value: string): string {
|
|
97
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
98
|
+
}
|