my-durable 0.0.16 → 0.0.18
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/apps/dpi-backend/src/git.ts +163 -0
- package/apps/dpi-backend/src/routes.ts +17 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/assets/index-CcCPNp5E.js +79 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/assets/index-DlEtM_VE.css +1 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/index.html +2 -2
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/AgentView.vue +2 -0
- package/apps/dpi-playground/apps/dpi-playground-fe/src/components/GitChip.vue +184 -0
- package/apps/dpi-sdk/src/client.ts +38 -0
- package/apps/dpi-sdk/src/protocol/rest-routes.ts +2 -0
- package/apps/dpi-tui/src/app.ts +38 -4
- package/apps/dpi-tui/src/commands.ts +69 -0
- package/apps/dpi-tui/src/views.ts +4 -0
- package/package.json +1 -1
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/assets/index-Cg3vey95.css +0 -1
- package/apps/dpi-playground/apps/dpi-playground-fe/dist/assets/index-DEFBWh1G.js +0 -77
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;450;500;550;600;650;700&family=Manrope:wght@400;500;600;650;700;750;800&family=IBM+Plex+Mono:wght@400;500&display=swap";.git-chip-wrap[data-v-68861f3c]{align-items:center;display:inline-flex;position:relative}.git-chip[data-v-68861f3c]{gap:4px}.git-chip .branch[data-v-68861f3c]{text-overflow:ellipsis;white-space:nowrap;max-width:14ch;overflow:hidden}.dirty[data-v-68861f3c]{color:var(--warn,#e5a50a)}.ahead[data-v-68861f3c]{color:var(--ok,#2ea043)}.behind[data-v-68861f3c]{color:var(--info,#58a6ff)}.git-panel[data-v-68861f3c]{z-index:40;background:var(--bg-2,#1c1c22);border:1px solid var(--border,#333);border-radius:8px;min-width:320px;max-width:min(560px,90vw);padding:10px 12px;font-size:12px;position:absolute;top:calc(100% + 6px);left:0;box-shadow:0 8px 24px #0006}.git-panel header[data-v-68861f3c]{justify-content:space-between;align-items:center;gap:8px;display:flex}.git-panel .line[data-v-68861f3c]{margin:4px 0}.git-panel .muted[data-v-68861f3c],.muted[data-v-68861f3c]{opacity:.7}.git-panel .wt-of[data-v-68861f3c]{color:var(--info,#58a6ff)}.git-panel .row[data-v-68861f3c]{flex-wrap:wrap;gap:6px;margin:8px 0;display:flex}.git-panel .row.commit[data-v-68861f3c],.git-panel .row.wt[data-v-68861f3c]{flex-wrap:nowrap}.git-panel input[data-v-68861f3c]{flex:1;min-width:0}.git-panel .wts[data-v-68861f3c]{gap:2px;margin:4px 0;padding:0;list-style:none;display:grid}.git-panel .wts li[data-v-68861f3c]{align-items:center;gap:8px;display:flex}.git-panel .wts code[data-v-68861f3c]{font-size:11px}.git-panel .log[data-v-68861f3c]{background:#00000040;border-radius:6px;max-height:220px;padding:8px;overflow:auto}.tunnel-status{flex-wrap:wrap;align-items:center;gap:.5rem;font-size:.75rem;display:inline-flex}.tunnel-status .axis{border:1px solid var(--border,#333);border-radius:999px;align-items:center;gap:.25rem;padding:.1rem .5rem;display:inline-flex}.tunnel-status .axis.transport{color:var(--fg,#eee)}.tunnel-status .axis.monitor.offline{color:var(--muted,#888)}.tunnel-status .axis.monitor.unauthorized,.tunnel-status .axis.monitor.mismatch,.tunnel-status .axis.monitor.incompatible{color:var(--warn,#e0b040)}.tunnel-status .axis.monitor.online{color:var(--ok,#6cc070)}.tunnel-status .axis.muted{color:var(--muted,#888);border-style:dashed}.tunnel-status .axis.url{color:var(--accent,#7ab8ff);text-decoration:none}.tunnel-status .live{color:var(--ok,#6cc070)}.token-manager{border:1px solid var(--border,#333);border-radius:8px;flex-direction:column;gap:.5rem;padding:.6rem .8rem;font-size:.8rem;display:flex}.token-manager header{align-items:center;gap:.4rem;display:flex}.token-manager h3{text-transform:uppercase;letter-spacing:.08em;margin:0;font-size:.8rem}.token-manager .pairing-status{border:1px solid var(--border,#333);border-radius:999px;margin-left:auto;padding:.05rem .5rem;font-size:.7rem}.token-manager .pairing-status.paired{color:var(--ok,#6cc070)}.token-manager .pairing-status.revoked{color:var(--warn,#e0b040)}.token-manager dl{grid-template-columns:auto 1fr;gap:.15rem .8rem;margin:0;display:grid}.token-manager dt{color:var(--muted,#888)}.token-manager dd{text-overflow:ellipsis;min-width:0;margin:0;overflow:hidden}.token-manager .credential-fingerprint{white-space:normal;overflow-wrap:anywhere}.token-manager .rotation-badge{color:var(--warn,#e0b040);align-items:center;gap:.35rem;margin:0;font-size:.75rem;display:flex}.token-manager .token-actions{flex-wrap:wrap;gap:.5rem;display:flex}.token-manager .spin{animation:1.2s linear infinite tm-spin}@keyframes tm-spin{to{transform:rotate(360deg)}}.device-handoff[data-v-01884b7b]{border:1px solid var(--border);background:var(--panel);border-radius:12px;margin-bottom:14px;padding:14px 16px}header[data-v-01884b7b]{align-items:center;gap:8px;margin-bottom:6px;font-weight:650;display:flex}h3[data-v-01884b7b]{margin:0;font-size:14.5px}.what[data-v-01884b7b]{color:var(--muted);margin:0 0 10px;font-size:13px}code[data-v-01884b7b]{font-family:ui-monospace,monospace;font-size:12px}.row[data-v-01884b7b]{flex-wrap:wrap;align-items:end;gap:8px;display:flex}label[data-v-01884b7b]{color:var(--muted);gap:4px;font-size:12px;display:grid}input[data-v-01884b7b]{border:1px solid var(--border);background:var(--panel);width:90px;color:inherit;font:inherit;border-radius:8px;padding:7px 9px}.err[data-v-01884b7b]{color:var(--danger,#f28b82);margin:8px 0 0;font-size:12.5px}.target[data-v-01884b7b]{color:var(--muted);word-break:break-all;margin:10px 0 0;font-family:ui-monospace,monospace;font-size:12px}.pair-wizard .pair-steps{color:var(--muted,#888);gap:.5rem;margin:.25rem 0 .75rem;padding:0;font-size:.78rem;list-style:none;display:flex}.pair-wizard .pair-steps li{border:1px solid #0000;border-radius:999px;padding:.15rem .6rem}.pair-wizard .pair-steps li.on{color:var(--fg,#eee);border-color:currentColor}.pair-wizard .pair-grid{grid-template-columns:auto 1fr;align-items:start;gap:1rem;display:grid}.pair-wizard .pair-code{letter-spacing:.18em;font-size:1.7rem;font-family:var(--mono,monospace);margin-bottom:.35rem}.pair-wizard .pair-copy-actions{flex-wrap:wrap;gap:.4rem;display:flex}.pair-wizard .pair-qr svg{border:1px solid var(--border,#333);border-radius:6px}.pair-wizard .pair-side{flex-direction:column;gap:.6rem;min-width:0;display:flex}.pair-wizard .fp{word-break:break-all;background:var(--bg2,#222);border-radius:6px;padding:.4rem;font-size:.78rem;display:block}.pair-wizard .pair-confirm{align-items:center;gap:.45rem;font-size:.85rem;display:flex}.pair-wizard .pair-confirm.warn{color:var(--warn,#e0b040)}.pair-wizard .spin{animation:1.2s linear infinite pair-spin}@keyframes pair-spin{to{transform:rotate(360deg)}}.pair-pending{border:1px solid #2a3442;border-radius:12px;margin:0 0 18px;padding:14px 16px}.pair-pending h3{margin:0 0 10px;font-size:14px}.pair-pending ul{gap:10px;margin:0;padding:0;list-style:none;display:grid}.pair-pending li{border-bottom:1px solid #222b36;gap:4px;padding-bottom:10px;display:grid}.pair-pending li:last-child{border-bottom:0;padding-bottom:0}.pair-pending .who{flex-wrap:wrap;align-items:baseline;gap:8px;display:flex}.pair-pending .fp{word-break:break-all;color:#7ee2a8;font-family:ui-monospace,monospace;font-size:12px}.pair-pending .row{justify-content:space-between;align-items:center;gap:10px;display:flex}.pair-pending .phase{color:#8fa0b5;font-size:12px}:root{font-family:var(--font-body);color:var(--fg);background:var(--bg);font-synthesis:none;--bg:#151010;--tree:#181213;--chat:#161011;--panel:#1c1516;--card:#251a1b;--line-soft:#2a2021;--fg:#f2eeee;--faint:#8d7778;--primary:#b9283c;--primary-line:#df5363;--primary-hover:#cf354a;--on-primary:#fff;--ok:#6fcf97;--warn:#f2c94c;--err:#ff6b6b;--offline:#5a4a4b;--font-body:"DM Sans", "Segoe UI", sans-serif;--font-display:"Manrope", "Segoe UI", sans-serif;--font-mono:"IBM Plex Mono", ui-monospace, Menlo, monospace;--topbar-h:65px;--tree-w:300px;--insp-w:450px;--radius:10px;--radius-sm:6px;--ease:cubic-bezier(.2, .7, .3, 1);--secondary:#c9a87c;--muted:var(--secondary);--line:#332829;--accent:#ff8997;--surface:#1e1718;--font-title:var(--font-display);font-size:16px}*{box-sizing:border-box}body{margin:0}button,input,textarea,select{font:inherit}button{cursor:pointer}button,a,input,textarea,select{-webkit-tap-highlight-color:transparent}button{color:inherit;background:0 0;border:0;justify-content:center;align-items:center;gap:7px;display:inline-flex}button:disabled{opacity:.8;cursor:not-allowed}button:not(:disabled):hover{filter:brightness(1.13)}button:focus-visible,summary:focus-visible{outline:2px solid var(--accent);outline-offset:3px}input,textarea,select{color:inherit;background:var(--chat);border:1px solid var(--line);border-radius:7px;outline:none;min-width:0;padding:10px 12px}input::placeholder,textarea::placeholder{color:var(--fg)}textarea{resize:vertical;line-height:1.6}select{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;cursor:pointer}h1,h2,h3,p{margin:0}h1,h2,h3{font-family:var(--font-title)}h2{font-size:16px;font-weight:650}pre,code{font-family:var(--font-mono)}pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:16px;line-height:1.7}small{font-size:16px}svg{flex-shrink:0}.shell{flex-direction:column;height:100dvh;display:flex;overflow:hidden}.topbar{height:var(--topbar-h);min-height:var(--topbar-h);border-bottom:1px solid var(--line);background:var(--panel);z-index:10;justify-content:space-between;align-items:center;padding:0 26px 0 17px;display:flex}.brand,.top-actions{align-items:center;gap:14px;display:flex}.brand strong{letter-spacing:-.5px;font-size:16px;font-family:var(--font-title);font-weight:750}.brandmark{flex-shrink:0;width:45px;height:25px;margin-left:3px;display:block}.brand-separator{color:var(--fg);margin:0 2px;font-weight:400}.muted{color:var(--muted)}.icon-button{width:29px;height:29px;color:var(--muted);border-radius:5px;padding:4px}.icon-button:hover{color:var(--fg);background:#ffffff08}.primary{background:var(--accent);color:var(--fg);border:1px solid var(--accent);white-space:nowrap;border-radius:7px;padding:12px 18px;font-weight:650;box-shadow:0 1px 2px #0002}.primary.small{padding:8px 12px;font-size:16px}.secondary{background:#2b2021;border:1px solid #443738;border-radius:6px;padding:8px 12px;font-size:16px;font-weight:500}.connection{color:var(--muted);align-items:center;gap:7px;font-size:16px;display:flex}.connection i,.live-dot{background:#756062;border-radius:100%;flex-shrink:0;width:5px;height:5px;display:inline-block}.connection .online,.live-dot{background:var(--accent);box-shadow:0 0 7px #cdadb018}.version{font-family:var(--font-mono);color:var(--fg);border:1px solid #3b3031;border-radius:4px;padding:4px 6px;font-size:16px}.workspace{flex:1;grid-template-columns:minmax(340px,34%) minmax(0,1fr);min-height:0;display:grid}.inspector{border-right:1px solid var(--line);background:var(--panel);flex-direction:column;min-height:0;display:flex}.session-heading{padding:25px 26px 21px}.eyebrow{letter-spacing:1.8px;color:var(--fg);font-size:16px;font-weight:600}.session-heading h1{color:var(--fg);letter-spacing:-.7px;overflow-wrap:anywhere;justify-content:space-between;align-items:center;gap:10px;margin:0 0 10px;font-size:16px;font-weight:650;display:flex}.path{font-family:var(--font-mono);color:var(--fg);overflow-wrap:anywhere;align-items:center;gap:7px;font-size:16px;display:flex}.session-status{align-items:center;gap:9px;margin-top:19px;display:flex}.status-pill{color:var(--fg);background:#352728;border-radius:4px;align-items:center;gap:6px;padding:4px 8px;font-size:16px;line-height:1.3;display:flex}.status-pill i{background:currentColor;border-radius:50%;width:4px;height:4px}.status-pill.working{color:var(--accent);background:#412b2d}.working i{animation:1.5s infinite pulse}.saved{color:var(--fg);align-items:center;gap:5px;margin-left:auto;font-size:16px;display:flex}.background-pill{color:var(--fg);font-size:16px}.model-section{border-top:1px solid var(--line);padding:18px 26px 22px}.section-label{letter-spacing:1.3px;color:var(--fg);justify-content:space-between;margin-bottom:13px;font-size:16px;display:flex}.section-label span+span{letter-spacing:0;color:var(--fg)}.model-button{text-align:left;background:#251c1d;border:1px solid #423435;border-radius:8px;width:100%;padding:11px 13px}.model-button img,.model-list img{object-fit:contain;background:#f0e8e9;border-radius:6px;width:29px;height:29px;padding:3px}.model-button>div{flex-direction:column;flex:1;gap:3px;min-width:0;display:flex}.model-button strong{text-overflow:ellipsis;white-space:nowrap;font-size:16px;overflow:hidden}.model-button small{color:var(--muted);font-size:16px}.thinking-row{color:var(--fg);justify-content:space-between;align-items:center;margin-top:10px;font-size:16px;display:flex}.thinking-row select{color:var(--fg);background:0 0;border:0;padding:4px 2px;font-size:16px}.metrics{grid-template-columns:1fr 1.3fr .5fr;margin-top:21px;display:grid}.metrics>div{flex-direction:column;gap:5px;display:flex}.metrics span{font-family:var(--font-title);letter-spacing:-.5px;font-size:19px;font-weight:500}.metrics small{color:var(--fg);font-size:16px}.stats-section{margin-top:24px}.stats-section .metrics{margin-top:14px}.usage-breakdown{border-collapse:collapse;font-variant-numeric:tabular-nums;width:100%;margin-top:22px;font-size:16px}.usage-breakdown th,.usage-breakdown td{text-align:right;border-bottom:1px solid var(--line);padding:10px 0;font-weight:400}.usage-breakdown th{color:var(--secondary)}.usage-breakdown th:first-child{text-align:left}.usage-breakdown td{color:var(--fg)}.inspector-tabs{border-top:1px solid var(--line);border-bottom:1px solid var(--line);background:var(--tree);padding:0 16px;display:flex}.inspector-tabs button{color:var(--fg);border-bottom:2px solid #0000;flex:1;gap:6px;padding:15px 3px 13px;font-size:16px}.inspector-tabs button.active{color:var(--accent);border-bottom-color:var(--accent)}.count{background:#462f31;border-radius:4px;padding:1px 4px;font-size:16px}.inspector-body{scrollbar-width:thin;scrollbar-color:var(--line) transparent;flex:1;padding:23px 26px 32px;overflow:auto}.panel-heading{justify-content:space-between;align-items:center;gap:9px;display:flex}.panel-heading svg{color:var(--fg)}.tag{color:var(--fg);white-space:nowrap;background:#302526;border:1px solid #403536;border-radius:4px;padding:3px 5px;font-size:16px}.tag.completed{color:var(--accent)}.tag.waiting,.tag.aborted,.tag.rejected{color:var(--fg)}.hint{color:var(--fg);margin:8px 0 18px;font-size:16px;line-height:1.65}.field-label{letter-spacing:1px;color:var(--fg);margin-bottom:8px;font-size:16px;display:block}.inline-input{background:#181112;border:1px solid #3a2d2e;border-radius:7px;align-items:center;display:flex;overflow:hidden}.inline-input input{width:100%;box-shadow:none;background:0 0;border:0;padding:10px;font-size:16px}.inline-input .icon-button{margin-right:4px}.counter-card{background:#261b1c;border:1px solid #403031;border-radius:8px;justify-content:space-between;align-items:center;margin:16px 0;padding:17px 15px;display:flex}.app-state-json{white-space:pre-wrap;overflow-wrap:anywhere;border:1px solid var(--line);background:#140e10;border-radius:8px;max-height:300px;margin:16px 0 24px;padding:16px;font-size:16px;line-height:1.6;overflow:auto}.state-field{min-width:0}.state-field:not(.state-root){border:1px solid var(--line);border-radius:8px;margin:10px 0;padding:12px}.state-field .state-field{background:#25191c40}.state-field-heading{align-items:center;gap:8px;margin-bottom:12px;display:flex}.state-field-heading strong{overflow-wrap:anywhere;flex:1;min-width:0;font-size:16px}.state-field-heading select{width:auto;max-width:120px;color:var(--secondary);padding:5px}.state-field-heading .icon-button{flex-shrink:0}.state-field textarea,.state-field input{width:100%;min-width:0;font-size:16px}.state-number{align-items:center;gap:8px;display:flex}.state-number input{text-align:center;appearance:textfield}.state-number input::-webkit-inner-spin-button{appearance:none}.state-number input::-webkit-outer-spin-button{appearance:none}.state-number button{flex-shrink:0;padding:10px}.state-boolean{align-items:center;gap:12px;display:flex}.state-boolean>span{color:var(--secondary)}.state-add{flex-wrap:wrap;gap:8px;margin-top:12px;display:flex}.state-add input{flex-basis:100%}.state-add select{flex:1;width:auto}.state-add-button{margin-top:8px}.counter-card .field-label{margin:0;font-size:16px}.counter-card strong{font-family:var(--font-title);color:var(--accent);margin-top:4px;font-size:32px;font-weight:500;line-height:1.1;display:block}.counter-card .secondary{color:var(--fg);background:#392a2b;border-color:#5a3c3f;padding:7px 9px;font-size:16px}.note{color:var(--fg);overflow-wrap:anywhere;align-items:flex-start;gap:8px;margin:0 0 10px;font-size:16px;line-height:1.6;display:flex}.note>span{background:#836669;border-radius:50%;flex-shrink:0;width:4px;height:4px;margin-top:7px}.notes{margin-bottom:14px}.raw{border-top:1px solid var(--line);margin-top:16px;padding-top:12px}.raw summary{cursor:pointer;color:var(--fg);font-family:var(--font-mono);align-items:center;gap:7px;font-size:16px;list-style:none;display:flex}.raw summary:after{content:"+";color:var(--fg);margin-left:auto}.raw[open] summary:after{content:"−"}.raw summary span{color:var(--fg);margin-left:auto;font-size:16px}.raw pre{background:var(--chat);color:var(--fg);border:1px solid #35292a;border-radius:6px;max-height:340px;padding:11px;overflow:auto}.raw textarea{width:100%;font-size:16px}.raw button{margin-top:8px}.section-divider{border-top:1px solid var(--line);margin:25px 0 22px}.demo-action{text-align:left;border-bottom:1px solid #362829;align-items:center;gap:11px;width:100%;padding:12px 0;display:flex}.demo-action>span:nth-child(2){flex-direction:column;flex:1;gap:4px;display:flex}.demo-action strong{font-size:16px;font-weight:500}.demo-action small{color:var(--fg);font-size:16px}.demo-action>svg{color:var(--fg)}.demo-icon{width:32px;height:32px;color:var(--accent);background:#3b292b;border-radius:7px;justify-content:center;align-items:center;display:flex}.demo-icon.lavender{color:var(--fg);background:#3b292b}.job-card{background:#26191a;border:1px solid #453536;border-radius:7px;margin-top:16px;padding:13px}.job-title{justify-content:space-between;align-items:center;gap:5px;display:flex}.job-title strong{font-size:16px;font-weight:500}.progress{background:#403031;border-radius:4px;height:3px;margin:14px 0 8px;overflow:hidden}.progress>div{background:var(--accent);height:100%;transition:width .3s}.job-meta{font-family:var(--font-mono);color:var(--fg);justify-content:space-between;gap:6px;font-size:16px;display:flex}.approval{flex-wrap:wrap;gap:7px;margin-top:15px;display:flex}.approval>span{width:100%;color:var(--fg);margin-bottom:3px;font-size:16px}.approval button{padding:6px 10px;font-size:16px}.text-button{color:var(--accent);margin-top:12px;padding:0;font-size:16px}.danger{color:var(--fg)}.inspector-footer{height:39px;min-height:39px;color:var(--fg);border-top:1px solid #372728;align-items:center;gap:8px;padding:0 22px;font-size:16px;display:flex}.inspector-footer .icon-button{margin-left:auto}.chat-panel{background:var(--chat);flex-direction:column;min-height:0;display:flex;position:relative}.agent-stream-status{border-bottom:1px solid var(--warn);color:var(--warn);flex-wrap:wrap;align-items:center;gap:12px;padding:12px 24px;display:flex}.agent-stream-status>div{overflow-wrap:anywhere;flex:220px;min-width:0}.agent-stream-status p{margin:4px 0 0;font-size:13px}.chat-header{border-bottom:1px solid var(--line);background:#1c1415;justify-content:space-between;align-items:center;min-height:63px;padding:0 25px;display:flex}.conversation-select{color:var(--fg);align-items:center;gap:9px;min-width:0;display:flex}.conversation-select select{box-shadow:none;color:var(--fg);background:0 0;border:0;max-width:230px;padding:8px 3px;font-size:16px}.fork-label{white-space:nowrap;color:var(--fg);align-items:center;gap:3px;font-size:16px;display:flex}.chat-header-actions{align-items:center;gap:8px;display:flex}.relative{position:relative}.dropdown{z-index:20;background:var(--line-soft);border:1px solid #4b3b3c;border-radius:8px;min-width:205px;padding:6px;position:absolute;top:36px;right:0;box-shadow:0 10px 35px #0005}.dropdown button{width:100%;color:var(--fg);border-radius:5px;justify-content:flex-start;padding:10px 9px;font-size:16px;display:flex}.dropdown button:hover{background:#3c2d2e}.chat-scroll{scrollbar-width:thin;scrollbar-color:var(--line) transparent;scroll-behavior:smooth;flex:1;padding:32px clamp(24px,5vw,88px) 20px;overflow:auto}.chat-empty{text-align:center;flex-direction:column;justify-content:center;align-items:center;min-height:100%;padding:30px 0 48px;display:flex}.agent-orb{background:radial-gradient(at top,#492d2f,#281a1b);border:1px solid #503a3c;border-radius:17px;justify-content:center;align-items:center;width:58px;height:58px;margin-bottom:26px;display:flex;position:relative;box-shadow:0 10px 50px #b07d820c}.agent-orb>img{width:45px;height:25px}.agent-orb>i{border:3px solid var(--chat);background:var(--accent);border-radius:50%;width:11px;height:11px;position:absolute;bottom:-1px;right:-2px}.chat-empty .eyebrow{letter-spacing:2.1px;font-size:16px}.chat-empty h2{letter-spacing:-.8px;margin:11px 0;font-size:27px;font-weight:500}.chat-empty p{color:var(--fg);font-size:16px;line-height:1.7}.starter-grid{grid-template-columns:1fr 1fr;gap:13px;width:100%;max-width:470px;margin-top:34px;display:grid}.starter-grid button{text-align:left;background:var(--tree);border:1px solid #3c2c2d;border-radius:8px;flex-direction:column;align-items:flex-start;gap:6px;padding:18px;display:flex;position:relative}.starter-grid button>svg:first-child{color:var(--fg);margin-bottom:13px}.starter-grid strong{font-size:16px;font-weight:500}.starter-grid span{color:var(--fg);font-size:16px}.starter-grid button>svg:last-child{color:var(--fg);position:absolute;top:16px;right:13px}.composer-region{background:linear-gradient(transparent, var(--chat) 15%);padding:18px clamp(20px,3vw,46px) 16px}.composer{background:#281c1d;border:1px solid #513d3f;border-radius:11px;box-shadow:0 6px 30px #0002}.composer textarea{resize:none;width:100%;min-height:80px;max-height:220px;box-shadow:none;background:0 0;border:0;padding:18px 20px 9px;font-size:16px;display:block}.composer-toolbar{justify-content:space-between;align-items:center;gap:7px;padding:9px 11px 12px 17px;display:flex}.composer-left,.composer-right{align-items:center;gap:12px;display:flex}.context-indicator{color:var(--fg);align-items:center;gap:5px;font-size:16px;display:flex}.composer-left select{color:var(--fg);background:0 0;border:0;padding:4px;font-size:16px}.interrupt{color:var(--fg);background:#452b2d24;border:1px solid #66414460;border-radius:5px;padding:6px 8px;font-size:16px}.send{background:var(--accent);width:33px;height:33px;color:var(--fg);border-radius:7px}.composer-footnote{color:var(--fg);justify-content:space-between;align-items:center;gap:12px;margin-top:11px;font-size:16px;display:flex}.composer-footnote>span:first-child{align-items:center;gap:6px;display:flex}.composer-footnote .live-dot{width:4px;height:4px}.entry{margin-bottom:26px;position:relative}.message-label{color:var(--fg);letter-spacing:1.3px;align-items:center;gap:8px;margin-bottom:10px;font-size:16px;display:flex}.message-label>span:not(.mini-pi):not(.streaming){font-family:var(--font-mono);letter-spacing:0;color:var(--fg);font-size:16px}.user-message{white-space:pre-wrap;color:var(--fg);font-size:16px;line-height:1.8}.pi-user{border-bottom:1px solid #392628;padding-bottom:20px}.mini-pi{flex-shrink:0;width:36px;height:20px}.markdown{color:var(--fg);overflow-wrap:anywhere;font-size:16px;line-height:1.85}.markdown p{margin:0 0 14px}.markdown p:last-child{margin-bottom:0}.markdown h1,.markdown h2,.markdown h3{color:var(--fg);margin:22px 0 12px;font-size:16px}.markdown ul,.markdown ol{margin:12px 0;padding-left:23px}.markdown li{margin:6px 0}.markdown strong{color:var(--fg);font-weight:600}.markdown code{color:var(--fg);background:#3a2628;border-radius:4px;padding:2px 5px;font-size:16px}.markdown pre{white-space:pre;background:#251819;border:1px solid #3d2b2d;border-radius:7px;margin:14px 0;padding:16px;overflow:auto}.markdown pre code{background:0 0;padding:0}.markdown a{color:var(--accent)}.markdown table{border-collapse:collapse;width:100%}.markdown td,.markdown th{border:1px solid var(--line);text-align:left;padding:8px}.fork-button{color:var(--fg);letter-spacing:0;opacity:1;white-space:nowrap;margin-left:auto;padding:3px 5px;font-size:16px;display:inline-flex}.entry:hover .fork-button,.fork-button:focus-visible{opacity:1}.event-line{color:var(--fg);align-items:center;gap:7px;padding:8px 0;font-size:16px;display:flex}.event-line>span{flex:1}.pi-tool-result{margin-top:-17px;margin-bottom:20px}.tool-call,.tool-result{color:var(--fg);background:#251819;border:1px solid #453032;border-radius:6px;margin:10px 0;font-size:16px;overflow:hidden}.tool-call summary,.tool-result summary{cursor:pointer;align-items:center;gap:9px;padding:10px 12px;list-style:none;display:flex}.tool-call summary:after,.tool-result summary:after{content:"⌄";color:var(--fg);margin-left:auto}.tool-call summary code{white-space:nowrap;text-overflow:ellipsis;color:var(--fg);font-size:16px;overflow:hidden}.tool-call pre,.tool-result pre{color:var(--fg);border-top:1px solid #412b2d;max-height:340px;margin:0;padding:11px;overflow:auto}.tool-result{background:#201415;border-color:#3b2b2c}.tool-result summary>span{color:var(--fg);font-size:16px}.tool-result.failed{color:var(--fg);border-color:#673f43}.tool-call.failed{border-color:#a44c58}.tool-status{color:var(--secondary);margin-left:auto}.tool-section-label{color:var(--secondary);border-top:1px solid #412b2d;align-items:center;gap:12px;padding:10px 12px;display:flex}.tool-section-label .fork-button{margin-left:auto}.thinking{color:var(--fg);margin:8px 0 16px;font-size:16px}.thinking summary{cursor:pointer}.thinking div{white-space:pre-wrap;color:var(--fg);border-left:1px solid #5a3a3d;margin-top:12px;padding-left:12px;font-size:16px;line-height:1.8}.message-error{color:var(--fg);overflow-wrap:anywhere;background:#39232550;border:1px solid #6b4245;border-radius:6px;margin-top:10px;padding:10px;font-size:16px;line-height:1.7}.streaming{letter-spacing:0;color:var(--accent);margin-left:auto;font-size:16px}.typing{gap:5px;padding:12px 0;display:flex}.typing i{background:#af7c81;border-radius:50%;width:5px;height:5px;animation:1.2s infinite pulse}.typing i:nth-child(2){animation-delay:.2s}.typing i:nth-child(3){animation-delay:.4s}.live-tool{background:#2c1d1e;border:1px solid #624346;border-radius:6px;margin:10px 0;padding:11px}.live-tool>div{color:var(--fg);align-items:center;gap:8px;font-size:16px;display:flex}.live-tool>div>span{color:var(--fg);margin-left:auto;font-size:16px}.live-tool pre{max-height:150px;color:var(--fg);overflow:auto}.queue{background:#2e1f20;border:1px solid #4a3537;border-radius:7px;margin-bottom:10px;padding:5px 10px}.queue>div{color:var(--fg);align-items:center;gap:7px;font-size:16px;display:flex}.queue span{white-space:nowrap;text-overflow:ellipsis;flex:1;overflow:hidden}.history-banner{color:var(--fg);background:#33292a33;align-items:center;gap:7px;padding:8px 26px;font-size:16px;display:flex}.empty-panel{color:var(--fg);flex-direction:column;align-items:center;gap:12px;padding:30px 0;font-size:16px;display:flex}.empty-panel strong{color:var(--fg);font-weight:500}.task-card{background:#2a1b1c;border:1px solid #4d3537;border-radius:7px;margin:10px 0;padding:13px}.task-card.child{border-left:2px solid #84595d;margin-left:14px}.task-card>div:first-child{justify-content:space-between;align-items:center;gap:6px;display:flex}.task-card strong{font-family:var(--font-mono);overflow-wrap:anywhere;font-size:16px;font-weight:400}.task-card p{color:var(--fg);margin:8px 0 11px;font-size:16px}.hook-card{background:#2a2525;border:1px solid #483c3d;border-radius:7px;margin-bottom:20px;padding:14px}.hook-card>div{color:var(--fg);align-items:center;gap:8px;display:flex}.hook-card strong{font-size:16px;font-weight:500}.hook-card p{color:var(--fg);margin-top:10px;font-size:16px;line-height:1.6}.switch{background:#544b4c;border-radius:12px;justify-content:flex-start;width:29px;height:17px;margin-left:auto;padding:2px}.switch>span{background:#dbcccd;border-radius:50%;width:13px;height:13px}.switch.on{background:var(--primary);justify-content:flex-end}.tool-row{border-bottom:1px solid #3a2a2b;padding:12px 0}.tool-row summary{cursor:pointer;color:var(--fg);align-items:center;gap:7px;list-style:none;display:flex}.tool-row strong{font-family:var(--font-mono);font-size:16px;font-weight:500}.tool-row .tag{margin-left:auto;font-size:16px}.tool-row p{color:var(--fg);white-space:pre-wrap;margin:12px 0;font-size:16px;line-height:1.75}.tool-row small{color:var(--fg);line-height:1.6}.tool-signature{border:1px solid var(--line);font-family:var(--font-mono);white-space:pre-wrap;overflow-wrap:anywhere;color:var(--fg);background:#120d0e;border-radius:8px;margin:0 0 12px;padding:12px 14px;font-size:14px;line-height:1.6}.tk-keyword{color:var(--accent)}.tk-function{color:#ffd38a}.tk-property{color:#9ed8cf}.tk-type{color:#8fb6ff}.tk-string{color:#e6b98f}.tk-number{color:#d4a5ff}.tk-comment{color:var(--faint);font-style:italic}.tk-punct{color:#b8a6a7}.prompt-chip{width:100%;color:var(--fg);text-align:left;border-bottom:1px solid var(--line);justify-content:space-between;padding:14px 0;font-size:16px;display:flex}.drawer-backdrop{z-index:40;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);background:#0007;position:fixed;inset:65px 0 0}.sessions-drawer{background:var(--card);border-right:1px solid #513d3f;flex-direction:column;width:326px;max-width:90vw;height:100%;padding:24px 18px;display:flex;box-shadow:20px 0 70px #0004}.sessions-drawer header{justify-content:space-between;align-items:center;margin-bottom:22px;padding:0 7px;display:flex}.sessions-drawer h2{font-size:17px;font-weight:550}.sessions-drawer h2 span{color:var(--fg);margin-left:6px;font-size:16px}.sessions-drawer>.primary{margin:0 6px 22px}.session-list{flex:1;overflow:auto}.session-item{text-align:left;border:1px solid #0000;border-radius:7px;flex-direction:column;align-items:stretch;gap:10px;width:100%;margin-bottom:7px;padding:15px;display:flex}.session-item.active{background:#362527;border-color:#5e3e41}.session-item:hover{background:#312324}.session-item strong{justify-content:space-between;align-items:center;font-size:16px;font-weight:550;display:flex}.session-item .path{font-size:16px}.session-model{color:var(--fg);overflow-wrap:anywhere;align-items:center;gap:6px;font-size:16px;display:flex}.session-model img{background:#ecdfe0;border-radius:3px;width:15px;height:15px;padding:1px}.active-dot{background:var(--accent);border-radius:50%;width:5px;height:5px}.sessions-drawer footer{color:var(--fg);align-items:center;gap:8px;padding:18px 8px 0;font-size:16px;display:flex}.modal-backdrop{z-index:80;-webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px);background:#0d0808bd;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}.modal{background:var(--panel);border:1px solid var(--line);border-radius:12px;width:460px;max-width:100%;max-height:90dvh;padding:28px;overflow:auto;box-shadow:0 24px 70px #0000008c}.modal header{justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:27px;display:flex}.modal header h2{letter-spacing:-.5px;margin-top:7px;font-size:22px;font-weight:550}.modal label{color:var(--fg);flex-direction:column;gap:9px;margin-top:20px;font-size:16px;display:flex}.modal input,.modal select{width:100%;padding:12px;font-size:16px}.modal small.path{color:var(--fg);margin-top:-1px;font-size:16px;line-height:1.6}.modal footer{justify-content:flex-end;gap:10px;margin-top:26px;display:flex}.model-modal{flex-direction:column;width:500px;display:flex;overflow:hidden}.login-veil{z-index:100;-webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px);background:#0d0808e0;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}.login-card{width:400px}.login-card .hint{color:var(--fg);font-size:15px;line-height:1.55}.login-card footer{justify-content:flex-end}.model-list{max-height:50vh;margin-top:12px;overflow:auto}.model-list button{text-align:left;border-radius:6px;justify-content:flex-start;gap:12px;width:100%;padding:12px 10px;display:flex}.model-list button.selected,.model-list button:hover{background:#402b2d}.model-list button>div{flex-direction:column;flex:1;gap:5px;display:flex}.model-list strong{font-size:16px;font-weight:500}.model-list small{color:var(--fg);font-size:16px}.model-modal .hint{margin:14px 0 0}.learning-list{flex-direction:column;gap:23px;padding:0;list-style:none;display:flex}.learning-list li{gap:17px;display:flex}.learning-list li>span{font-family:var(--font-mono);color:var(--fg);border:1px solid #5a383b;border-radius:5px;flex-shrink:0;justify-content:center;align-items:center;width:28px;height:27px;font-size:16px;display:flex}.learning-list strong{color:var(--fg);font-size:16px;font-weight:500}.learning-list p{color:var(--fg);margin-top:5px;font-size:16px;line-height:1.8}.help-modal{width:530px}.toast{z-index:100;background:var(--card);color:var(--on-primary);border:1px solid var(--line);border-radius:999px;align-items:center;gap:10px;max-width:90%;padding:13px 19px;font-size:16px;font-weight:600;line-height:1.5;display:flex;position:fixed;bottom:28px;left:50%;transform:translate(-50%);box-shadow:0 10px 30px #00000073}.toast svg{color:var(--accent);flex-shrink:0}.pi-user .message-label{justify-content:flex-end}.pi-user .user-message{text-align:right}.state-json-actions{flex-wrap:wrap;justify-content:flex-end;gap:10px;margin-bottom:20px;display:flex}.state-json-actions .hint{flex-basis:100%;margin:0}textarea.app-state-json{resize:vertical;width:100%;min-height:280px;max-height:none;font-family:var(--font-mono);color:var(--fg);margin-bottom:10px}.toast-enter-active,.toast-leave-active{transition:opacity .2s}.toast-enter-from,.toast-leave-to{opacity:0}.error-banner{color:var(--fg);z-index:90;background:#462b2d;align-items:center;gap:10px;padding:10px 25px;font-size:16px;display:flex}.error-banner span{flex:1}.welcome{text-align:center;background:radial-gradient(#39232533,#0000 65%);flex-direction:column;flex:1;justify-content:center;align-items:center;padding:35px;display:flex}.welcome-symbol{width:126px;height:70px;margin-bottom:33px;position:relative}.welcome .eyebrow{color:var(--fg);letter-spacing:2.6px;font-size:16px}.welcome h1{letter-spacing:-2.5px;color:var(--fg);margin:20px 0;font-size:clamp(36px,4.6vw,64px);font-weight:500;line-height:1.15}.welcome p{color:var(--fg);margin:4px 0 30px;font-size:16px}.welcome-features{color:var(--fg);gap:33px;margin-top:70px;font-size:16px;display:flex}.welcome-features>span{align-items:center;gap:8px;display:flex}.selected{color:var(--accent)}@keyframes pulse{0%,to{opacity:.45}50%{opacity:1}}@media (width>=1600px){.workspace{grid-template-columns:440px minmax(0,1fr)}.chat-scroll{padding-left:max(70px,50% - 385px);padding-right:max(70px,50% - 385px)}.composer-region{padding-left:max(46px,50% - 415px);padding-right:max(46px,50% - 415px)}}@media (width<=1050px){.workspace{grid-template-columns:330px minmax(0,1fr)}.session-heading,.model-section,.inspector-body{padding-left:20px;padding-right:20px}.chat-empty h2{font-size:23px}.composer-footnote>span:last-child{display:none}.starter-grid{grid-template-columns:1fr;max-width:300px}.starter-grid button{padding:13px}.starter-grid button>svg:first-child{margin-bottom:3px}.topbar{padding-right:16px}}@media (width<=740px){.shell{height:auto;min-height:100dvh;overflow:visible}.topbar{height:58px;min-height:58px;padding:0 12px;position:sticky;top:0}.brand{gap:8px}.brand strong{font-size:16px}.brand-separator,.brand>.muted,.version,.connection{display:none}.top-actions{gap:5px}.workspace{flex-direction:column;display:flex}.inspector{border-right:0;border-bottom:1px solid var(--line);min-height:310px;max-height:55vh}.session-heading{padding:17px 20px 10px}.session-heading h1{margin:5px 0;font-size:19px}.session-heading>.eyebrow,.session-status,.model-section,.inspector-footer{display:none}.inspector-tabs{padding:0 13px}.inspector-tabs button{padding:11px 3px}.inspector-body{min-height:150px;padding:17px 20px}.chat-panel{height:85dvh;min-height:500px}.chat-header{min-height:55px;padding:0 13px}.chat-scroll{padding:24px 22px}.chat-empty h2{font-size:22px}.composer-region{padding:12px}.composer-left{gap:3px}.composer-left .context-indicator{display:none}.composer-right{gap:8px}.modal{padding:22px}.welcome{min-height:90dvh}.welcome-features{flex-direction:column;align-items:center;gap:20px;margin-top:40px}.drawer-backdrop{top:58px}.toast{width:max-content;max-width:90%;font-size:16px;bottom:15px}.fork-label{display:none}}@media (prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important;animation:none!important}}@media (width<=740px){.model-section{padding:9px 20px;display:block}.model-section .section-label,.model-section .thinking-row,.model-section .metrics{display:none}.model-button{padding:7px 10px}.model-button img{width:24px;height:24px}.model-button small{display:none}}.primary,.send{background:var(--primary);border-color:var(--primary-line);color:#fff}.primary:hover,.send:hover{background:var(--primary-hover)}.model-button img,.model-list img{background:#fff;border:1px solid #67575c;flex-shrink:0;width:42px;height:42px;padding:5px}.session-model img{background:#fff;flex-shrink:0;width:32px;height:32px;padding:3px}img[src$=openai\.png]{background:#242024;border-color:#81747a;padding:3px}button:disabled{opacity:1;color:#d4c7ca;background-color:#33272b;border-color:#554249}.icon-button{width:36px;height:36px;color:var(--fg)}.inspector-tabs{padding:0 10px}.inspector-tabs button{min-width:0;padding:15px 7px 13px}.session-heading h1{line-height:1.4}.session-status,.section-label,.panel-heading,.job-title,.job-meta,.composer-footnote,.composer-toolbar{flex-wrap:wrap}.metrics{grid-template-columns:1fr 1.4fr .6fr;gap:12px}.metrics small{line-height:1.5}.model-button{min-height:70px}.model-button small{line-height:1.5}.model-button strong{white-space:normal}.inspector-footer{height:auto;min-height:52px;padding:8px 20px}.counter-card{flex-wrap:wrap;gap:12px}.counter-card .field-label{max-width:180px;line-height:1.5}.raw summary{flex-wrap:wrap;line-height:1.6}.raw summary span{color:var(--fg)}.tool-row summary{flex-wrap:wrap}.tool-row .tag{margin-left:auto}.tool-call summary{flex-wrap:wrap}.tool-call summary code{max-width:100%}.fork-button{opacity:1;color:#ffd3da}.session-item .path{line-height:1.6}.sessions-drawer{width:420px}.modal{width:560px}.model-modal{width:650px}.model-list button{min-height:78px}.model-list small{line-height:1.5}.task-card>div:first-child{flex-wrap:wrap}.composer-footnote{line-height:1.6}.composer-left,.composer-right{flex-wrap:wrap;gap:8px}.send{width:40px;height:40px}.interrupt{min-height:40px}.tag{line-height:1.4}.brand{gap:10px}.welcome-features{flex-wrap:wrap;justify-content:center}@media (width>=741px) and (width<=1599px){.workspace{grid-template-columns:minmax(410px,36%) minmax(0,1fr)}.chat-scroll{padding-left:30px;padding-right:30px}.composer-footnote>span:last-child{display:none}.chat-header{flex-wrap:wrap;gap:10px;padding:10px 18px}}@media (width<=1100px) and (width>=741px){.brand>.muted,.brand-separator,.version{display:none}.starter-grid{grid-template-columns:1fr}.composer-left .context-indicator{display:none}.conversation-select select{max-width:205px}}@media (width<=740px){.inspector{max-height:60vh}.topbar{height:68px;min-height:68px}.top-actions .icon-button{display:none}.brand strong{font-size:16px}.model-button img{width:40px;height:40px}.model-button small{display:block}.model-button{min-height:64px}.inspector-tabs{padding:0 4px}.inspector-tabs button{gap:4px;padding:12px 4px}.inspector-body{padding:18px 16px}.conversation-select{gap:6px;max-width:calc(100% - 94px)}.conversation-select select{width:100%;max-width:180px}.chat-header-actions{gap:0}.chat-header-actions .icon-button{width:30px}.chat-header{padding:0 10px}.drawer-backdrop{top:68px}.composer-toolbar{padding-left:9px}.composer-left select{max-width:120px;padding-left:0}.composer-footnote{align-items:flex-start}.composer-footnote>span:first-child{align-items:baseline}.starter-grid strong,.starter-grid span{line-height:1.5}.chat-empty{padding:24px 0}.chat-panel{min-height:760px}.modal{padding:20px}.top-actions .primary{padding:8px}}.session-heading .cwd-button{text-align:left;width:100%;min-width:0;display:flex}.cwd-button:hover{color:var(--fg)}.cwd-button svg{flex-shrink:0}.cwd-trail{white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;overflow:hidden}.full-directory{white-space:pre-wrap;overflow-wrap:anywhere;-webkit-user-select:text;user-select:text;font-size:16px;line-height:1.6;display:block}.modal.path-modal{width:max-content;min-width:min(960px,100%)}.markdown :is(h4,h5,h6,small,sub,sup){font-size:16px}.inspector>.inspector-tabs{border-top:0;flex-shrink:0;min-height:74px;padding:0 8px}.inspector>.inspector-tabs button{flex-direction:column;gap:5px;padding:10px 4px;line-height:1.4;position:relative}.inspector-tabs .count{position:absolute;top:4px;right:4px}.inspector-body.session-panel{padding:0 0 24px}.session-panel .session-heading{padding:24px 26px}.session-panel .model-section{padding:22px 26px}@media (width<=740px){.inspector>.inspector-tabs{padding:0 4px}.session-panel .session-heading,.session-panel .model-section{padding:20px 16px}.session-panel .session-heading>.eyebrow{display:block}.session-panel .session-status,.session-panel .section-label,.session-panel .thinking-row{display:flex}.session-panel .metrics{display:grid}}.hint,.path,.eyebrow,.section-label,.section-label span+span,.field-label,.counter-card .field-label,.model-button small,.model-list small,.metrics small,.thinking-row>span,.saved,.job-meta,.demo-action small,.task-card p,.tool-row p,.tool-row small,.tool-call summary code,.tool-result summary>span,.thinking,.thinking div,.raw summary,.raw summary span,.message-label>span:not(.mini-pi):not(.streaming),.composer-footnote,.context-indicator,.composer-left select,.version,.session-model,.sessions-drawer footer,.inspector-footer,.starter-grid span,.chat-empty p,.welcome p,.welcome-features,.learning-list p{color:var(--secondary)}input::placeholder,textarea::placeholder{color:var(--secondary);opacity:1}.chat-header{flex-wrap:nowrap;gap:14px}.chat-header-actions{flex-shrink:0}.lineage-picker{flex:1;min-width:0;padding:8px 0;position:relative}.lineage-toolbar{align-items:center;gap:4px;min-width:0;display:flex}.conversation-lineage{scrollbar-width:thin;scrollbar-color:var(--line) transparent;align-items:center;gap:4px;min-width:0;display:flex;overflow-x:auto}.lineage-crumb{color:var(--secondary);white-space:nowrap;border-radius:6px;flex-shrink:0;padding:7px 9px;font-size:16px}.lineage-crumb:hover{background:#342328}.lineage-crumb.current{color:var(--accent);background:#392328}.lineage-separator{color:var(--secondary)}.conversation-menu-toggle{flex-shrink:0}.lineage-source{color:var(--secondary);margin:4px 9px 0;font-size:16px;line-height:1.5}.conversation-menu-dismiss{z-index:25;cursor:default;position:fixed;inset:0}.conversation-picker{z-index:30;background:#251b1e;border:1px solid #68474e;border-radius:9px;width:min(540px,100vw - 48px);max-height:min(65vh,620px);padding:8px;position:absolute;top:100%;left:0;overflow-y:auto;box-shadow:0 16px 48px #0007}.conversation-picker-heading{color:var(--secondary);padding:10px 12px 14px;font-size:16px}.conversation-choice{text-align:left;border-radius:6px;justify-content:space-between;align-items:center;gap:14px;width:100%;padding:12px;display:flex}.conversation-choice:hover,.conversation-choice.selected{background:#3b272d}.conversation-choice-content{flex-direction:column;gap:6px;min-width:0;display:flex}.choice-lineage{color:var(--fg);flex-wrap:wrap;align-items:center;gap:5px;font-size:16px;line-height:1.5;display:flex}.choice-lineage>span{white-space:nowrap}.choice-lineage .choice-ancestor,.choice-lineage>svg,.choice-source{color:var(--secondary)}.choice-source{font-size:16px;line-height:1.5}.conversation-choice.selected .choice-lineage>span:last-child{color:var(--accent)}@media (width<=740px){.chat-header{flex-wrap:wrap;gap:4px;padding:8px 10px}.lineage-picker{flex-basis:100%;width:100%}.chat-header-actions{margin-left:auto}.conversation-picker{width:min(540px,100vw - 20px)}.lineage-crumb{padding:7px}}.queue-mode{min-width:150px;position:relative}.composer-left .queue-mode select{appearance:none;color:#f8eeee;cursor:pointer;background:#342329;border:1px solid #77515a;border-radius:8px;width:100%;max-width:none;min-height:40px;padding:7px 36px 7px 12px;font-size:16px;font-weight:600}.queue-mode>svg{pointer-events:none;color:var(--secondary);position:absolute;top:50%;right:10px;transform:translateY(-50%)}.interrupt .stop-sign{color:#ff647a;stroke:#ffb5c0;flex-shrink:0}.switch{border-radius:20px;flex-shrink:0;width:58px;height:34px;padding:4px}.switch>span{width:26px;height:26px}.state-boolean .switch{margin-left:0}.task-section{border-top:1px solid var(--line);margin-top:24px;padding-top:20px}.task-section h3,.task-example h4{align-items:center;gap:8px;margin:0 0 10px;font-size:16px;font-weight:650;display:flex}.task-example{border:1px solid var(--line);background:#25191c;border-radius:8px;margin-top:12px;padding:16px}.task-example p,.task-explanation p,.task-empty{color:var(--secondary);margin:8px 0 14px;font-size:16px;line-height:1.6}.task-decision-buttons{flex-wrap:wrap;gap:8px;margin:16px 0;display:flex}.approval-card{border-color:#c68957}.tasks-panel .task-note{margin-bottom:0}.tasks-panel .task-card .raw{margin-top:12px}.chat-header-actions{flex-wrap:wrap;justify-content:flex-end;gap:8px;display:flex}.chat-header{flex-wrap:wrap}.chat-header-actions .secondary{min-height:38px}@media (width<=740px){.topbar{flex-wrap:wrap;gap:12px;height:auto;padding:12px 16px}.top-actions{flex-wrap:wrap;gap:8px;margin-left:auto}.chat-header-actions{justify-content:flex-start;width:100%}}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--line);background-clip:padding-box;border:2px solid #0000;border-radius:10px}::-webkit-scrollbar-thumb:hover{background:var(--faint);background-clip:padding-box}.label{font-family:var(--font-display);letter-spacing:.14em;text-transform:uppercase;color:var(--faint);font-size:14px;font-weight:700}.topbar-sep{background:var(--line);flex-shrink:0;width:1px;height:22px}.daemon{color:var(--faint);flex-wrap:wrap;align-items:center;gap:8px;min-width:0;font-size:15px;display:flex}.daemon b{color:var(--fg);font-weight:500;font-family:var(--font-mono)}.daemon .sep{color:var(--offline);flex-shrink:0}.dot{background:var(--ok);width:8px;height:8px;box-shadow:0 0 0 3px color-mix(in oklab, var(--ok) 16%, transparent);border-radius:50%;flex:0 0 8px;display:inline-block}.dot.off{background:var(--offline);box-shadow:none}.chip{border:1px solid var(--line);height:28px;font-family:var(--font-mono);color:var(--faint);background:var(--bg);white-space:nowrap;border-radius:999px;align-items:center;gap:6px;padding:0 12px;font-size:13px;display:inline-flex}.chip b{color:var(--fg);font-weight:500}.chip.accent{border-color:color-mix(in oklab, var(--accent) 40%, var(--line));color:var(--accent)}.chip .dot{width:7px;height:7px;box-shadow:none;flex:0 0 7px}.secondary{border:1px solid var(--line);background:var(--card);transition:background .12s var(--ease), border-color .12s var(--ease)}.secondary:not(:disabled):hover{border-color:var(--faint)}.scrim{z-index:80;background:#0a0607ad;place-items:center;padding:20px;display:grid;position:fixed;inset:0}.dialog{border:1px solid var(--line);background:var(--panel);border-radius:12px;width:min(560px,100%);max-height:calc(100dvh - 40px);overflow-y:auto;box-shadow:0 24px 70px #0000008c}.dialog-head{justify-content:space-between;align-items:flex-start;gap:12px;padding:18px 20px 0;display:flex}.dialog-eyebrow{font-family:var(--font-display);letter-spacing:.14em;text-transform:uppercase;color:var(--accent);font-size:14px;font-weight:700}.dialog h3{font-family:var(--font-display);letter-spacing:-.01em;margin:5px 0 0;font-size:20px;font-weight:700}.dialog-body{flex-direction:column;gap:12px;padding:16px 20px;display:flex}.help-step{border-bottom:1px solid var(--line-soft);grid-template-columns:26px minmax(0,1fr);gap:10px;padding:7px 0;font-size:16px;display:grid}.help-step:last-of-type{border-bottom:0}.help-step .n{font-family:var(--font-mono);color:var(--accent)}.help-step b{font-weight:600}.help-step .body{color:var(--muted);margin-top:2px;line-height:1.6}.help-callout{border-radius:var(--radius-sm);border:1px solid var(--line);background:var(--chat);color:var(--muted);align-items:flex-start;gap:10px;margin-top:4px;padding:10px 12px;font-size:16px;display:flex}.help-callout svg{flex-shrink:0;margin-top:2px}.dialog-foot{align-items:center;gap:8px;padding:0 20px 18px;display:flex}.dialog-foot .right{gap:8px;margin-left:auto;display:flex}.mnav{display:none}@media (width<=760px){.daemon,.topbar-sep{display:none}.mnav{padding:4px 6px calc(4px + env(safe-area-inset-bottom));border-top:1px solid var(--line);background:var(--panel);grid-template-columns:repeat(3,1fr);gap:2px;display:grid}.mnav button{min-height:48px;color:var(--faint);background:0 0;border:0;border-radius:8px;flex-direction:column;justify-content:center;align-items:center;gap:2px;font-size:12px;display:flex;position:relative}.mnav button.is-on{color:var(--accent);background:color-mix(in oklab, var(--accent) 9%, transparent)}.mnav .badge{background:var(--primary);min-width:16px;height:16px;color:var(--on-primary);font-family:var(--font-mono);border-radius:999px;padding:0 4px;font-size:11px;line-height:16px;position:absolute;top:5px;right:calc(50% - 22px)}}.composer:focus-within{border-color:color-mix(in oklab, var(--accent) 55%, var(--line));box-shadow:0 0 0 3px color-mix(in oklab, var(--accent) 10%, transparent)}.send{border:1px solid var(--primary-line);transition:background .12s var(--ease);border-radius:8px}.pi-user{border-bottom:0;flex-direction:column;align-items:flex-end;gap:8px;padding-bottom:12px;display:flex}.pi-user .user-message{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);max-width:78%;padding:10px 14px}.inspector-tabs{background:var(--tree)}.inspector-tabs button{color:var(--muted)}.inspector-tabs button:hover{color:var(--fg)}.inspector-tabs button.active{color:var(--accent)}.insp-tab-active-line{border-bottom:2px solid var(--accent)}.switch{background:var(--offline)}.switch.on{background:var(--primary)}.switch.on>span{background:var(--on-primary)}.tool-call{background:var(--card);border-color:var(--line)}.tool-result{background:var(--chat)}.tool-result.failed,.tool-call.failed{border-color:color-mix(in oklab, var(--err) 45%, var(--line))}.metrics{border:1px solid var(--line);border-radius:var(--radius);background:var(--line);gap:1px;overflow:hidden}.metrics>div{background:var(--chat);padding:9px 12px}.metrics span{font-family:var(--font-mono);font-weight:500}.model-button{background:var(--chat);border:1px solid var(--line);border-radius:var(--radius)}.model-button:hover{border-color:var(--faint)}input,textarea,select{border-radius:var(--radius-sm)}input:focus,textarea:focus,select:focus{border-color:var(--accent);box-shadow:0 0 0 3px color-mix(in oklab, var(--accent) 10%, transparent)}.chat-panel{background:var(--chat)}.chat-header,.inspector{background:var(--panel)}.network-sidebar{background:var(--tree)}.net-workspace{--tree-w:300px;--inspector-w:450px;--rail-w:52px;flex:1;min-height:0;display:flex;position:relative}.net-workspace>.chat-panel,.net-workspace>.welcome,.net-workspace>.offline-panel{flex:auto;min-width:0}.net-workspace>.network-sidebar{width:var(--tree-w);flex:none}.net-workspace>.network-sidebar.collapsed{width:var(--rail-w)}.net-workspace>.inspector{width:var(--inspector-w);border-right:1px solid var(--line);border-left:0;flex:none;min-height:0;max-height:none}.net-workspace>.inspector.collapsed{width:var(--rail-w)}.network-summary{color:var(--muted);border:1px solid var(--line);white-space:nowrap;border-radius:999px;align-items:center;gap:6px;margin-left:6px;padding:3px 10px;font-size:15px;display:inline-flex}.tree-toggle,.inspector-toggle,.tree-backdrop,.inspector-backdrop{display:none}.panel-rail{scrollbar-width:none;flex-direction:column;align-items:center;gap:6px;height:100%;padding:10px 0;display:flex;overflow-y:auto}.panel-rail .panel-toggle{margin-bottom:6px}.rail-device,.rail-tab{width:38px;height:38px;color:var(--muted);border-radius:8px;position:relative}.rail-device:hover,.rail-tab:hover{color:var(--fg);background:#ffffff0a}.rail-tab.active{color:var(--accent);background:#ff899714}.rail-device .dot{width:8px;height:8px;box-shadow:0 0 0 2px var(--tree);flex-basis:8px;position:absolute;bottom:4px;right:4px}.rail-tab .count{background:var(--accent);color:#1a1012;border-radius:8px;min-width:16px;height:16px;padding:0 4px;font-size:11px;line-height:16px;position:absolute;top:2px;right:2px}.inspector .panel-rail{background:var(--panel)}.inspector-tabs .panel-toggle{align-self:center;margin-left:auto}.device-icon{flex:none;display:inline-block}.device-icon.exe,.device-icon.apple{color:#e9e2e2}.network-sidebar{background:var(--tree);border-right:1px solid var(--line);flex-direction:column;min-height:0;display:flex}.tree-head{color:var(--muted);letter-spacing:1.6px;justify-content:space-between;align-items:center;padding:10px 12px 10px 16px;font-size:14px;font-weight:650;display:flex}.tree-head span{align-items:center;gap:8px;display:inline-flex}.tree-list{scrollbar-width:thin;scrollbar-color:var(--line) transparent;outline:none;flex:1;padding:4px 6px 12px;overflow:auto}.tree-list:focus-visible{box-shadow:inset 0 0 0 1px #856669}.tree-empty{padding:12px 10px}.tree-row{--depth:0;min-height:34px;padding:3px 6px 3px calc(4px + var(--depth) * 16px);cursor:pointer;color:#e9e2e2;white-space:nowrap;border-radius:6px;align-items:center;gap:7px;font-size:15px;display:flex;position:relative}.tree-row:hover{background:#ffffff08}.tree-row.focus{box-shadow:inset 0 0 0 1px #6d5557}.tree-row.sel{background:#ff899714}.tree-row.sel:before{content:"";background:var(--accent);border-radius:2px;width:3px;position:absolute;top:5px;bottom:5px;left:0}.tree-row.stale{opacity:.55}.tree-row .chev{width:18px;height:18px;color:var(--muted);flex:0 0 18px;padding:0}.tree-row span.chev{display:inline-block}.tree-row .name{text-overflow:ellipsis;min-width:0;overflow:hidden}.tree-row.device .name{font-family:var(--font-mono);font-size:15px}.tree-row.session .name{font-weight:600}.tree-row .ico{color:var(--muted);flex:none}.tree-row .ico.root{color:var(--accent)}.tree-row .badge{color:var(--muted);font-size:12px;font-family:var(--font-mono);border:1px solid #443738;border-radius:4px;padding:1px 5px}.tree-row .meta,.tree-row .path{color:var(--muted);text-overflow:ellipsis;min-width:0;font-size:13px;overflow:hidden}.tree-row .path{font-family:var(--font-mono);flex:1}.tree-row .meta.err{color:#ff8a80}.tree-row .acts{background:var(--tree);border-radius:6px;gap:2px;margin-left:auto;display:none}.tree-row:hover .acts,.tree-row.focus .acts{display:inline-flex}.tree-row:hover .acts~*,.tree-row .acts .icon-button{width:26px;height:26px}.tree-row .acts .icon-button:disabled{opacity:.35;background:0 0}.tree-row .bg-count{color:var(--muted);align-items:center;gap:4px;font-size:13px;display:inline-flex}.tree-row>.dot.work,.tree-row>.bg-count{margin-left:auto}.tree-row>.dot.work+.bg-count{margin-left:6px}.tree-row:hover>.dot.work,.tree-row:hover>.bg-count,.tree-row.focus>.dot.work,.tree-row.focus>.bg-count{display:none}.dot{background:#756062;border-radius:50%;flex:0 0 9px;width:9px;height:9px;display:inline-block}.dot.on{background:var(--ok);box-shadow:0 0 6px #6fcf9766}.dot.chk{background:var(--secondary);animation:1.1s ease-in-out infinite dpi-pulse}.dot.err{background:var(--err)}.dot.off{background:var(--offline)}.dot.work{background:var(--accent);animation:1.1s ease-in-out infinite dpi-pulse}.dot.bg{border:2px solid var(--accent);background:0 0;width:9px;height:9px}@keyframes dpi-pulse{50%{opacity:.35}}.tree-resizer{cursor:col-resize;z-index:5;flex:0 0 6px;width:6px;margin:0 -3px;position:relative}.tree-resizer:hover{background:#ff899733}.crumbs{scrollbar-width:thin;scrollbar-color:var(--line) transparent;align-items:flex-end;gap:4px;min-width:0;padding:2px 0;display:flex;overflow-x:auto}.crumb{color:#e9e2e2;border-radius:8px;flex-direction:column;flex:none;align-items:flex-start;gap:2px;max-width:260px;padding:5px 10px 6px;display:inline-flex}.crumb:hover{background:#ffffff0a}.crumb.current{color:var(--accent);background:#ff899714}.crumb-kind{letter-spacing:1.2px;text-transform:uppercase;color:var(--muted);font-size:11px;line-height:1}.crumb-main{align-items:center;gap:7px;min-width:0;max-width:100%;font-size:17px;font-weight:600;line-height:1.2;display:inline-flex}.crumb-name,.crumb .device-chip{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.crumb .device-chip{font-family:var(--font-mono)}.crumb-sep{color:var(--muted);flex:none;margin-bottom:8px}.chat-header-actions .secondary.active{border-color:var(--accent)}.history-divider{color:var(--muted);letter-spacing:1px;text-transform:uppercase;align-items:center;gap:8px;margin:8px 0 18px;font-size:14px;display:flex}.history-divider:after{content:"";border-top:1px dashed #4a3b3c;flex:1}.entry.history{opacity:.6}.retry-note{margin-top:4px}.session-modal{width:1120px}.device-modal code,.session-modal code{overflow-wrap:anywhere}.modal .field{flex-direction:column;gap:9px;margin-top:20px;display:flex}.dir-browser{flex-direction:column;gap:8px;display:flex}.dir-path{gap:8px;display:flex}.dir-path input{font-family:var(--font-mono);flex:1;font-size:14px!important}.dir-list{border:1px solid var(--line);background:var(--chat);border-radius:7px;max-height:220px;padding:4px;overflow:auto}.dir-item{color:#e9e2e2;border-radius:5px;justify-content:flex-start;width:100%;padding:6px 8px;font-size:15px}.dir-item:hover{background:#ffffff0a}.dir-item[aria-selected=true]{color:#fff;background:#ff899714}.dir-item span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.device-result{overflow-wrap:anywhere;border:1px solid #544042;border-radius:8px;align-items:flex-start;gap:10px;margin-top:18px;padding:12px;display:flex}.device-result.online{border-color:#6fcf9777}.device-result:not(.online){border-color:#ff6b6b77}.primary.danger{background:#c94f5d;border-color:#c94f5d}.offline-panel .chat-empty{gap:6px;max-width:640px;margin:0 auto}.offline-panel .eyebrow{font-family:var(--font-mono);letter-spacing:.5px;color:var(--muted);overflow-wrap:anywhere}.offline-panel.unauthorized svg,.offline-panel.mismatch svg{color:var(--err)}.offline-error{font-family:var(--font-mono);overflow-wrap:anywhere;font-size:14px}.offline-command{border:1px solid var(--line);background:#0f0a0b;border-radius:8px;align-items:center;gap:8px;margin:10px 0 4px;padding:10px 14px;display:inline-flex}.offline-command code{color:#fff;font-size:17px}.offline-actions{gap:10px;margin-top:16px;display:flex}.spin{animation:1s linear infinite dpi-spin}@keyframes dpi-spin{to{transform:rotate(360deg)}}@media (width<=1100px){.inspector-toggle{display:inline-flex}.net-workspace>.inspector.overlay{z-index:30;width:min(420px,100%);position:absolute;top:0;bottom:0;left:0;box-shadow:20px 0 60px #0008}.inspector-backdrop{z-index:29;background:#0d080899;display:block;position:absolute;inset:0}}@media (width<=760px){.tree-resizer{display:none}.tree-toggle{display:inline-flex}.net-workspace>.network-sidebar{display:none}.net-workspace>.network-sidebar.open{z-index:40;width:min(340px,90vw);display:flex;position:absolute;top:0;bottom:0;left:0;box-shadow:20px 0 60px #0008}.tree-backdrop{z-index:39;background:#0d080899;display:block;position:absolute;inset:0}.network-summary,.new-agent-button{display:none!important}.shell{height:100dvh;overflow:hidden}.net-workspace .chat-panel{height:auto;min-height:0}.net-workspace>.inspector .session-status,.net-workspace>.inspector .model-section{display:block}.top-actions .inspector-toggle{display:inline-flex}.crumb-kind{display:none}}.net-workspace .lineage-picker{flex:240px}.net-workspace .conversation-lineage{flex:0 auto}.composer{position:relative}.slash-menu{background:var(--card);z-index:20;border:1px solid #544042;border-radius:10px;max-height:320px;margin:0;padding:6px;list-style:none;position:absolute;bottom:calc(100% + 8px);left:0;right:0;overflow-y:auto;box-shadow:0 20px 60px #0007}.slash-menu li{cursor:pointer;border-radius:6px;align-items:baseline;gap:10px;padding:7px 10px;display:flex}.slash-menu li.active{background:#ff899714}.slash-menu code{color:var(--accent);font-size:15px}.slash-args{color:var(--muted);font-family:var(--font-mono);font-size:14px}.slash-desc{color:var(--muted);text-align:right;margin-left:auto;font-size:14px}.job-run{border:1px solid #5a3a3e;border-left:4px solid var(--accent);min-width:0;color:var(--fg);background:linear-gradient(#2a1b1d,#221617);border-radius:9px;margin:14px 0 6px;padding:16px 18px 14px}.job-run.is-completed{border-left-color:var(--ok)}.job-run.is-waiting{border-left-color:var(--warn)}.job-run.is-failed,.job-run.is-declined,.job-run.is-aborted{border-left-color:#8d6a6d}.job-run-head{align-items:center;gap:12px;display:flex}.job-run-icon{width:40px;height:40px;color:var(--accent);background:#3d2629;border-radius:9px;flex:none;place-items:center;display:grid}.job-run-titles{flex:1;min-width:0}.job-run-titles h3{font-family:var(--font-title);margin:2px 0 0;font-size:19px;font-weight:750}.job-run-eyebrow{letter-spacing:1.2px;color:var(--secondary);flex-wrap:wrap;align-items:center;gap:4px 12px;font-size:16px;display:flex}.job-run-repeat{letter-spacing:0;color:var(--fg);align-items:center;gap:5px;display:inline-flex}.job-run-status{background:#352728;border-radius:5px;flex:none;align-items:center;gap:7px;padding:4px 9px;font-size:16px;display:inline-flex}.job-run-status i{background:currentColor;border-radius:50%;width:7px;height:7px}.job-run-status.running{color:var(--accent)}.job-run-status.running i{animation:1.5s infinite pulse}.job-run-status.waiting{color:var(--warn)}.job-run-status.completed{color:var(--ok)}.job-run-about{margin:12px 0 8px;font-size:16px;line-height:1.55}.job-run-meta{font-family:var(--font-mono);color:var(--secondary);flex-wrap:wrap;gap:6px 14px;font-size:16px;display:flex}.job-run-input,.job-run-resumed{color:var(--fg)}.job-run-resumed{color:var(--warn)}.job-stepper{flex-wrap:wrap;align-items:center;gap:8px 0;margin:16px 0 12px;padding:0;list-style:none;display:flex}.job-stepper li{align-items:center;display:inline-flex}.job-stepper li:not(:last-child):after{content:"→";color:#8d6a6d;margin:0 8px}.job-chip{background:#241819;border:1px solid #4a3638;border-radius:999px;align-items:center;gap:7px;padding:4px 11px 4px 4px;font-size:16px;display:inline-flex}.job-step-dot{width:24px;height:24px;font-family:var(--font-mono);background:#3a2a2c;border-radius:50%;place-items:center;font-size:15px;display:grid}.ph-done .job-chip{border-color:#3f6b52}.ph-done .job-step-dot{color:#b9f2cf;background:#24543a}.ph-running .job-chip{border-color:var(--accent);box-shadow:0 0 0 3px #ff899722}.ph-running .job-step-dot{color:var(--accent);background:#5a2a31}.ph-waiting .job-chip{border-color:var(--warn);box-shadow:0 0 0 3px #f2c94c22}.ph-waiting .job-step-dot{color:var(--warn);background:#574719}.ph-failed .job-chip,.ph-aborted .job-chip{border-color:#8d4a52}.job-stepper .ph-skipped{opacity:.55}.job-step-progress{font-family:var(--font-mono);color:var(--secondary)}.job-phases{grid-template-columns:minmax(0,1fr);gap:6px;display:grid}.job-phase{background:#1f1415;border:1px solid #3c2b2d;border-radius:7px}.job-phase.ph-running{border-color:#6a3a41}.job-phase.ph-waiting{border-color:#6b5a24}.job-phase.ph-skipped,.job-phase.ph-pending{opacity:.7}.job-phase>summary{cursor:pointer;flex-wrap:wrap;align-items:baseline;gap:4px 10px;padding:9px 12px;font-size:16px;list-style:none;display:flex}.job-phase summary::-webkit-details-marker{display:none}.job-phase-n{font-family:var(--font-mono);color:var(--secondary)}.job-phase-summary{color:#e6d7d8;flex:1;min-width:160px}.job-phase-time{font-family:var(--font-mono);color:var(--secondary);margin-left:auto}.job-phase-about,.job-phase-empty{color:var(--secondary);margin:0;padding:0 12px 8px 34px;font-size:16px}.job-steps{grid-template-columns:minmax(0,1fr);gap:5px;margin:0;padding:0 12px 10px 34px;list-style:none;display:grid}.job-steps>li{font-size:16px;display:block}.job-steps summary,.job-step-line{align-items:center;gap:9px;min-width:0;list-style:none;display:flex}.job-steps summary{cursor:pointer}.job-steps summary:after{content:"⌄";color:var(--secondary);margin-left:2px}.job-steps details[open] summary:after{content:"⌃"}.job-steps code{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:var(--font-mono);background:#2c1e20;border-radius:4px;padding:1px 6px;font-size:16px;overflow:hidden}.job-steps .k-tool code{border:1px solid #5a3a3e;flex:none}.job-step-kind{color:var(--secondary)}.job-steps .k-tool .job-step-kind,.job-steps .k-agent .job-step-kind{color:var(--accent)}.job-step-text{min-width:0;color:var(--fg)}.job-step-args{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:var(--font-mono);color:var(--secondary);overflow:hidden}.job-step-state{font-family:var(--font-mono);color:var(--secondary);flex:none;align-items:center;gap:5px;display:inline-flex}.job-steps .st-done .job-step-state svg{color:var(--ok)}.job-steps .st-error .job-step-state svg{color:#ff8997}.job-steps .st-waiting .job-step-state svg{color:var(--warn)}.job-steps pre{white-space:pre-wrap;color:#e6d7d8;background:#180f10;border-radius:5px;max-height:180px;margin:4px 0 2px 25px;padding:7px 9px;font-size:16px;overflow:auto}.job-step-link{margin:2px 0 0 25px}.job-approval{background:#2c2514;border:1px solid #8a7330;border-radius:8px;margin-top:12px;padding:12px 14px}.job-approval>div:first-child{color:var(--warn);align-items:center;gap:8px;display:flex}.job-approval p{margin:6px 0 10px;font-size:16px;line-height:1.5}.job-approval-buttons{flex-wrap:wrap;gap:8px;display:flex}.job-run-foot{color:var(--secondary);border-top:1px solid #3c2b2d;flex-wrap:wrap;align-items:center;gap:8px 14px;margin-top:12px;padding-top:10px;font-size:16px;display:flex}.job-run-foot span{flex:1;min-width:220px}.job-def{background:#241819;border:1px solid #453536;border-radius:8px;margin-top:12px;padding:13px}.job-def-head{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.job-def-head svg{color:var(--accent)}.job-def-head strong{margin-right:auto;font-size:16px}.job-def p{margin:8px 0;font-size:16px;line-height:1.5}.job-chain{flex-wrap:wrap;gap:6px 4px;margin:8px 0;padding:0;list-style:none;display:flex}.job-chain li{border:1px solid #4a3638;border-radius:999px;align-items:center;gap:5px;padding:3px 9px 3px 3px;font-size:16px;display:inline-flex}.job-chain li span{width:20px;height:20px;font-family:var(--font-mono);background:#3a2a2c;border-radius:50%;place-items:center;font-size:14px;display:grid}.job-chain li:not(:last-child):after{content:"→";color:#8d6a6d;margin-left:4px}.job-def-meta{flex-wrap:wrap;gap:6px;display:flex}.job-def-meta code{font-family:var(--font-mono);color:var(--secondary);overflow-wrap:anywhere;font-size:16px}.job-def-last{flex-wrap:wrap;align-items:center;gap:6px 10px;margin-top:10px;font-size:16px;display:flex}.job-def-last .text-button{margin-top:0}.job-def-run{margin-top:12px}.task-tree{gap:4px;margin:0 0 12px;padding:0;list-style:none;display:grid}.task-row{margin-left:calc(var(--depth) * 18px);background:#211617;border:1px solid #3c2b2d;border-left:3px solid #5a3a3e;border-radius:6px;flex-wrap:wrap;align-items:center;gap:4px 8px;padding:7px 10px;font-size:16px;display:flex}.task-row[data-task-kind=pi\.tool]{border-left-color:var(--accent)}.task-row[data-task-kind^="demo.job."]{border-left-color:var(--warn)}.task-row small{width:100%;font-family:var(--font-mono);color:var(--secondary);font-size:16px}.task-row .text-button{margin-top:0}.task-phase{font-family:var(--font-mono);color:var(--secondary)}.context-section{margin-top:18px}.context-numbers{flex-wrap:wrap;align-items:baseline;gap:6px 8px;font-size:16px;display:flex}.context-numbers strong{font-family:var(--font-title);font-size:22px}.context-pct{font-family:var(--font-mono);color:var(--secondary)}.context-compacting{color:var(--warn);align-items:center;gap:6px;margin-left:auto;display:inline-flex}.context-compacting i{background:currentColor;border-radius:50%;width:7px;height:7px;animation:1.2s infinite pulse}.context-bar{touch-action:none;background:#2d2122;border:1px solid #453536;border-radius:7px;height:14px;margin:40px 0 44px;position:relative}.context-bar.off .context-marker{opacity:.45}.context-fill{background:linear-gradient(90deg, #8a3a45, var(--accent));border-radius:7px 0 0 7px;transition:width .3s;position:absolute;inset:0 auto 0 0}.context-fill.hot{background:linear-gradient(90deg, #8a3a45, var(--warn))}.context-reserve{background:repeating-linear-gradient(135deg,#ffffff10 0 5px,#0000 5px 10px);border-left:1px solid #8d6a6d;border-radius:0 7px 7px 0;position:absolute;top:0;bottom:0;right:0}.context-marker{background:var(--warn);cursor:ew-resize;touch-action:none;border-radius:2px;width:4px;height:26px;margin-left:-2px;padding:0;position:absolute;top:-7px}.context-marker:before{content:"";position:absolute;inset:-4px -10px}.context-marker.wait{background:var(--fg)}.context-marker-label{white-space:nowrap;font-size:16px;font-family:var(--font-mono);color:var(--warn);pointer-events:none;position:absolute;left:50%;transform:translate(-50%)}.context-marker.auto .context-marker-label{bottom:calc(100% + 4px)}.context-marker.wait .context-marker-label{color:var(--fg);top:calc(100% + 4px)}.context-marker.label-start .context-marker-label{left:-2px;transform:none}.context-marker.label-end .context-marker-label{left:auto;right:-2px;transform:none}.context-hint{margin:0 0 10px}.context-details-toggle{color:var(--warn);letter-spacing:0;align-items:center;gap:4px;padding:0;font-size:16px;display:inline-flex}.compaction-modal p{margin:0 0 12px;font-size:16px;line-height:1.6}.modal .context-toggle{flex-direction:row;align-items:center;gap:8px;margin-top:6px}.modal .context-toggle input{width:auto;padding:0}.context-toggle{flex-wrap:wrap;align-items:center;gap:4px 8px;font-size:16px;display:flex}.context-toggle small{width:100%;color:var(--secondary);font-size:16px}.ext-card{background:#241819;border:1px solid #453536;border-left:3px solid #5a3a3e;border-radius:8px;margin-top:12px;padding:13px 14px}.ext-card.on{border-left-color:var(--ok)}.ext-head{align-items:center;gap:12px;display:flex}.ext-head>div{flex-wrap:wrap;align-items:baseline;gap:4px 10px;min-width:0;display:flex}.ext-head strong{font-size:16px}.ext-head code{font-family:var(--font-mono);color:var(--secondary);font-size:16px}.ext-card>p{margin:8px 0 0;font-size:16px;line-height:1.5}.ext-prompts{border-top:1px solid #3c2b2d;gap:10px;margin:12px 0 0;padding:10px 0 0;list-style:none;display:grid}.ext-prompts.off{opacity:.6}.ext-prompts li{gap:4px;font-size:16px;display:grid}.ext-prompts label{cursor:pointer;align-items:center;gap:8px;display:flex}.ext-prompts code{font-family:var(--font-mono);font-size:16px}.ext-prompts li>span{color:var(--secondary);padding-left:26px;line-height:1.45}.ext-prompts details{padding-left:26px}.ext-prompts summary{cursor:pointer;color:var(--accent)}.ext-prompts pre{white-space:pre-wrap;background:#180f10;border-radius:6px;max-height:260px;margin:6px 0 0;padding:9px;font-size:16px;overflow:auto}.tool-row.disabled summary>strong,.tool-row.disabled summary>svg,.tool-row.disabled>p,.tool-row.disabled>pre,.tool-row.disabled>small{opacity:.45}.tool-row summary .tag.tool-disabled{color:var(--warn);border-color:#6b5a24;margin-left:2px;margin-right:auto}.tool-row summary .tag.tool-disabled+.tag{margin-left:auto}
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
<meta name="referrer" content="no-referrer" />
|
|
8
8
|
<link rel="icon" type="image/svg+xml" href="/icons/durable-pi.svg" />
|
|
9
9
|
<title>Durable Pi | Network</title>
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-CcCPNp5E.js"></script>
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DlEtM_VE.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="app"></div>
|
|
@@ -17,6 +17,7 @@ import type { AgentSummary } from "@sdk/api";
|
|
|
17
17
|
import { lineage } from "@sdk/models";
|
|
18
18
|
import TranscriptItem from "./TranscriptItem.vue";
|
|
19
19
|
import DeviceIcon from "./DeviceIcon.vue";
|
|
20
|
+
import GitChip from "./GitChip.vue";
|
|
20
21
|
import Composer from "./Composer.vue";
|
|
21
22
|
import {
|
|
22
23
|
activeLineage,
|
|
@@ -175,6 +176,7 @@ watch(
|
|
|
175
176
|
}}</span></span
|
|
176
177
|
>
|
|
177
178
|
</button>
|
|
179
|
+
<GitChip v-if="sessionSummary?.cwd" />
|
|
178
180
|
<template v-for="c in activeLineage" :key="c.id">
|
|
179
181
|
<ChevronRight class="crumb-sep" :size="18" aria-hidden="true" />
|
|
180
182
|
<button
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Git chip for the session breadcrumb: branch + dirty at a glance, click for
|
|
3
|
+
// the real panel — status, worktrees, fetch/pull/log/commit, new worktree.
|
|
4
|
+
// Everything goes through the device's whitelisted /v1/git API.
|
|
5
|
+
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
6
|
+
import { ChevronDown, GitBranch, RefreshCw } from "lucide-vue-next";
|
|
7
|
+
import { deviceId, sessionSummary, notify } from "../client/store";
|
|
8
|
+
import { deviceClient } from "../client/hub";
|
|
9
|
+
import type { GitStatus } from "@sdk/client";
|
|
10
|
+
|
|
11
|
+
const props = defineProps<{ deviceId?: string; cwd?: string }>();
|
|
12
|
+
const status = ref<GitStatus | null>(null);
|
|
13
|
+
const open = ref(false);
|
|
14
|
+
const busy = ref("");
|
|
15
|
+
const logText = ref("");
|
|
16
|
+
const commitMsg = ref("");
|
|
17
|
+
const wtName = ref("");
|
|
18
|
+
|
|
19
|
+
const cwd = computed(() => props.cwd ?? sessionSummary.value?.cwd ?? "");
|
|
20
|
+
const devId = computed(() => props.deviceId ?? devId.value);
|
|
21
|
+
const dirty = computed(() => (status.value?.staged ?? 0) + (status.value?.unstaged ?? 0) + (status.value?.untracked ?? 0));
|
|
22
|
+
|
|
23
|
+
let timer: ReturnType<typeof setInterval> | undefined;
|
|
24
|
+
async function refresh() {
|
|
25
|
+
if (!cwd.value || !devId.value) return;
|
|
26
|
+
try {
|
|
27
|
+
status.value = await deviceClient(devId.value).gitStatus(cwd.value);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
status.value = null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// First fetch inside setup: SSR (and the first paint) wait for it, so the chip
|
|
33
|
+
// never flashes empty; later refreshes ride the interval + cwd changes.
|
|
34
|
+
await refresh();
|
|
35
|
+
watch(cwd, () => void refresh());
|
|
36
|
+
onMounted(() => {
|
|
37
|
+
timer = setInterval(refresh, 15000);
|
|
38
|
+
});
|
|
39
|
+
onBeforeUnmount(() => timer && clearInterval(timer));
|
|
40
|
+
|
|
41
|
+
async function op(name: "fetch" | "pull" | "log" | "branch", keepOpen = true) {
|
|
42
|
+
if (!devId.value || !cwd.value) return;
|
|
43
|
+
busy.value = name;
|
|
44
|
+
logText.value = "";
|
|
45
|
+
try {
|
|
46
|
+
const out = await deviceClient(devId.value).gitOp(cwd.value, name);
|
|
47
|
+
if (name === "log" || name === "branch") logText.value = out.text;
|
|
48
|
+
else notify(`git ${name}: ${out.text.split("\n")[0] ?? "ok"}`);
|
|
49
|
+
await refresh();
|
|
50
|
+
} catch (e) {
|
|
51
|
+
notify(`git ${name} failed: ${(e as Error).message.slice(0, 120)}`);
|
|
52
|
+
} finally {
|
|
53
|
+
busy.value = "";
|
|
54
|
+
void keepOpen;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function commit() {
|
|
59
|
+
if (!devId.value || !cwd.value || !commitMsg.value.trim()) return;
|
|
60
|
+
busy.value = "commit";
|
|
61
|
+
try {
|
|
62
|
+
const out = await deviceClient(devId.value).gitOp(cwd.value, "commit", { message: commitMsg.value.trim() });
|
|
63
|
+
notify(out.text);
|
|
64
|
+
commitMsg.value = "";
|
|
65
|
+
await refresh();
|
|
66
|
+
} catch (e) {
|
|
67
|
+
notify(`commit failed: ${(e as Error).message.slice(0, 120)}`);
|
|
68
|
+
} finally {
|
|
69
|
+
busy.value = "";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function addWorktree() {
|
|
74
|
+
if (!devId.value || !cwd.value || !wtName.value.trim() || !status.value?.root) return;
|
|
75
|
+
busy.value = "wt";
|
|
76
|
+
const root = status.value.root;
|
|
77
|
+
const parent = root.slice(0, root.lastIndexOf("/"));
|
|
78
|
+
const base = root.slice(root.lastIndexOf("/") + 1);
|
|
79
|
+
const target = `${parent}/${base}-${wtName.value.trim()}`;
|
|
80
|
+
try {
|
|
81
|
+
const out = await deviceClient(devId.value).gitOp(cwd.value, "worktree-add", {
|
|
82
|
+
branch: wtName.value.trim(),
|
|
83
|
+
target,
|
|
84
|
+
});
|
|
85
|
+
notify(`worktree: ${out.text.split("\n")[0] ?? target}`);
|
|
86
|
+
wtName.value = "";
|
|
87
|
+
await refresh();
|
|
88
|
+
} catch (e) {
|
|
89
|
+
notify(`worktree failed: ${(e as Error).message.slice(0, 120)}`);
|
|
90
|
+
} finally {
|
|
91
|
+
busy.value = "";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function removeWorktree(path: string) {
|
|
96
|
+
if (!devId.value || !cwd.value) return;
|
|
97
|
+
if (!confirm(`Remove worktree ${path}?`)) return;
|
|
98
|
+
busy.value = "wt";
|
|
99
|
+
try {
|
|
100
|
+
await deviceClient(devId.value).gitOp(cwd.value, "worktree-remove", { target: path });
|
|
101
|
+
await refresh();
|
|
102
|
+
} catch (e) {
|
|
103
|
+
notify(`remove failed: ${(e as Error).message.slice(0, 120)}`);
|
|
104
|
+
} finally {
|
|
105
|
+
busy.value = "";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<template>
|
|
111
|
+
<span v-if="status?.repo" class="git-chip-wrap">
|
|
112
|
+
<button class="crumb git-chip" :title="`${status.root} · ${status.head ?? ''} ${status.subject ?? ''}`" @click="open = !open">
|
|
113
|
+
<GitBranch :size="16" aria-hidden="true" />
|
|
114
|
+
<span class="branch">{{ status.branch ?? status.head }}</span>
|
|
115
|
+
<span v-if="dirty" class="dirty">±{{ dirty }}</span>
|
|
116
|
+
<span v-if="status.ahead" class="ahead">↑{{ status.ahead }}</span>
|
|
117
|
+
<span v-if="status.behind" class="behind">↓{{ status.behind }}</span>
|
|
118
|
+
<ChevronDown :size="14" aria-hidden="true" />
|
|
119
|
+
</button>
|
|
120
|
+
<div v-if="open" class="git-panel" @click.stop>
|
|
121
|
+
<header>
|
|
122
|
+
<strong>git · {{ status.root }}</strong>
|
|
123
|
+
<button :disabled="!!busy" title="refresh" @click="refresh()"><RefreshCw :size="14" /></button>
|
|
124
|
+
</header>
|
|
125
|
+
<p class="line">
|
|
126
|
+
{{ status.branch ?? "detached" }}<template v-if="status.upstream"> → {{ status.upstream }}</template>
|
|
127
|
+
· <span title="staged/unstaged/untracked">+{{ status.staged ?? 0 }} ~{{ status.unstaged ?? 0 }} ?{{ status.untracked ?? 0 }}</span>
|
|
128
|
+
</p>
|
|
129
|
+
<p class="line muted">{{ status.head }} {{ status.subject }}</p>
|
|
130
|
+
<p v-if="status.worktreeOf" class="line wt-of">worktree of {{ status.worktreeOf }}</p>
|
|
131
|
+
|
|
132
|
+
<div class="row">
|
|
133
|
+
<button :disabled="!!busy" @click="op('fetch')">fetch</button>
|
|
134
|
+
<button :disabled="!!busy" @click="op('pull')">pull</button>
|
|
135
|
+
<button :disabled="!!busy" @click="op('log')">log</button>
|
|
136
|
+
<button :disabled="!!busy" @click="op('branch')">branches</button>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div class="row commit">
|
|
140
|
+
<input v-model="commitMsg" placeholder="commit message…" @keyup.enter="commit" />
|
|
141
|
+
<button :disabled="!!busy || !commitMsg.trim()" @click="commit">commit -am</button>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<div class="row wt">
|
|
145
|
+
<input v-model="wtName" placeholder="branch name…" @keyup.enter="addWorktree" />
|
|
146
|
+
<button :disabled="!!busy || !wtName.trim()" @click="addWorktree">+ worktree</button>
|
|
147
|
+
</div>
|
|
148
|
+
<ul v-if="(status.worktrees?.length ?? 0) > 1" class="wts">
|
|
149
|
+
<li v-for="w in status.worktrees" :key="w.path">
|
|
150
|
+
<code>{{ w.path }}</code>
|
|
151
|
+
<span class="muted">{{ w.branch ?? w.head }}</span>
|
|
152
|
+
<button v-if="w.path !== status.root" :disabled="!!busy" @click="removeWorktree(w.path)">remove</button>
|
|
153
|
+
</li>
|
|
154
|
+
</ul>
|
|
155
|
+
|
|
156
|
+
<pre v-if="logText" class="log">{{ logText.slice(0, 4000) }}</pre>
|
|
157
|
+
</div>
|
|
158
|
+
</span>
|
|
159
|
+
</template>
|
|
160
|
+
|
|
161
|
+
<style scoped>
|
|
162
|
+
.git-chip-wrap { position: relative; display: inline-flex; align-items: center; }
|
|
163
|
+
.git-chip { gap: 4px; }
|
|
164
|
+
.git-chip .branch { max-width: 14ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
165
|
+
.dirty { color: var(--warn, #e5a50a); }
|
|
166
|
+
.ahead { color: var(--ok, #2ea043); }
|
|
167
|
+
.behind { color: var(--info, #58a6ff); }
|
|
168
|
+
.git-panel {
|
|
169
|
+
position: absolute; top: calc(100% + 6px); left: 0; z-index: 40; min-width: 320px; max-width: min(560px, 90vw);
|
|
170
|
+
background: var(--bg-2, #1c1c22); border: 1px solid var(--border, #333); border-radius: 8px; padding: 10px 12px;
|
|
171
|
+
box-shadow: 0 8px 24px rgb(0 0 0 / 40%); font-size: 12px;
|
|
172
|
+
}
|
|
173
|
+
.git-panel header { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
|
|
174
|
+
.git-panel .line { margin: 4px 0; }
|
|
175
|
+
.git-panel .muted, .muted { opacity: 0.7; }
|
|
176
|
+
.git-panel .wt-of { color: var(--info, #58a6ff); }
|
|
177
|
+
.git-panel .row { display: flex; gap: 6px; margin: 8px 0; flex-wrap: wrap; }
|
|
178
|
+
.git-panel .row.commit, .git-panel .row.wt { flex-wrap: nowrap; }
|
|
179
|
+
.git-panel input { flex: 1; min-width: 0; }
|
|
180
|
+
.git-panel .wts { list-style: none; padding: 0; margin: 4px 0; display: grid; gap: 2px; }
|
|
181
|
+
.git-panel .wts li { display: flex; gap: 8px; align-items: center; }
|
|
182
|
+
.git-panel .wts code { font-size: 11px; }
|
|
183
|
+
.git-panel .log { max-height: 220px; overflow: auto; background: rgb(0 0 0 / 25%); padding: 8px; border-radius: 6px; }
|
|
184
|
+
</style>
|
|
@@ -38,6 +38,34 @@ const newId = (): string =>
|
|
|
38
38
|
* Typed client for one dpi-backend. Works in browsers and Node (fetch + streams).
|
|
39
39
|
* `token` is optional: the playground hub injects it, so browsers never hold one.
|
|
40
40
|
*/
|
|
41
|
+
export interface GitStatus {
|
|
42
|
+
repo: boolean;
|
|
43
|
+
root?: string;
|
|
44
|
+
branch?: string;
|
|
45
|
+
detached?: boolean;
|
|
46
|
+
upstream?: string;
|
|
47
|
+
ahead?: number;
|
|
48
|
+
behind?: number;
|
|
49
|
+
staged?: number;
|
|
50
|
+
unstaged?: number;
|
|
51
|
+
untracked?: number;
|
|
52
|
+
head?: string;
|
|
53
|
+
subject?: string;
|
|
54
|
+
worktreeOf?: string;
|
|
55
|
+
worktrees?: { path: string; branch: string | null; head: string }[];
|
|
56
|
+
}
|
|
57
|
+
export type GitOpName =
|
|
58
|
+
| "diff"
|
|
59
|
+
| "log"
|
|
60
|
+
| "fetch"
|
|
61
|
+
| "pull"
|
|
62
|
+
| "switch"
|
|
63
|
+
| "branch"
|
|
64
|
+
| "commit"
|
|
65
|
+
| "worktree-list"
|
|
66
|
+
| "worktree-add"
|
|
67
|
+
| "worktree-remove";
|
|
68
|
+
|
|
41
69
|
export class DpiClient {
|
|
42
70
|
readonly baseUrl: string;
|
|
43
71
|
constructor(
|
|
@@ -140,6 +168,16 @@ export class DpiClient {
|
|
|
140
168
|
return this.stream(`/v1/events${docs.length ? `?docs=${docs.map(enc).join(",")}` : ""}`, signal);
|
|
141
169
|
}
|
|
142
170
|
|
|
171
|
+
// ------------------------------------------------------------ git
|
|
172
|
+
/** Structured git status for a folder on the device (repo:false when none). */
|
|
173
|
+
gitStatus(path: string) {
|
|
174
|
+
return this.request<GitStatus>("GET", `/v1/git/status?path=${enc(path)}`);
|
|
175
|
+
}
|
|
176
|
+
/** One whitelisted git operation; see the device's git.ts for the op list. */
|
|
177
|
+
gitOp(path: string, op: GitOpName, opts: { ref?: string; branch?: string; message?: string; target?: string } = {}) {
|
|
178
|
+
return this.request<{ text: string }>("POST", "/v1/git", { path, op, ...opts });
|
|
179
|
+
}
|
|
180
|
+
|
|
143
181
|
// ------------------------------------------------------------ sessions
|
|
144
182
|
sessions() {
|
|
145
183
|
return this.request<SessionSummary[]>("GET", "/v1/sessions");
|
package/apps/dpi-tui/src/app.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type OverlayHandle,
|
|
13
13
|
type TuiInputListenerResult,
|
|
14
14
|
} from "@earendil-works/pi-tui";
|
|
15
|
-
import { DpiClient, follow, type AgentHandle, type ConnectionState } from "../../dpi-sdk/src/client.ts";
|
|
15
|
+
import { DpiClient, follow, type AgentHandle, type ConnectionState, type GitStatus } from "../../dpi-sdk/src/client.ts";
|
|
16
16
|
import { isDpiError } from "../../dpi-sdk/src/errors.ts";
|
|
17
17
|
import { AgentModel, SessionModel } from "../../dpi-sdk/src/models.ts";
|
|
18
18
|
import { sumUsage } from "../../dpi-sdk/src/transcript.ts";
|
|
@@ -152,6 +152,26 @@ export class App {
|
|
|
152
152
|
private overlays: Array<{ handle: OverlayHandle; onClose?: () => void }> = [];
|
|
153
153
|
private ticker?: ReturnType<typeof setInterval>;
|
|
154
154
|
private sessions: SessionSummary[] = [];
|
|
155
|
+
/** Ambient git for the panel: cwd → {at, status}, 30s TTL, fetched as the
|
|
156
|
+
* cursor moves — the herd and agent header always show ⎇ branch ±dirty. */
|
|
157
|
+
private gitCache = new Map<string, { at: number; s: GitStatus }>();
|
|
158
|
+
|
|
159
|
+
private refreshGit(cwd: string | undefined) {
|
|
160
|
+
if (!cwd) return;
|
|
161
|
+
const hit = this.gitCache.get(cwd);
|
|
162
|
+
if (hit && Date.now() - hit.at < 30_000) return;
|
|
163
|
+
void this.client
|
|
164
|
+
.gitStatus(cwd)
|
|
165
|
+
.then((s) => {
|
|
166
|
+
this.gitCache.set(cwd, { at: Date.now(), s });
|
|
167
|
+
this.requestRender();
|
|
168
|
+
})
|
|
169
|
+
.catch(() => {});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private gitOf(cwd: string | undefined): GitStatus | undefined {
|
|
173
|
+
return cwd ? this.gitCache.get(cwd)?.s : undefined;
|
|
174
|
+
}
|
|
155
175
|
private devicesCache: DeviceRow[] = [];
|
|
156
176
|
private deviceOnline = false;
|
|
157
177
|
private events: { at: string; glyph: string; text: string }[] = [];
|
|
@@ -300,6 +320,11 @@ export class App {
|
|
|
300
320
|
}
|
|
301
321
|
}
|
|
302
322
|
|
|
323
|
+
/** The open session's working folder — what /git operates on. */
|
|
324
|
+
cwd(): string | undefined {
|
|
325
|
+
return this.sessionSummary()?.cwd ?? undefined;
|
|
326
|
+
}
|
|
327
|
+
|
|
303
328
|
sessionSummary(): SessionSummary | undefined {
|
|
304
329
|
const s = this.sessions.find((x) => x.id === this.sessionId);
|
|
305
330
|
return s && this.session.state.ready ? { ...s, agents: this.session.state.agents } : s;
|
|
@@ -621,9 +646,13 @@ export class App {
|
|
|
621
646
|
// trailing space, the list closes, and the NEXT enter submits). An exact
|
|
622
647
|
// command ("/model") runs immediately instead — completing it would force
|
|
623
648
|
// a pointless second enter.
|
|
649
|
+
// Complete ONLY while the command name itself is being typed — once a
|
|
650
|
+
// space follows, the arguments are on the line and enter must submit,
|
|
651
|
+
// not wipe them back to "/git ". (Caught driving /git worktree new ….)
|
|
624
652
|
if (
|
|
625
653
|
sugg &&
|
|
626
654
|
input.kind === "message" &&
|
|
655
|
+
!input.value.includes(" ") &&
|
|
627
656
|
!COMMANDS.some((c) => `/${c.name}` === input.value)
|
|
628
657
|
) {
|
|
629
658
|
const pick = sugg.names[this.suggIndex % sugg.names.length]!;
|
|
@@ -819,7 +848,8 @@ export class App {
|
|
|
819
848
|
this.requestRender();
|
|
820
849
|
}
|
|
821
850
|
|
|
822
|
-
|
|
851
|
+
/** Git events land in the log view (L) — /git writes through here. */
|
|
852
|
+
logEvent(glyph: string, text: string) {
|
|
823
853
|
const at = new Date().toLocaleTimeString("en-GB", { hour12: false });
|
|
824
854
|
this.events.push({ at, glyph, text });
|
|
825
855
|
if (this.events.length > 500) this.events.splice(0, this.events.length - 500);
|
|
@@ -962,7 +992,8 @@ export class App {
|
|
|
962
992
|
}).slice(0, h);
|
|
963
993
|
if (!wide) return table;
|
|
964
994
|
const selected = this.sessions.find((s) => s.id === (this.selectedId ?? this.sessionId));
|
|
965
|
-
|
|
995
|
+
if (selected?.cwd) this.refreshGit(selected.cwd);
|
|
996
|
+
const panel = renderHerdPanel(panelW, h, selected, this.contextLeft(), this.device.name, this.gitOf(selected?.cwd));
|
|
966
997
|
return joinPanel(table, panel, tableW, w, h);
|
|
967
998
|
}
|
|
968
999
|
|
|
@@ -990,8 +1021,11 @@ export class App {
|
|
|
990
1021
|
const ctx = this.contextLeft();
|
|
991
1022
|
const barW = Math.min(40, Math.max(12, Math.round(w * 0.3)));
|
|
992
1023
|
const filled = ctx === undefined ? 0 : Math.round((ctx / 100) * barW);
|
|
1024
|
+
const g = this.gitOf(session?.cwd);
|
|
1025
|
+
if (session?.cwd) this.refreshGit(session.cwd);
|
|
1026
|
+
const gitChip = g?.repo ? ` ${c.muted("⎇")} ${c.text(g.branch ?? g.head ?? "")}${(g.staged ?? 0) + (g.unstaged ?? 0) + (g.untracked ?? 0) ? c.warn(` ±${(g.staged ?? 0) + (g.unstaged ?? 0) + (g.untracked ?? 0)}`) : ""}` : "";
|
|
993
1027
|
lines.push(
|
|
994
|
-
`${c.muted(this.device.id)} ${c.muted("›")} ${c.text(session?.name ?? "—")} ${c.muted("›")} ${c.bold(`#${this.agentId ?? 1}`)} ` +
|
|
1028
|
+
`${c.muted(this.device.id)} ${c.muted("›")} ${c.text(session?.name ?? "—")} ${c.muted("›")} ${c.bold(`#${this.agentId ?? 1}`)}${gitChip} ` +
|
|
995
1029
|
`${STATE_COLOR[state](STATE_GLYPH[state] + " " + (this.busy() ? "working" : "idle"))} ` +
|
|
996
1030
|
`${c.muted("ctx")} ${ctx === undefined ? c.muted("—") : c.accent(rep("█", filled)) + c.muted(rep("░", barW - filled)) + " " + c.muted(`${ctx}%`)}`,
|
|
997
1031
|
);
|
|
@@ -5,6 +5,7 @@ import type { App } from "./app.ts";
|
|
|
5
5
|
import { Panel, pickAgent, pickDevice, pickModel, pickSession } from "./pickers.ts";
|
|
6
6
|
import { parsePairArgs, runPairing } from "./pairing.ts";
|
|
7
7
|
import { c } from "./theme.ts";
|
|
8
|
+
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
8
9
|
|
|
9
10
|
export const COMMANDS: SlashCommand[] = [
|
|
10
11
|
{ name: "sessions", description: "switch or create a session on this device" },
|
|
@@ -25,10 +26,75 @@ export const COMMANDS: SlashCommand[] = [
|
|
|
25
26
|
{ name: "name", description: "rename this agent", argumentHint: "<name>" },
|
|
26
27
|
{ name: "tasks", description: "live tasks in this session" },
|
|
27
28
|
{ name: "pair", description: "pair THIS device with a hub", argumentHint: "<CODE|PAIR_URL> [--hub ORIGIN] [--force]" },
|
|
29
|
+
{
|
|
30
|
+
name: "git",
|
|
31
|
+
description: "git on the session's folder: status, diff, log, pull, fetch, branch, switch, commit, worktree",
|
|
32
|
+
argumentHint: "[status|diff|log|fetch|pull|branch|switch <b>|commit <msg>|worktree [new <b>|remove <path>]]",
|
|
33
|
+
getArgumentCompletions: (prefix) =>
|
|
34
|
+
["status", "diff", "log", "fetch", "pull", "branch", "switch", "commit", "worktree"]
|
|
35
|
+
.filter((s) => s.startsWith(prefix))
|
|
36
|
+
.map((s) => ({ value: s, label: s })),
|
|
37
|
+
},
|
|
28
38
|
{ name: "help", description: "keys and commands" },
|
|
29
39
|
{ name: "quit", description: "exit (the agent keeps running)" },
|
|
30
40
|
];
|
|
31
41
|
|
|
42
|
+
const GIT_OPS = new Set(["diff", "log", "fetch", "pull", "branch", "switch", "commit", "worktree", "status", "wt"]);
|
|
43
|
+
|
|
44
|
+
/** /git — the device's own git, spoken through its whitelisted API. */
|
|
45
|
+
async function gitCommand(app: App, arg: string) {
|
|
46
|
+
const cwd = app.cwd();
|
|
47
|
+
if (!cwd) return app.notify("no session open — /git works on a session's folder");
|
|
48
|
+
const [op = "status", ...rest] = arg.split(/\s+/).filter(Boolean);
|
|
49
|
+
const a1 = rest[0] ?? "";
|
|
50
|
+
try {
|
|
51
|
+
if (op === "status" || op === "") {
|
|
52
|
+
const s = await app.client.gitStatus(cwd);
|
|
53
|
+
if (!s.repo) return app.notify(`${cwd} is not a git repository`);
|
|
54
|
+
const dirty = (s.staged ?? 0) + (s.unstaged ?? 0) + (s.untracked ?? 0);
|
|
55
|
+
app.notify(`⎇ ${s.branch ?? s.head} ±${dirty}${s.ahead ? ` ↑${s.ahead}` : ""}${s.behind ? ` ↓${s.behind}` : ""}`);
|
|
56
|
+
app.logEvent("git", `${cwd} · ${s.branch ?? s.head}${s.upstream ? ` → ${s.upstream}` : ""} · +${s.staged ?? 0}/~${s.unstaged ?? 0}/?${s.untracked ?? 0}${s.worktreeOf ? ` · worktree of ${s.worktreeOf}` : ""}`);
|
|
57
|
+
for (const w of s.worktrees ?? []) app.logEvent("git", ` ${w.path} ${w.branch ? `⎇ ${w.branch}` : w.head}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (op === "worktree" || op === "wt") {
|
|
61
|
+
if (a1 === "new" || a1 === "add") {
|
|
62
|
+
const name = rest[1];
|
|
63
|
+
if (!name) return app.notify("usage: /git worktree new <branch-name>");
|
|
64
|
+
const base = await app.client.gitStatus(cwd);
|
|
65
|
+
if (!base.repo) return app.notify("not a git repository");
|
|
66
|
+
const target = join(dirname(base.root ?? cwd), `${basename(base.root ?? cwd)}-${name}`);
|
|
67
|
+
const out = await app.client.gitOp(cwd, "worktree-add", { branch: name, target });
|
|
68
|
+
app.flash(`worktree: ${out.text.split("\n")[0] ?? target}`);
|
|
69
|
+
app.logEvent("git", `worktree add ${target} (${name})`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (a1 === "remove" || a1 === "rm") {
|
|
73
|
+
const target = rest[1];
|
|
74
|
+
if (!target || !isAbsolute(target)) return app.notify("usage: /git worktree remove /abs/path");
|
|
75
|
+
const out = await app.client.gitOp(cwd, "worktree-remove", { target });
|
|
76
|
+
app.flash(out.text);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const s = await app.client.gitStatus(cwd);
|
|
80
|
+
for (const w of s.worktrees ?? []) app.logEvent("git", `${w.path} ${w.branch ? `⎇ ${w.branch}` : w.head}`);
|
|
81
|
+
app.notify(`${s.worktrees?.length ?? 0} worktree${s.worktrees?.length === 1 ? "" : "s"} (log view)`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (!GIT_OPS.has(op)) return app.notify(`unknown git op "${op}" — status diff log fetch pull branch switch commit worktree`);
|
|
85
|
+
if (op === "switch" && !a1) return app.notify("usage: /git switch <branch>");
|
|
86
|
+
if (op === "commit" && !rest.length) return app.notify("usage: /git commit <message>");
|
|
87
|
+
const out = await app.client.gitOp(cwd, op as never, {
|
|
88
|
+
ref: op === "switch" ? a1 : undefined,
|
|
89
|
+
message: op === "commit" ? rest.join(" ") : undefined,
|
|
90
|
+
});
|
|
91
|
+
for (const line of out.text.split("\n").slice(0, 12)) if (line.trim()) app.logEvent("git", line);
|
|
92
|
+
app.notify(`${op} ✓ (log view: L)`);
|
|
93
|
+
} catch (e) {
|
|
94
|
+
app.notify(`git ${op}: ${(e as Error).message.slice(0, 80)}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
32
98
|
const KEYS: [string, string][] = [
|
|
33
99
|
["enter", "send; steers the agent while it works"],
|
|
34
100
|
["alt+enter", "queue as a follow-up"],
|
|
@@ -113,6 +179,9 @@ export async function runCommand(app: App, text: string): Promise<boolean> {
|
|
|
113
179
|
app.flash(`Agent renamed to ${arg}`);
|
|
114
180
|
});
|
|
115
181
|
break;
|
|
182
|
+
case "git":
|
|
183
|
+
await gitCommand(app, arg);
|
|
184
|
+
break;
|
|
116
185
|
case "pair":
|
|
117
186
|
await runPairing(app, parsePairArgs(arg));
|
|
118
187
|
break;
|