@tea-agent/loop-agent 0.24.3 → 0.24.4

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.
@@ -0,0 +1 @@
1
+ :root{--canvas: #f7f7f4;--canvas-soft: #fafaf7;--surface: #ffffff;--surface-muted: #f0efe9;--ink: #26251e;--body: #5a5852;--muted: #807d72;--muted-soft: #a09c92;--hairline: #e6e5e0;--hairline-strong: #cfcdc4;--orange: #f54e00;--orange-active: #d04200;--orange-soft: color-mix(in srgb, var(--orange) 12%, white);--blue: #3f6ea4;--green: #1f8a65;--amber: #a66a00;--red: #c72d53;--peach: #f1d8cb;--mint: #d8ead5;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-8: 32px;--radius-sm: 6px;--radius-md: 8px;--radius-lg: 12px;--radius-pill: 999px;--transition-fast: .16s ease;--transition-base: .22s ease;--shadow-panel: 0 1px 2px rgb(38 37 30 / 4%), 0 8px 24px rgb(38 37 30 / 3%);--shadow-soft: 0 10px 30px rgb(38 37 30 / 6%);--shell-max: 1680px;color-scheme:light;font-family:Segoe UI,PingFang SC,Hiragino Sans GB,Noto Sans SC,system-ui,-apple-system,sans-serif;line-height:1.5}*{box-sizing:border-box}html{background:radial-gradient(1200px 480px at 8% -10%,rgb(241 216 203 / 45%),transparent 55%),radial-gradient(900px 420px at 100% 0%,rgb(216 234 213 / 35%),transparent 50%),var(--canvas);min-height:100%}body{margin:0;min-height:100vh;min-width:320px;background:transparent;color:var(--ink);font-size:14px}button,input,select,textarea{font:inherit}a{color:inherit}code,.cmd,.kv dd code{font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace}.embedded-operator-nav{position:sticky;top:0;z-index:40;display:flex;gap:6px;align-items:center;margin:0;padding:8px max(24px,calc((100vw - var(--shell-max)) / 2));border-bottom:1px solid var(--hairline);background:color-mix(in srgb,var(--canvas) 92%,transparent);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);font-size:13px;font-weight:700;flex-shrink:0}.console-app{display:flex;flex-direction:column;min-height:100dvh}html:has(.console-app.is-chat-focus),body:has(.console-app.is-chat-focus){height:100%;overflow:hidden}.console-app.is-chat-focus{height:100dvh;max-height:100dvh;overflow:hidden}.console-shell{flex:1 1 auto;display:flex;flex-direction:column;min-height:0;width:min(100% - 48px,var(--shell-max));margin:0 auto;padding:var(--space-5) 0 var(--space-12, 48px)}.embedded-operator-nav a,.embedded-operator-nav span{padding:6px 12px;border-radius:var(--radius-pill);text-decoration:none;color:var(--ink)}.embedded-operator-nav a:hover,.embedded-operator-nav a:focus-visible{background:var(--surface);color:var(--orange-active);outline:none}.embedded-operator-nav span[aria-current=page]{background:var(--ink);color:#fff}.top-nav{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);margin:0 0 var(--space-5);padding:6px;border:1px solid var(--hairline);border-radius:var(--radius-pill);background:color-mix(in srgb,var(--surface) 88%,transparent);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);box-shadow:var(--shadow-panel)}.nav-link{appearance:none;display:inline-flex;align-items:center;min-height:34px;padding:0 14px;border:1px solid transparent;border-radius:var(--radius-pill);background:transparent;color:var(--body);font-size:13px;font-weight:600;cursor:pointer;transition:background-color var(--transition-fast),color var(--transition-fast),border-color var(--transition-fast),transform var(--transition-fast)}.nav-link:hover:not(:disabled),.nav-link:focus-visible:not(:disabled){background:var(--surface);color:var(--ink);outline:none}.nav-link-active{background:var(--ink);color:#fff}.nav-link-active:hover:not(:disabled),.nav-link-active:focus-visible:not(:disabled){background:var(--ink);color:#fff}.nav-link-external{margin-left:auto;border-color:var(--hairline);background:var(--surface);color:var(--orange-active)}.nav-link-external:hover:not(:disabled){border-color:var(--orange);background:var(--orange-soft);color:var(--orange-active)}.readiness{appearance:none;position:relative;display:inline-flex;align-items:center;gap:6px;margin-left:auto;min-height:34px;padding:0 28px 0 12px;border:1px solid var(--hairline);border-radius:var(--radius-pill);background:var(--surface);color:var(--ink);cursor:pointer;box-shadow:none;transition:background-color var(--transition-fast),border-color var(--transition-fast)}.readiness:hover{border-color:color-mix(in srgb,var(--ink) 18%,var(--hairline));background:color-mix(in srgb,var(--surface) 70%,var(--bg))}.readiness:focus-visible{outline:2px solid color-mix(in srgb,var(--orange) 45%,transparent);outline-offset:2px}.readiness:before{content:"";position:absolute;top:50%;right:10px;width:7px;height:7px;border-radius:50%;transform:translateY(-50%);background:var(--muted-soft)}.readiness[data-state=setup-required]{border-color:var(--hairline);background:var(--surface)}.readiness[data-state=setup-required]:before{background:var(--amber)}.readiness[data-state=ready]{border-color:var(--hairline);background:var(--surface)}.readiness[data-state=ready]:before{background:color-mix(in srgb,var(--green) 72%,white);box-shadow:0 0 0 3px color-mix(in srgb,var(--green) 12%,transparent)}.readiness[data-state=degraded]{border-color:var(--hairline);background:var(--surface)}.readiness[data-state=degraded]:before{background:var(--amber);box-shadow:0 0 0 3px color-mix(in srgb,var(--amber) 12%,transparent)}.readiness-label{margin:0;color:var(--muted);font-size:11px;font-weight:600;line-height:1;letter-spacing:.02em;text-transform:uppercase}.readiness strong{color:var(--ink);font-size:12px;font-weight:700;line-height:1}.readiness[data-state=ready] strong,.readiness[data-state=setup-required] strong,.readiness[data-state=degraded] strong{color:var(--ink)}.flash{display:flex;gap:var(--space-3);align-items:flex-start;margin:0 0 var(--space-4);padding:12px 14px;border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface);box-shadow:var(--shadow-panel);animation:flash-in .22s ease}.flash-error{border-color:color-mix(in srgb,var(--red) 35%,var(--hairline));background:color-mix(in srgb,var(--red) 6%,var(--surface));color:var(--red)}.flash-info{border-color:color-mix(in srgb,var(--blue) 30%,var(--hairline));background:color-mix(in srgb,var(--blue) 6%,var(--surface));color:var(--body)}.flash-label{flex:0 0 auto;min-width:3.5rem;font-size:12px;font-weight:700;letter-spacing:.02em}.flash-body{margin:0;overflow-wrap:anywhere}.muted{color:var(--muted)}.console-main{display:grid;gap:var(--space-4)}.console-main--fill{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:var(--space-3)}.console-shell.is-chat-focus{flex:1 1 auto;min-height:0;overflow:hidden;padding-bottom:var(--space-3)}.console-shell.is-chat-focus .top-nav{margin-bottom:var(--space-3);flex-shrink:0}.console-shell.is-chat-focus .console-main--fill{flex:1 1 auto;min-height:0;overflow:hidden}.panel--chat{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;padding:var(--space-3);overflow:hidden}.panel--chat .oc-shell{flex:1 1 auto;min-height:0;height:auto;overflow:hidden}.console-shell.is-chat-focus .panel--chat{padding:var(--space-2)}.console-shell.is-chat-focus .panel--chat .oc-shell{min-height:0;gap:var(--space-2);overflow:hidden}.inspect-split-panel{position:fixed;z-index:50;top:16px;right:16px;bottom:16px;width:min(420px,calc(100vw - 32px));display:grid;align-content:start;gap:var(--space-4);padding:var(--space-5);overflow:auto;border:1px solid var(--hairline);border-radius:var(--radius-lg);background:var(--surface);box-shadow:var(--shadow-soft)}.inspect-split-nodes{display:grid;gap:6px;margin:0;padding:0;list-style:none;max-height:220px;overflow:auto}.inspect-split-nodes li{display:grid;grid-template-columns:1fr auto;gap:var(--space-2);align-items:center;padding:6px 8px;border:1px solid var(--hairline);border-radius:var(--radius-sm);background:var(--canvas-soft);font-size:12px}.inspect-split-nodes li.is-active{border-color:var(--orange);background:var(--orange-soft)}.inspect-split-nodes code{color:var(--muted);font-size:11px}.inspect-split-header{display:flex;align-items:start;justify-content:space-between;gap:var(--space-3)}.inspect-split-header h2,.inspect-split-panel h3{margin:0}.inspect-split-kicker,.inspect-split-id{margin:0;color:var(--muted);font-size:12px}.inspect-split-facts{display:grid;gap:var(--space-2);margin:0}.inspect-split-facts div{display:grid;grid-template-columns:7rem 1fr;gap:var(--space-2);padding-bottom:var(--space-2);border-bottom:1px solid var(--hairline)}.inspect-split-facts dt{color:var(--muted)}.inspect-split-facts dd{margin:0;overflow-wrap:anywhere}.inspect-split-events{display:grid;gap:8px;margin:0;padding-left:18px;color:var(--body);font-size:12px}.panel{min-width:0;border:1px solid var(--hairline);background:color-mix(in srgb,var(--surface) 96%,transparent);border-radius:var(--radius-lg);padding:var(--space-6);box-shadow:var(--shadow-panel)}.panel h2{margin:0 0 var(--space-2);color:var(--ink);font-size:18px;font-weight:700;letter-spacing:-.01em}.panel-lead{margin:0 0 var(--space-5);color:var(--muted);font-size:13px}.panel h3{margin:0 0 var(--space-3);color:var(--ink);font-size:15px;font-weight:700}.empty-state{border:1px dashed var(--hairline-strong);border-radius:var(--radius-md);padding:var(--space-6);background:var(--canvas-soft);color:var(--body)}.empty-title{margin:0 0 var(--space-2);color:var(--ink);font-size:16px;font-weight:700}.checklist{margin:0 0 var(--space-4);padding:var(--space-4);border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--canvas-soft);list-style:none;color:var(--body)}.checklist li{position:relative;padding:6px 0 6px 22px}.checklist li:before{content:"";position:absolute;left:0;top:12px;width:8px;height:8px;border-radius:50%;background:var(--orange);opacity:.75}.kv{display:grid;gap:var(--space-1);margin:0}.kv div{display:grid;grid-template-columns:9rem 1fr;gap:var(--space-3);padding:10px 0;border-bottom:1px solid var(--hairline)}.kv div:last-child{border-bottom:none}.kv dt{color:var(--muted);font-size:12px;font-weight:600}.kv dd{margin:0;color:var(--ink);overflow-wrap:anywhere}code{font-size:.85rem;color:var(--ink)}.cmd{display:block;width:100%;margin:0;padding:var(--space-4);border:1px solid var(--hairline);border-radius:var(--radius-md);background:#f3efe8;color:var(--ink);font-size:12px;line-height:1.55;overflow-x:auto;white-space:pre-wrap;word-break:break-word;max-height:280px}.external-link{color:var(--orange);text-decoration:none;font-weight:600}.external-link:hover{text-decoration:underline}.error{color:var(--red)}.wizard-steps{display:grid;grid-template-columns:repeat(auto-fit,minmax(108px,1fr));gap:var(--space-2);list-style:none;padding:0;margin:0 0 var(--space-5)}.wizard-steps li{position:relative;min-height:52px;display:grid;align-content:center;gap:2px;border:1px solid var(--hairline);border-radius:var(--radius-md);padding:8px 10px;background:var(--canvas-soft);color:var(--muted);font-size:12px;font-weight:650;transition:border-color var(--transition-fast),background-color var(--transition-fast),color var(--transition-fast),box-shadow var(--transition-fast)}.wizard-steps li[data-done=true]{border-color:color-mix(in srgb,var(--green) 35%,var(--hairline));background:color-mix(in srgb,var(--mint) 45%,var(--surface));color:var(--green)}.wizard-steps li[data-active=true]{border-color:var(--orange);background:var(--surface);color:var(--ink);box-shadow:0 0 0 3px var(--orange-soft)}.wizard-steps .step-index{font-size:10px;font-weight:700;letter-spacing:.04em;color:var(--muted-soft)}.wizard-steps li[data-active=true] .step-index,.wizard-steps li[data-done=true] .step-index{color:inherit;opacity:.8}.wizard-steps .step-label{line-height:1.25}.field{display:grid;gap:var(--space-1);margin:0 0 var(--space-4);width:min(100%,520px)}.field span{font-size:12px;font-weight:600;color:var(--muted)}.field input,.field textarea,.field select{width:100%;border:1px solid var(--hairline-strong);border-radius:var(--radius-md);background:var(--surface);color:var(--ink);transition:border-color var(--transition-fast),box-shadow var(--transition-fast)}.field input{min-height:40px;padding:0 var(--space-3)}.field select{min-height:40px;padding:0 var(--space-3);cursor:pointer}.field textarea{min-height:120px;padding:var(--space-3);resize:vertical}.field input:focus-visible,.field textarea:focus-visible,.field select:focus-visible{outline:none;border-color:var(--orange);box-shadow:0 0 0 3px var(--orange-soft)}.field input:disabled,.field textarea:disabled,.field select:disabled{background:var(--surface-muted);color:var(--muted);cursor:not-allowed}.workflow-hint{margin:calc(var(--space-1) * -1) 0 var(--space-3);color:var(--muted);font-size:12px}.prd-drop-zone{position:relative;width:min(100%,720px);margin:0 0 var(--space-3);padding:var(--space-3);border:1px dashed var(--hairline-strong);border-radius:var(--radius-md);background:var(--surface);transition:border-color var(--transition-fast),background-color var(--transition-fast),box-shadow var(--transition-fast)}.prd-drop-zone.is-dragover{border-color:var(--orange);background:var(--orange-soft);box-shadow:0 0 0 3px var(--orange-soft)}.prd-upload-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2) var(--space-3);margin:0 0 var(--space-3)}.prd-upload-btn{display:inline-flex;align-items:center;cursor:pointer;margin:0}.prd-upload-btn.is-disabled,.prd-upload-btn:has(input:disabled){opacity:.55;cursor:not-allowed;pointer-events:none}.prd-upload-btn:focus-within{outline:none;box-shadow:0 0 0 3px var(--orange-soft)}.prd-upload-hint{font-size:12px}.prd-file-meta{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2) var(--space-3);margin:0 0 var(--space-3);padding:var(--space-2) var(--space-3);border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface-muted);font-size:12px;color:var(--ink)}.prd-file-list{list-style:none;margin:0 0 var(--space-3);padding:0;display:grid;gap:var(--space-2)}.prd-file-list li{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--space-2);padding:var(--space-2) var(--space-3);border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface-muted);font-size:12px}.prd-file-list li.is-primary{border-color:color-mix(in srgb,var(--orange) 40%,var(--hairline-strong))}.prd-file-item-name{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--space-1);min-width:0}.prd-file-primary-tag{display:inline-flex;align-items:center;padding:0 6px;border-radius:999px;background:var(--orange-soft);color:var(--orange);font-size:11px;font-weight:700}.prd-file-item-actions{display:inline-flex;flex-wrap:wrap;gap:var(--space-1)}.prd-file-item-actions .btn,.prd-file-clear{min-height:28px;padding:0 var(--space-2);font-size:12px}.prd-drop-hint{margin:0 0 var(--space-3);font-size:13px;font-weight:600;color:var(--orange)}.prd-text-field{width:100%;margin-bottom:0}.prd-file-error{width:min(100%,720px);margin:0 0 var(--space-3);padding:var(--space-2) var(--space-3);border:1px solid color-mix(in srgb,var(--orange) 45%,var(--hairline-strong));border-radius:var(--radius-md);background:var(--orange-soft);color:var(--ink);font-size:13px}.meta-row{display:grid;gap:6px;margin:0 0 var(--space-4);padding:var(--space-3) var(--space-4);border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--canvas-soft);font-size:13px}.meta-row code{font-size:12px}.cta-row,.recovery-cta-row{display:flex;flex-wrap:wrap;gap:var(--space-2);align-items:center}.cta-row{margin-top:var(--space-3)}.cta-row .cmd{flex:1 1 100%}.wizard-body{display:grid;gap:var(--space-3);justify-items:stretch;animation:panel-in .24s ease}.wizard-body>div{display:grid;gap:var(--space-3);justify-items:stretch}.wizard-body .field{width:100%;max-width:640px}.wizard-body .cta-row{margin-top:var(--space-1)}.interview-card{width:100%;padding:var(--space-5);border:1px solid var(--hairline);border-radius:var(--radius-lg);background:linear-gradient(180deg,var(--surface),var(--canvas-soft));box-shadow:var(--shadow-panel)}.interview-q{margin:0 0 var(--space-2);color:var(--ink)}.btn,button{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:40px;padding:0 16px;border:1px solid var(--hairline-strong);border-radius:var(--radius-md);background:var(--surface);color:var(--ink);font-size:13px;font-weight:650;cursor:pointer;transition:border-color var(--transition-fast),background-color var(--transition-fast),color var(--transition-fast),box-shadow var(--transition-fast),transform var(--transition-fast)}.btn:hover:not(:disabled),button:hover:not(:disabled){border-color:color-mix(in srgb,var(--ink) 18%,var(--hairline-strong));background:var(--canvas-soft);outline:none}.btn:focus-visible:not(:disabled),button:focus-visible:not(:disabled){outline:none;box-shadow:0 0 0 3px var(--orange-soft)}.btn:active:not(:disabled),button:active:not(:disabled){transform:translateY(1px)}.btn:disabled,button:disabled{opacity:.48;cursor:not-allowed;transform:none}.btn-primary,button.btn-primary{border-color:var(--orange);background:var(--orange);color:#fff;box-shadow:0 6px 16px #f54e002e}.btn-primary:hover:not(:disabled),button.btn-primary:hover:not(:disabled){border-color:var(--orange-active);background:var(--orange-active);color:#fff}.btn-secondary,button.btn-secondary{border-color:var(--hairline-strong);background:var(--surface);color:var(--ink)}.btn-ghost,button.btn-ghost{border-color:transparent;background:transparent;color:var(--body)}.btn-ghost:hover:not(:disabled),button.btn-ghost:hover:not(:disabled){background:var(--surface-muted);color:var(--ink)}.recovery-cta-row{margin-top:var(--space-3)}.recovery-confirm{margin-top:var(--space-3);padding:var(--space-4);border:1px solid color-mix(in srgb,var(--orange) 40%,var(--hairline));border-radius:var(--radius-md);background:color-mix(in srgb,var(--orange-soft) 55%,var(--surface))}.recovery-confirm h3{margin:0 0 var(--space-2);font-size:15px}.recovery-confirm .cta-row{margin-top:var(--space-3)}.recovery-report{margin-top:var(--space-3);padding:var(--space-4);border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface)}.recovery-report-header{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);margin-bottom:var(--space-3)}.recovery-report-header h3{margin:0;font-size:15px}.recovery-report-facts{display:grid;gap:var(--space-2);margin:0 0 var(--space-3)}.recovery-report-facts div{display:grid;grid-template-columns:5.5rem 1fr;gap:var(--space-2)}.recovery-report-facts dt{color:var(--muted);font-size:12px}.recovery-report-facts dd{margin:0;overflow-wrap:anywhere}.recovery-steps{margin:.5rem 0 .75rem 1.25rem;padding:0;line-height:1.5}.recovery-lane{margin-top:var(--space-4);padding-top:var(--space-3);border-top:1px solid var(--border, #ddd)}.recovery-lane h3{margin:0 0 .35rem;font-size:1rem}.recovery-cta-row button[disabled]{opacity:.4}@keyframes flash-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}@keyframes panel-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}@media(max-width:800px){.console-shell{width:min(100% - 24px,var(--shell-max));padding-top:var(--space-5)}.readiness,.nav-link-external{margin-left:0}.inspect-split-panel{inset:auto 0 0;width:100%;max-height:min(76vh,680px);border-radius:var(--radius-lg) var(--radius-lg) 0 0}.kv div{grid-template-columns:1fr}.wizard-steps{grid-template-columns:repeat(2,minmax(0,1fr))}}.oc-shell{display:flex;flex-direction:column;height:100%;min-height:0;gap:var(--space-3);position:relative;overflow:hidden}.oc-workspace{display:flex;flex:1 1 auto;min-height:0;gap:0;position:relative;overflow:hidden}.oc-main{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;min-height:0;overflow:hidden}.oc-sidebar{flex:0 0 280px;width:280px;min-width:0;display:flex;flex-direction:column;gap:var(--space-2);margin-right:var(--space-3);padding:var(--space-2);overflow:hidden;border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface)}.oc-sidebar.is-collapsed{flex:0 0 36px;width:36px;padding:6px;overflow:visible;align-items:center}.oc-sidebar-header-actions{display:inline-flex;align-items:center;gap:2px}.oc-sidebar-toggle{appearance:none;display:inline-grid;place-items:center;width:28px;height:28px;padding:0;border:1px solid transparent;border-radius:var(--radius-sm);background:transparent;color:var(--muted);cursor:pointer;transition:background-color var(--transition-fast),color var(--transition-fast),border-color var(--transition-fast)}.oc-sidebar-toggle:hover{color:var(--ink);background:color-mix(in srgb,var(--orange) 8%,var(--surface));border-color:var(--hairline)}.oc-sidebar-toggle:focus-visible{outline:2px solid color-mix(in srgb,var(--orange) 45%,transparent);outline-offset:1px}.oc-sidebar-toggle.is-collapsed{width:100%;height:28px}.oc-sidebar-section{display:flex;flex-direction:column;gap:6px;min-height:0}.oc-sidebar-section>header{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:13px;flex-shrink:0}.oc-sessions{flex:0 1 auto;max-height:min(34vh,320px)}.oc-session-scroll{min-height:0;max-height:min(28vh,260px);overflow:auto;padding:4px;border:1px solid var(--hairline);border-radius:var(--radius-md);background:linear-gradient(180deg,color-mix(in srgb,var(--canvas-soft) 70%,white),var(--canvas-soft))}.oc-session-group{display:flex;flex-direction:column;gap:2px}.oc-session-group>small{margin:4px 6px 2px;color:var(--muted-soft);font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.oc-session-item{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:2px;align-items:center;border-radius:6px;transition:background-color var(--transition-fast)}.oc-session-item:hover,.oc-session-item:focus-within{background:color-mix(in srgb,var(--orange) 8%,var(--surface))}.oc-session-item.is-active{background:var(--orange-soft)}.oc-session-main{min-width:0;padding:5px 6px;border:0;background:transparent;color:inherit;text-align:left;cursor:pointer}.oc-session-main strong,.oc-session-main span{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.oc-session-main strong{font-size:12.5px;font-weight:650;line-height:1.25}.oc-session-main span{color:var(--muted-soft);font-size:10px;margin-top:1px;font-variant-numeric:tabular-nums}.oc-session-actions{display:none;align-items:center;gap:0;padding-right:2px}.oc-session-item:hover .oc-session-actions,.oc-session-item:focus-within .oc-session-actions,.oc-session-item.is-active .oc-session-actions{display:inline-flex}.oc-session-menu{padding:3px 4px;border:0;background:transparent;color:var(--muted);cursor:pointer;font:inherit;font-size:10px;white-space:nowrap}.oc-session-menu:hover{color:var(--ink)}.oc-session-delete{color:color-mix(in srgb,var(--red) 75%,var(--muted))}.oc-session-delete:hover{color:var(--red)}.oc-explorer{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:6px;border-top:1px solid var(--hairline);padding-top:var(--space-3);margin-top:2px}.oc-explorer-hint{margin:0 2px;color:var(--muted-soft);font-size:10.5px;letter-spacing:.01em}.oc-tree-scroll{flex:1 1 auto;min-height:120px;max-height:min(52vh,560px);overflow:auto;padding:4px;border:1px solid var(--hairline);border-radius:var(--radius-md);background:linear-gradient(180deg,color-mix(in srgb,var(--canvas-soft) 70%,white),var(--canvas-soft))}.oc-tree,.oc-tree ul{list-style:none;padding:0;margin:0}.oc-tree-node{position:relative}.oc-tree-row{appearance:none;display:grid;grid-template-columns:12px 14px minmax(0,1fr);align-items:center;gap:6px;width:100%;min-height:28px;margin:0;padding:3px 8px;border:0;border-radius:6px;background:transparent;color:var(--ink);text-align:left;cursor:pointer;transition:background-color var(--transition-fast),color var(--transition-fast)}.oc-tree-row:hover{background:color-mix(in srgb,var(--orange) 8%,var(--surface))}.oc-tree-row:focus-visible{outline:2px solid color-mix(in srgb,var(--orange) 45%,transparent);outline-offset:1px}.oc-tree-chevron{color:var(--muted-soft);transition:transform var(--transition-fast);transform:rotate(0)}.oc-tree-chevron.is-open{transform:rotate(90deg)}.oc-tree-chevron-spacer{width:12px;height:12px}.oc-tree-glyph{flex-shrink:0;color:var(--amber)}.oc-tree-glyph.is-folder-open{color:color-mix(in srgb,var(--orange) 70%,var(--amber))}.oc-tree-glyph.is-file{color:var(--muted)}.oc-tree-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12.5px;line-height:1.25;font-variant-numeric:tabular-nums}.oc-tree-dir .oc-tree-label{font-weight:650;color:var(--ink)}.oc-tree-file .oc-tree-label{font-family:JetBrains Mono,SFMono-Regular,Consolas,Segoe UI Mono,monospace;font-size:11.5px;font-weight:500;color:var(--body)}.oc-tree-node.is-dot .oc-tree-label{color:var(--muted-soft);font-weight:500}.oc-tree-node.is-dot .oc-tree-glyph{opacity:.7}.oc-tree-children{position:relative;margin:0}.oc-tree-children:before{content:"";position:absolute;top:0;bottom:2px;left:var(--oc-tree-guide, 14px);width:1px;background:color-mix(in srgb,var(--hairline-strong) 55%,transparent);pointer-events:none}.oc-tree-empty,.oc-sidebar-error{margin:8px 6px;color:var(--muted);font-size:12px;line-height:1.45}.oc-sidebar-error{color:var(--red)}.oc-governance{margin-top:auto;border-top:1px solid var(--hairline);padding-top:8px}.oc-governance-links{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:4px}.oc-governance-links button{min-height:28px;padding:4px 6px;border:1px solid var(--hairline);border-radius:var(--radius-sm);background:var(--surface);color:var(--muted);cursor:pointer;font:inherit;font-size:11px;font-weight:600;text-align:center}.oc-governance-links button:hover{color:var(--ink);border-color:color-mix(in srgb,var(--ink) 18%,var(--hairline))}.oc-governance-panel button{border:0;background:transparent;color:var(--muted);cursor:pointer;font:inherit;font-size:11px}.oc-governance-panel{position:absolute;z-index:10;left:282px;bottom:0;width:min(440px,calc(100vw - 320px));max-height:75%;overflow:auto;padding:12px;border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface);box-shadow:var(--shadow-panel)}.oc-governance-panel header,.oc-governance-panel article,.oc-governance-panel label{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:5px 0}.oc-governance-panel small,.oc-governance-panel span{color:var(--muted);font-size:11px}.oc-governance-panel button:disabled{cursor:wait;opacity:.5}.oc-governance-error{color:var(--danger);font-size:12px}.oc-model-provider{display:block!important;border-bottom:1px solid var(--hairline)}.oc-model-provider>div,.oc-model-row,.oc-model-row>div{display:flex;align-items:center;justify-content:space-between;gap:8px}.oc-model-row{padding:4px 0 4px 10px}.oc-model-form{display:grid;gap:6px;margin-top:10px}.oc-model-form input{min-width:0;border:1px solid var(--hairline);border-radius:var(--radius-sm);background:var(--surface);color:var(--ink);padding:6px 8px;font:inherit;font-size:12px}.oc-model-form button{justify-self:start}.oc-model-test-result{padding:6px 8px;border-radius:var(--radius-sm);background:var(--surface-muted);font-size:12px}.oc-file-preview{position:absolute;z-index:4;inset:0;display:flex;flex-direction:column;overflow:hidden;border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface);box-shadow:var(--shadow-panel)}.oc-file-preview header{display:flex;align-items:center;gap:var(--space-2);padding:10px 12px;border-bottom:1px solid var(--hairline)}.oc-file-tabs{display:flex;min-width:0;gap:2px;overflow-x:auto}.oc-file-tabs button{max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--muted);padding:4px 7px;cursor:pointer}.oc-file-tabs button.is-active{background:var(--surface-muted);color:var(--ink);font-weight:650}.oc-file-preview header span{flex:1;color:var(--muted);font-size:11px}.oc-file-preview pre{flex:1;min-height:0;margin:0;padding:var(--space-3);overflow:auto;font-size:12px;line-height:1.5;white-space:pre-wrap}.oc-file-preview-loading{position:absolute;z-index:5;right:12px;top:12px;padding:6px 8px;border-radius:var(--radius-sm);background:var(--surface-muted);color:var(--muted);font-size:12px}.oc-process{display:flex;flex-direction:column;flex:0 0 min(360px,42%);width:min(360px,42%);min-width:280px;min-height:0;border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface);box-shadow:var(--shadow-panel);margin-left:var(--space-3);overflow:hidden;animation:panel-in .18s ease}.oc-process-head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);padding:10px 12px;border-bottom:1px solid var(--hairline);flex-shrink:0}.oc-process-title{display:inline-flex;align-items:center;gap:8px;font-weight:700;font-size:13px;color:var(--ink)}.oc-process-title-mark{width:8px;height:8px;border-radius:50%;background:var(--orange)}.oc-process-count{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:18px;padding:0 6px;border-radius:var(--radius-pill);background:var(--surface-muted);color:var(--muted);font-size:11px;font-weight:650}.oc-process-close{font-size:12px}.oc-process-timeline{flex:1 1 auto;min-height:0;overflow-y:auto;padding:var(--space-3);display:flex;flex-direction:column;gap:0}.oc-process-empty{padding:var(--space-4);color:var(--muted);font-size:12.5px;line-height:1.5;text-align:center}.oc-process-item{display:grid;grid-template-columns:20px 52px minmax(0,1fr);gap:8px;align-items:start;padding:4px 0}.oc-process-rail{position:relative;display:grid;place-items:center;min-height:34px}.oc-process-rail:before{position:absolute;top:0;bottom:0;width:1px;background:var(--hairline);content:""}.oc-process-item.is-first .oc-process-rail:before{top:50%}.oc-process-item.is-last .oc-process-rail:before{bottom:50%}.oc-process-dot{position:relative;z-index:1;width:8px;height:8px;border-radius:50%;background:var(--surface);border:2px solid color-mix(in srgb,var(--orange) 55%,var(--hairline))}.oc-process-item.is-running .oc-process-dot{border-color:var(--orange-active);background:var(--orange-soft)}.oc-process-item.is-error .oc-process-dot{border-color:var(--red);background:color-mix(in srgb,var(--red) 18%,var(--surface))}.oc-process-time{padding-top:10px;font-size:11px;color:var(--muted-soft);font-variant-numeric:tabular-nums;white-space:nowrap}.oc-process-body{min-width:0}.oc-process-toggle{cursor:pointer;font:inherit}.oc-process-toggle.is-active{border-color:color-mix(in srgb,var(--orange) 40%,var(--hairline));background:color-mix(in srgb,var(--orange) 10%,var(--surface));color:var(--orange-active)}.oc-process-toggle.is-running{border-color:color-mix(in srgb,var(--orange) 40%,var(--hairline));color:var(--orange-active)}.oc-process-toggle.is-error{border-color:color-mix(in srgb,var(--red) 35%,var(--hairline));background:color-mix(in srgb,var(--red) 8%,var(--surface));color:var(--red)}.oc-process-summary{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border:1px solid var(--hairline);border-radius:var(--radius-pill);background:var(--surface);color:var(--muted);font-size:11.5px;font-weight:650;cursor:pointer;font:inherit;max-width:min(420px,100%);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.oc-process-summary:hover,.oc-process-summary.is-open{border-color:color-mix(in srgb,var(--orange) 35%,var(--hairline));color:var(--orange-active)}.oc-process-summary.is-running{color:var(--orange-active);border-color:color-mix(in srgb,var(--orange) 40%,var(--hairline));background:var(--orange-soft)}.oc-process-summary.is-error{color:var(--red);border-color:color-mix(in srgb,var(--red) 35%,var(--hairline));background:color-mix(in srgb,var(--red) 8%,var(--surface))}.oc-process-summary.is-ok{color:var(--green);border-color:color-mix(in srgb,var(--green) 30%,var(--hairline))}@media(max-width:900px){.oc-sidebar{position:absolute;inset:0 auto 0 0;z-index:7;width:min(300px,calc(100% - 24px));margin-right:0;box-shadow:var(--shadow-soft)}.oc-sidebar.is-collapsed{position:static;width:auto}.oc-workspace.has-process .oc-process{position:absolute;top:0;right:0;bottom:0;z-index:8;width:min(360px,calc(100% - 24px));min-width:0;flex:none;margin-left:0;border-radius:var(--radius-md) 0 0 var(--radius-md);box-shadow:var(--shadow-soft)}}.oc-banner{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2) var(--space-3);padding:10px 14px;border:1px solid var(--hairline);border-radius:var(--radius-md);background:color-mix(in srgb,var(--surface) 94%,transparent);box-shadow:var(--shadow-panel)}.oc-shell>.oc-alert:first-child{margin-top:0}.oc-banner-title{display:inline-flex;align-items:center;gap:6px;font-weight:700;color:var(--ink)}.oc-banner-dot{width:8px;height:8px;border-radius:50%;background:var(--orange)}.oc-banner-body{color:var(--body);font-size:12.5px;line-height:1.45}.oc-banner-body strong{color:var(--ink)}.oc-cap-chips{display:inline-flex;flex-wrap:wrap;gap:6px;margin-left:auto;align-items:center}.oc-cap-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;border:1px solid color-mix(in srgb,var(--green) 30%,var(--hairline));border-radius:var(--radius-pill);background:color-mix(in srgb,var(--mint) 50%,var(--surface));color:var(--green);font-size:11.5px;font-weight:650}button.oc-cap-chip{font:inherit;cursor:pointer}.oc-tools-menu{position:relative}.oc-tools-menu--composer .oc-tools-catalog{top:auto;bottom:calc(100% + 8px)}.oc-tools-toggle.is-active{border-color:color-mix(in srgb,var(--orange) 40%,var(--hairline));background:color-mix(in srgb,var(--orange) 10%,var(--surface));color:var(--orange-active)}.oc-tools-catalog{position:absolute;top:calc(100% + 8px);right:0;z-index:20;width:min(420px,calc(100vw - 48px));max-height:min(480px,60vh);display:flex;flex-direction:column;border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface);box-shadow:var(--shadow-soft);animation:panel-in .16s ease;overflow:hidden}.oc-tools-catalog-head{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid var(--hairline);flex-shrink:0}.oc-tools-catalog-head strong{font-size:13px;color:var(--ink)}.oc-tools-catalog-meta{font-size:11.5px;color:var(--muted-soft)}.oc-tools-catalog-close{margin-left:auto;font-size:12px}.oc-tools-catalog-body{overflow-y:auto;padding:var(--space-3);display:flex;flex-direction:column;gap:var(--space-3)}.oc-tools-group-title{margin:0 0 8px;font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--muted-soft)}.oc-tools-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.oc-tools-item{padding:8px 10px;border:1px solid var(--hairline);border-radius:var(--radius-sm);background:color-mix(in srgb,var(--surface-muted) 55%,var(--surface))}.oc-tools-item-head{display:flex;align-items:center;gap:8px;margin-bottom:4px}.oc-tools-name{font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;font-size:12.5px;font-weight:650;color:var(--ink)}.oc-tools-kind{margin-left:auto;font-size:10.5px;font-weight:650;padding:1px 7px;border-radius:var(--radius-pill);color:var(--muted);background:var(--surface);border:1px solid var(--hairline)}.oc-tools-kind.is-explore,.oc-tools-kind.is-read{color:var(--green);border-color:color-mix(in srgb,var(--green) 28%,var(--hairline));background:color-mix(in srgb,var(--mint) 45%,transparent)}.oc-tools-kind.is-mutation{color:var(--orange-active);border-color:color-mix(in srgb,var(--orange) 35%,var(--hairline));background:var(--orange-soft)}.oc-tools-kind.is-long-running{color:var(--amber);border-color:color-mix(in srgb,var(--amber) 35%,var(--hairline));background:color-mix(in srgb,var(--amber) 10%,var(--surface))}.oc-tools-desc{margin:0;font-size:12px;line-height:1.45;color:var(--body)}.oc-tools-cli{margin:4px 0 0;font-size:11px;color:var(--muted-soft)}.oc-tools-cli code{font-size:11px;color:var(--muted)}.oc-alert{padding:8px 12px;border-radius:var(--radius-md);font-size:12.5px;line-height:1.45;animation:panel-in .18s ease}.oc-alert-warning{border:1px solid color-mix(in srgb,var(--amber) 35%,var(--hairline));background:color-mix(in srgb,var(--amber) 8%,var(--surface));color:var(--amber)}.oc-alert-error{border:1px solid color-mix(in srgb,var(--red) 35%,var(--hairline));background:color-mix(in srgb,var(--red) 7%,var(--surface));color:var(--red)}.oc-messages{flex:1 1 auto;min-height:0;overflow-y:auto;padding:var(--space-3) var(--space-3) var(--space-2);display:flex;flex-direction:column;gap:var(--space-3)}.oc-messages.has-thread:before{content:"";flex:1 1 auto;min-height:0}.oc-messages.is-empty{justify-content:center;align-items:center;padding:var(--space-5) var(--space-4) var(--space-4);background:radial-gradient(640px 300px at 50% 28%,rgb(241 216 203 / 34%),transparent 68%),radial-gradient(520px 240px at 78% 78%,rgb(216 234 213 / 26%),transparent 62%)}.oc-main.is-landing{position:relative}.oc-day-sep{display:flex;align-items:center;gap:var(--space-3);color:var(--muted-soft);font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;margin:var(--space-2) 0}.oc-day-sep:before,.oc-day-sep:after{content:"";flex:1;height:1px;background:var(--hairline)}.oc-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;flex:0 1 auto;gap:var(--space-5);text-align:center;width:min(100%,680px);padding:var(--space-2) var(--space-2) var(--space-4);animation:oc-empty-in .28s ease both}@keyframes oc-empty-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@media(prefers-reduced-motion:reduce){.oc-empty{animation:none}}.oc-empty-hero{display:flex;flex-direction:column;align-items:center;gap:var(--space-3)}.oc-empty-icon{display:grid;place-items:center;width:56px;height:56px;border-radius:50%;background:linear-gradient(145deg,color-mix(in srgb,var(--orange) 18%,white),color-mix(in srgb,var(--mint) 35%,white));color:var(--orange-active);font-size:26px;font-weight:800;font-family:var(--font-mono, "JetBrains Mono", monospace);box-shadow:0 0 0 1px color-mix(in srgb,var(--orange) 18%,transparent)}.oc-empty-copy{display:flex;flex-direction:column;align-items:center;gap:var(--space-2)}.oc-empty h3{margin:0;font-size:20px;font-weight:700;letter-spacing:-.02em;color:var(--ink)}.oc-empty p{margin:0;max-width:48ch;color:var(--muted);font-size:13px;line-height:1.55}.oc-suggestions{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2);width:100%}@media(max-width:560px){.oc-suggestions{grid-template-columns:1fr}}.oc-suggestion{appearance:none;display:flex;flex-direction:column;gap:4px;text-align:left;padding:12px 14px;border:1px solid var(--hairline);border-radius:var(--radius-md);background:color-mix(in srgb,var(--surface) 88%,transparent);color:var(--ink);cursor:pointer;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);transition:border-color var(--transition-fast),background var(--transition-fast),transform var(--transition-fast),box-shadow var(--transition-fast)}.oc-suggestion:hover:not(:disabled){border-color:var(--orange);background:var(--surface);box-shadow:0 0 0 3px var(--orange-soft);transform:translateY(-1px)}.oc-suggestion:disabled{opacity:.55;cursor:not-allowed}.oc-suggestion strong{font-size:13px;font-weight:700}.oc-suggestion span{color:var(--muted);font-size:12px;line-height:1.4}.oc-empty-tips{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--space-2) var(--space-3);margin:0;padding:0;list-style:none;color:var(--muted-soft);font-size:12px}.oc-empty-tips li{display:inline-flex;align-items:center;gap:6px}.oc-empty-tips kbd{display:inline-grid;place-items:center;min-width:1.4em;padding:1px 6px;border:1px solid var(--hairline);border-radius:5px;background:var(--surface);color:var(--body);font-family:var(--font-mono, "JetBrains Mono", monospace);font-size:11px;font-weight:600;line-height:1.4}.oc-empty-cta{margin-top:calc(var(--space-1) * -1)}.oc-row{display:flex;flex-direction:column;gap:4px;max-width:100%}.oc-row.is-user{align-self:flex-end;align-items:flex-end;width:auto;max-width:min(100%,720px)}.oc-row.is-assistant,.oc-row.is-tool{align-self:stretch;align-items:stretch;width:100%;max-width:100%}.oc-meta{display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--muted-soft)}.oc-row.is-assistant .oc-meta,.oc-row.is-user .oc-meta{width:100%;max-width:100%}.oc-message-copy{appearance:none;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:26px;height:26px;margin-left:auto;padding:0;border:1px solid transparent;border-radius:var(--radius-sm);background:transparent;color:var(--muted-soft);cursor:pointer;opacity:0;transition:opacity var(--transition-fast),background var(--transition-fast),color var(--transition-fast),border-color var(--transition-fast)}.oc-row:hover .oc-message-copy,.oc-row:focus-within .oc-message-copy,.oc-message-copy:focus-visible{opacity:1}.oc-message-copy:hover{color:var(--body);background:color-mix(in srgb,var(--ink) 6%,transparent);border-color:var(--hairline)}.oc-message-copy.is-copied{opacity:1;color:var(--green, #1a7f37);border-color:color-mix(in srgb,var(--green, #1a7f37) 35%,transparent)}@media(hover:none){.oc-message-copy{opacity:.5}}.oc-avatar{display:grid;place-items:center;width:22px;height:22px;border-radius:50%;font-size:11px;font-weight:700;flex-shrink:0}.oc-avatar-user{background:var(--ink);color:#fff}.oc-avatar-assistant{background:var(--orange);color:#fff}.oc-role{font-weight:700;color:var(--body)}.oc-time{font-variant-numeric:tabular-nums}.oc-bubble{padding:10px 14px;border-radius:var(--radius-lg);font-size:14px;line-height:1.6;overflow-wrap:anywhere;word-break:break-word;animation:bubble-in .16s ease}@keyframes bubble-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}.oc-bubble-user{background:var(--ink);color:#fff;border-bottom-right-radius:4px}.oc-bubble-assistant{background:var(--surface);color:var(--ink);border:1px solid var(--hairline);border-bottom-left-radius:4px;box-shadow:var(--shadow-panel)}.oc-bubble-tool{background:var(--canvas-soft);color:var(--body);border:1px solid var(--hairline)}.oc-bubble-error{border-color:color-mix(in srgb,var(--red) 40%,var(--hairline));background:color-mix(in srgb,var(--red) 7%,var(--surface));color:var(--red)}.oc-md{display:flex;flex-direction:column;gap:var(--space-2)}.oc-md>*:first-child{margin-top:0}.oc-md>*:last-child{margin-bottom:0}.oc-md p{margin:0}.oc-md h1,.oc-md h2,.oc-md h3,.oc-md h4{margin:var(--space-3) 0 var(--space-2);color:inherit;font-weight:700;line-height:1.3}.oc-md h1{font-size:1.3em}.oc-md h2{font-size:1.18em}.oc-md h3{font-size:1.06em}.oc-md h4{font-size:1em}.oc-md ul,.oc-md ol{margin:0;padding-left:1.4em;display:flex;flex-direction:column;gap:2px}.oc-md li{margin:0}.oc-md blockquote{margin:0;padding:2px 0 2px 12px;border-left:3px solid var(--hairline-strong);color:var(--body)}.oc-bubble-user .oc-md blockquote{border-left-color:#ffffff59;color:#ffffffd1}.oc-md code{font-size:.86em;padding:1px 5px;border-radius:4px;background:color-mix(in srgb,var(--ink) 8%,transparent)}.oc-bubble-user .oc-md code{background:#ffffff29}.oc-md pre{margin:0;padding:0;background:transparent}.oc-codeblock{margin:0;border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface-muted);overflow:hidden}.oc-bubble-assistant .oc-codeblock{background:#f3efe8}.oc-codeblock-head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);padding:5px 6px 5px 10px;border-bottom:1px solid var(--hairline);font-size:11px;color:var(--muted);background:color-mix(in srgb,var(--ink) 4%,transparent)}.oc-codeblock-lang{font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;font-weight:600;text-transform:lowercase}.oc-codeblock-copy{appearance:none;display:inline-flex;align-items:center;gap:5px;min-height:26px;padding:0 9px;border:1px solid transparent;border-radius:var(--radius-sm);background:transparent;color:var(--muted);font-size:11px;font-weight:600;cursor:pointer;transition:background var(--transition-fast),color var(--transition-fast),border-color var(--transition-fast)}.oc-codeblock-copy:hover{background:var(--surface);color:var(--ink);border-color:var(--hairline-strong)}.oc-codeblock pre{margin:0;padding:var(--space-3) var(--space-4);overflow-x:auto;font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;font-size:12.5px;line-height:1.55;white-space:pre;color:var(--ink)}.oc-md a{color:var(--orange);text-decoration:none;border-bottom:1px solid color-mix(in srgb,var(--orange) 40%,transparent)}.oc-md a:hover{border-bottom-color:var(--orange)}.oc-md hr{margin:var(--space-2) 0;border:none;border-top:1px solid var(--hairline-strong)}.oc-stream{white-space:pre-wrap;font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;font-size:13px;line-height:1.55}.oc-cursor{display:inline-block;width:7px;height:1.05em;margin-left:2px;vertical-align:text-bottom;background:var(--orange);animation:oc-blink 1s steps(2,start) infinite;border-radius:1px}@keyframes oc-blink{to{opacity:0}}.oc-tool{border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface);overflow:hidden;box-shadow:var(--shadow-panel);animation:panel-in .16s ease}.oc-tool.is-error{border-color:color-mix(in srgb,var(--red) 38%,var(--hairline))}.oc-tool.is-running{border-color:color-mix(in srgb,var(--orange) 40%,var(--hairline))}.oc-tool-head{display:flex;align-items:center;gap:var(--space-2);width:100%;padding:9px 12px;border:none;background:transparent;text-align:left;cursor:pointer;font:inherit;color:var(--ink)}.oc-tool-head:hover{background:var(--surface-muted)}.oc-tool-chevron{flex-shrink:0;width:12px;height:12px;transition:transform var(--transition-fast)}.oc-tool-head[aria-expanded=true] .oc-tool-chevron{transform:rotate(90deg)}.oc-tool-icon{display:grid;place-items:center;width:22px;height:22px;border-radius:6px;background:var(--surface-muted);color:var(--orange-active);flex-shrink:0}.oc-tool-name{font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;font-size:12.5px;font-weight:650;flex-shrink:0}.oc-tool-args{color:var(--muted);font-size:11.5px;font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-right:8px;min-width:0}.oc-tool-status{margin-left:auto;flex-shrink:0;font-size:11px;font-weight:650;padding:2px 8px;border-radius:var(--radius-pill)}.oc-tool-waiting{color:var(--muted);font-size:12px;animation:pulse 1.5s infinite}.oc-tool-status.is-running{color:var(--orange-active);background:var(--orange-soft)}.oc-tool-status.is-ok{color:var(--green);background:color-mix(in srgb,var(--mint) 60%,transparent)}.oc-tool-status.is-error{color:var(--red);background:color-mix(in srgb,var(--red) 10%,transparent)}.oc-tool-body{padding:0 var(--space-4) var(--space-3);border-top:1px solid var(--hairline)}.oc-tool-section{display:flex;flex-direction:column;gap:4px;padding-top:var(--space-3)}.oc-tool-label{font-size:11px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--muted-soft)}.oc-tool-code{margin:0;padding:var(--space-3);border-radius:var(--radius-sm);background:var(--surface-muted);font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;font-size:12px;line-height:1.5;white-space:pre-wrap;word-break:break-word;max-height:260px;overflow-y:auto;color:var(--body)}.oc-composer{flex:0 0 auto;flex-shrink:0;border-top:1px solid var(--hairline);padding:var(--space-2) var(--space-3) var(--space-3);background:color-mix(in srgb,var(--canvas) 92%,transparent);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);z-index:2}.oc-composer-inner{display:flex;flex-direction:column;gap:var(--space-2);max-width:1080px;margin:0 auto;position:relative}.console-shell.is-chat-focus .oc-composer-inner{max-width:none}.oc-composer-shell{position:relative;display:flex;align-items:flex-end;gap:var(--space-2);padding:8px 8px 8px 14px;border:1px solid var(--hairline-strong);border-radius:var(--radius-lg);background:var(--surface);box-shadow:var(--shadow-panel);transition:border-color var(--transition-fast),box-shadow var(--transition-fast)}.oc-composer-shell:focus-within{border-color:var(--orange);box-shadow:0 0 0 3px var(--orange-soft)}.oc-composer-shell.is-busy{opacity:.78}.oc-textarea{flex:1 1 auto;min-width:0;min-height:52px;max-height:280px;padding:8px 0;border:none;background:transparent;color:var(--ink);font:inherit;font-size:14px;line-height:1.5;resize:none;outline:none}.oc-textarea::placeholder{color:var(--muted-soft)}.oc-send{appearance:none;display:grid;place-items:center;width:38px;height:38px;flex-shrink:0;padding:0;border:1px solid var(--orange);border-radius:50%;background:var(--orange);color:#fff;cursor:pointer;transition:background var(--transition-fast),border-color var(--transition-fast),transform var(--transition-fast)}.oc-send:hover:not(:disabled){background:var(--orange-active);border-color:var(--orange-active)}.oc-send:active:not(:disabled){transform:scale(.94)}.oc-send:disabled{opacity:.4;cursor:not-allowed}.oc-stop{appearance:none;display:grid;place-items:center;width:38px;height:38px;flex-shrink:0;padding:0;border:1px solid var(--hairline-strong);border-radius:50%;background:var(--surface);color:var(--red);cursor:pointer;transition:background var(--transition-fast),border-color var(--transition-fast)}.oc-stop:hover{background:color-mix(in srgb,var(--red) 8%,var(--surface));border-color:var(--red)}.oc-composer-foot{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;font-size:11.5px;color:var(--muted)}.oc-composer-hint{display:inline-flex;align-items:center;gap:5px}.oc-kbd{display:inline-block;padding:1px 6px;border:1px solid var(--hairline-strong);border-bottom-width:2px;border-radius:4px;background:var(--surface);color:var(--body);font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;font-size:10.5px;line-height:1.4}.oc-operation-card{width:min(720px,calc(100% - 42px));margin:var(--space-3) auto;padding:var(--space-4);border:1px solid var(--hairline-strong);border-left:4px solid var(--blue);border-radius:var(--radius-lg);background:color-mix(in srgb,var(--surface) 96%,var(--blue) 4%);box-shadow:var(--shadow-sm)}.oc-operation-card.is-succeeded{border-left-color:var(--green)}.oc-operation-card.is-failed,.oc-operation-card.is-timed-out{border-left-color:var(--red)}.oc-operation-card.is-needs-reconcile{border-left-color:var(--orange)}.oc-operation-head,.oc-operation-actions{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3)}.oc-operation-kicker{margin-bottom:2px;color:var(--muted);font-size:10px;font-weight:700;letter-spacing:.12em;text-transform:uppercase}.oc-operation-state{padding:3px 9px;border-radius:999px;background:color-mix(in srgb,var(--blue) 12%,transparent);color:var(--blue);font-size:11px;font-weight:700}.oc-operation-state.is-succeeded{background:color-mix(in srgb,var(--green) 12%,transparent);color:var(--green)}.oc-operation-state.is-failed,.oc-operation-state.is-timed-out{background:color-mix(in srgb,var(--red) 12%,transparent);color:var(--red)}.oc-operation-state.is-needs-reconcile{background:color-mix(in srgb,var(--orange) 14%,transparent);color:var(--orange-active)}.oc-operation-id,.oc-operation-controller{margin-top:var(--space-2);color:var(--muted);font-size:12px;overflow-wrap:anywhere}.oc-operation-identities{display:flex;flex-wrap:wrap;gap:6px;margin-top:var(--space-3)}.oc-operation-identities span{padding:3px 7px;border:1px solid var(--hairline);border-radius:999px;background:var(--surface-raised);color:var(--muted);font-size:11px}.oc-operation-preview{max-height:150px;margin:var(--space-3) 0 0;padding:var(--space-2) var(--space-3);overflow:auto;border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface-sunken);color:var(--body);font-size:11px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}.oc-operation-preview.is-error{border-color:color-mix(in srgb,var(--red) 30%,var(--hairline));color:var(--red)}.oc-operation-actions{justify-content:flex-start;margin-top:var(--space-3)}.oc-operation-actions time{margin-left:auto;color:var(--muted-soft);font-size:11px}.oc-interview-start{margin:var(--space-3) auto}.oc-draft-card{margin:var(--space-3);padding:var(--space-4);border:1px solid var(--hairline);border-radius:var(--radius-lg);background:var(--surface-raised)}.oc-draft-card>header,.oc-task-kind-card{display:flex;align-items:center;gap:var(--space-3);justify-content:space-between}.oc-interview-bubble{margin:var(--space-3) 0;padding:var(--space-3);border-left:3px solid var(--accent);background:var(--surface-sunken)}.oc-interview-bubble textarea{width:100%;min-height:72px;margin:var(--space-2) 0;resize:vertical}.oc-interview-bubble .btn+.btn{margin-left:var(--space-2)}.oc-draft-card pre{max-height:300px;overflow:auto;white-space:pre-wrap;font-size:11px}.oc-assessment{margin:var(--space-3) 0;color:var(--orange)}.oc-assessment.is-fresh,.oc-task-kind-card.is-confirmed{color:var(--green)}.oc-task-kind-card{margin-top:var(--space-3);padding:var(--space-3);border:1px solid var(--hairline);border-radius:var(--radius-md)}.oc-task-kind-card small{color:var(--muted)}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.01ms!important}}.oc-human-gate-card{margin:12px 0;padding:14px;border:1px solid var(--border);border-left:4px solid var(--accent);border-radius:10px;background:var(--surface-raised)}.oc-human-gate-card header,.oc-human-gate-card footer{display:flex;justify-content:space-between;align-items:center;gap:12px}.oc-human-gate-card header div{display:grid;gap:3px}.oc-human-gate-card header span{font-size:12px;text-transform:uppercase}.oc-human-gate-challenges,.oc-human-gate-card ol{max-height:160px;overflow:auto}.oc-human-gate-card footer{margin-top:12px}.oc-human-gate-error{margin-top:8px;color:var(--danger)}.oc-human-gate-card.is-confirmed{border-left-color:var(--success)}.oc-task-context{margin:var(--space-3);padding:var(--space-3);border:1px solid var(--hairline);border-radius:var(--radius-lg);background:var(--surface-raised)}.oc-task-context>header{display:flex;justify-content:space-between;gap:var(--space-2);align-items:center}.oc-task-context>header div{display:grid;gap:2px}.oc-task-context>header small,.oc-task-context>header span,.oc-task-context-empty{color:var(--text-muted)}.oc-task-context-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-2);margin-top:var(--space-3)}.oc-task-context-grid>div{display:grid;gap:4px;min-width:0;padding:var(--space-2);border:1px solid var(--hairline);border-radius:var(--radius-md)}.oc-task-context-grid span,.oc-task-context-grid small{overflow-wrap:anywhere}.oc-shortcut-palette{position:absolute;z-index:12;left:0;right:0;bottom:calc(100% + 8px);display:grid;padding:6px;border:1px solid var(--hairline);border-radius:var(--radius-lg);background:var(--surface-raised);box-shadow:var(--shadow-lg)}.oc-shortcut-palette button{display:flex;gap:var(--space-3);align-items:center;border:0;border-radius:var(--radius-md);padding:9px 10px;color:inherit;background:transparent;text-align:left}.oc-shortcut-palette button:hover,.oc-shortcut-palette button.is-active{background:var(--surface-hover)}.oc-shortcut-palette button span{display:grid;gap:2px}.oc-shortcut-palette button small{color:var(--text-muted)}.oc-composer-inner{position:relative}@media(max-width:800px){.oc-task-context-grid{grid-template-columns:1fr}}.oc-file-palette{position:absolute;z-index:13;left:0;right:0;bottom:calc(100% + 8px);display:grid;max-height:240px;overflow:auto;padding:6px;border:1px solid var(--hairline);border-radius:var(--radius-lg);background:var(--surface-raised);box-shadow:var(--shadow-lg)}.oc-file-palette button{border:0;padding:8px 10px;color:inherit;background:transparent;text-align:left;border-radius:var(--radius-md)}.oc-file-palette button:hover{background:var(--surface-hover)}.oc-runtime-controls{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:8px}.oc-runtime-controls select{max-width:220px;border:1px solid var(--hairline);border-radius:var(--radius-md);padding:5px 8px;color:inherit;background:var(--surface-raised)}.oc-runtime-controls select.oc-model-select{max-width:min(100%,360px);font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace;font-size:12px}.oc-runtime-controls .oc-process-summary{margin-left:0}.oc-usage{color:var(--text-muted);font-size:12px;font-variant-numeric:tabular-nums}.oc-runtime-context-menu{position:relative;margin-left:auto;flex-shrink:0}.oc-runtime-context-toggle.is-active{color:var(--orange-active);background:color-mix(in srgb,var(--orange) 10%,var(--surface))}.oc-runtime-context-panel{position:absolute;right:0;bottom:calc(100% + 8px);z-index:16;width:min(400px,calc(100vw - 32px));max-height:min(360px,45vh);display:flex;flex-direction:column;border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface);box-shadow:var(--shadow-soft);animation:panel-in .16s ease;overflow:hidden}.oc-runtime-context-head{display:flex;align-items:center;gap:8px;flex-shrink:0;padding:10px 12px;border-bottom:1px solid var(--hairline);background:var(--surface-raised)}.oc-runtime-context-head strong{font-size:13px}.oc-runtime-context-meta{flex:1;font-size:11px;color:var(--muted)}.oc-runtime-context-close{flex-shrink:0;min-height:28px;padding:0 8px;font-size:12px}.oc-runtime-context-body{flex:1;min-height:0;overflow:auto;padding:10px 12px 12px}.oc-runtime-context-dl{margin:0;display:grid;gap:10px;font-size:12.5px}.oc-runtime-context-dl>div{display:grid;gap:3px}.oc-runtime-context-dl dt{margin:0;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}.oc-runtime-context-dl dd{margin:0;line-height:1.45;color:var(--body)}.oc-runtime-context-hash{display:inline-block;margin-left:6px;font-size:11px;color:var(--muted)}.oc-runtime-context-skills{margin:4px 0 0;padding:0;list-style:none;display:grid;gap:4px}.oc-runtime-context-skills li{display:flex;align-items:baseline;justify-content:space-between;gap:8px}.oc-runtime-context-skills span{font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums}.oc-runtime-context-raw{margin-top:12px;padding-top:10px;border-top:1px solid var(--hairline);font-size:12px;color:var(--muted)}.oc-runtime-context-raw summary{cursor:pointer;-webkit-user-select:none;user-select:none}.oc-runtime-context-raw pre{margin:8px 0 0;max-height:140px;overflow:auto;padding:8px;border:1px solid var(--hairline);border-radius:var(--radius-sm);background:var(--canvas-soft);font-size:11px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}@media(max-width:800px){.oc-runtime-context-menu{margin-left:0;width:100%}.oc-runtime-context-panel{left:0;right:0;width:auto}}.oc-artifact-card{max-width:100%;margin:12px 0;padding:14px;overflow:hidden;border:1px solid var(--hairline);border-radius:var(--radius-lg);background:var(--surface-raised)}.oc-artifact-card header{display:flex;justify-content:space-between;gap:12px}.oc-artifact-card pre{max-height:320px;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere}.oc-artifact-card iframe{width:100%;min-height:280px;border:1px solid var(--hairline);background:#fff}.oc-load-history{display:block;margin:8px auto}.oc-attach-button{display:inline-flex;min-height:24px;align-items:center;color:var(--muted);cursor:pointer;font-size:12px}.oc-composer-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:10px 14px;margin-bottom:6px;font-size:11.5px;color:var(--muted)}.oc-composer-toolbar .oc-attach-button{margin-bottom:0}.oc-attachment-list{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:6px}.oc-attachment-list span{display:inline-flex;align-items:center;gap:5px;max-width:100%;padding:4px 8px;border:1px solid var(--hairline);border-radius:999px;overflow-wrap:anywhere}.oc-attachment-list button{border:0;color:inherit;background:transparent;cursor:pointer}@media(max-width:640px){.oc-runtime-controls,.oc-composer-foot{align-items:stretch;flex-direction:column}.oc-runtime-controls select,.oc-runtime-controls button{width:100%;max-width:100%;min-height:44px}.oc-artifact-card{width:100%}.oc-composer{padding-inline:8px}}.oc-branch-actions{display:inline-flex;gap:.4rem;align-items:center;flex-wrap:wrap}.oc-branch-badge{padding:.15rem .45rem;border-radius:999px;border:1px solid currentColor;font-size:.72rem}.oc-branch-badge.is-branch{color:#f6ad55}.oc-branch-badge.is-mainline{color:#68d391}
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Loop 操作台 · Operator Console</title>
7
- <script type="module" crossorigin src="/assets/index-Cv68Ge5n.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-Dp_2sKGk.css">
7
+ <script type="module" crossorigin src="/assets/index-CSRIhuzh.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-Yyn3ynVv.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Client-side PRD file import helpers for Console Happy Path.
3
+ * Validates extension/size, decodes strict UTF-8, composes multi-file bodies,
4
+ * and budgets operator JSON payloads.
5
+ */
6
+ export const MAX_PRD_FILE_BYTES = 8 * 1024 * 1024;
7
+ export const MAX_OPERATOR_JSON_BYTES = 10 * 1024 * 1024;
8
+ export const MAX_PRD_FILES = 8;
9
+ export const PRD_FILE_ACCEPT = ".md,.markdown,.txt,text/markdown,text/plain";
10
+ export const ALLOWED_PRD_EXTENSIONS = [".md", ".markdown", ".txt"];
11
+ const EXT_RE = /\.[^.]+$/;
12
+ export function prdFileExtension(fileName) {
13
+ const match = EXT_RE.exec(fileName.trim());
14
+ return match ? match[0].toLowerCase() : "";
15
+ }
16
+ export function isAllowedPrdExtension(fileName) {
17
+ const ext = prdFileExtension(fileName);
18
+ return ALLOWED_PRD_EXTENSIONS.includes(ext);
19
+ }
20
+ export function formatByteSize(bytes) {
21
+ if (!Number.isFinite(bytes) || bytes < 0)
22
+ return "0 B";
23
+ if (bytes < 1024)
24
+ return `${bytes} B`;
25
+ if (bytes < 1024 * 1024) {
26
+ const kib = bytes / 1024;
27
+ return `${kib >= 10 ? kib.toFixed(0) : kib.toFixed(1)} KiB`;
28
+ }
29
+ const mib = bytes / (1024 * 1024);
30
+ return `${mib >= 10 ? mib.toFixed(1) : mib.toFixed(2)} MiB`;
31
+ }
32
+ function fail(code, message) {
33
+ return { ok: false, code, message };
34
+ }
35
+ /**
36
+ * Validate an incoming FileList before decoding.
37
+ * `existingCount` supports append mode (existing + incoming ≤ MAX_PRD_FILES).
38
+ */
39
+ export function validatePrdFileList(files, options) {
40
+ const list = files ? Array.from(files) : [];
41
+ const maxFiles = options?.maxFiles ?? MAX_PRD_FILES;
42
+ const existingCount = options?.existingCount ?? 0;
43
+ if (list.length === 0) {
44
+ return fail("file-count", "请选择至少一个 .md / .markdown / .txt 文件");
45
+ }
46
+ if (existingCount + list.length > maxFiles) {
47
+ return fail("file-count", `最多支持 ${maxFiles} 个文件(当前已有 ${existingCount} 个)`);
48
+ }
49
+ return null;
50
+ }
51
+ /**
52
+ * Validate and decode a single PRD text file as strict UTF-8.
53
+ */
54
+ export async function importPrdFile(file) {
55
+ const fileName = file.name?.trim() || "untitled.md";
56
+ if (!isAllowedPrdExtension(fileName)) {
57
+ return fail("unsupported-extension", "仅支持 .md、.markdown、.txt 文件");
58
+ }
59
+ if (file.size > MAX_PRD_FILE_BYTES) {
60
+ return fail("file-too-large", `文件不能超过 ${formatByteSize(MAX_PRD_FILE_BYTES)}`);
61
+ }
62
+ let buffer;
63
+ try {
64
+ buffer = await file.arrayBuffer();
65
+ }
66
+ catch {
67
+ return fail("read-failed", "文件读取失败,请重试或直接粘贴");
68
+ }
69
+ let text;
70
+ try {
71
+ text = new TextDecoder("utf-8", { fatal: true }).decode(buffer);
72
+ }
73
+ catch {
74
+ return fail("invalid-utf8", "文件不是有效 UTF-8,请另存为 UTF-8");
75
+ }
76
+ if (text.trim().length === 0) {
77
+ return fail("empty-content", "文件内容不能为空或只有空白");
78
+ }
79
+ const textBytes = new TextEncoder().encode(text).byteLength;
80
+ return {
81
+ ok: true,
82
+ text,
83
+ fileName,
84
+ sourceBytes: file.size,
85
+ textBytes,
86
+ };
87
+ }
88
+ /**
89
+ * Decode multiple files; any failure aborts the whole batch (no partial list).
90
+ */
91
+ export async function importPrdFiles(files) {
92
+ if (files.length === 0) {
93
+ return fail("file-count", "请选择至少一个 .md / .markdown / .txt 文件");
94
+ }
95
+ if (files.length > MAX_PRD_FILES) {
96
+ return fail("file-count", `最多支持 ${MAX_PRD_FILES} 个文件`);
97
+ }
98
+ const documents = [];
99
+ for (const file of files) {
100
+ const result = await importPrdFile(file);
101
+ if (!result.ok)
102
+ return result;
103
+ documents.push({
104
+ fileName: result.fileName,
105
+ text: result.text,
106
+ sourceBytes: result.sourceBytes,
107
+ textBytes: result.textBytes,
108
+ });
109
+ }
110
+ return { ok: true, documents };
111
+ }
112
+ /** Browser-safe title for compose header (no Node deps). */
113
+ export function extractComposeTitle(text, fallbackFileName) {
114
+ const lines = text.split(/\r?\n/);
115
+ for (const line of lines) {
116
+ const heading = /^(#{1,6})\s+(.+)$/.exec(line.trim());
117
+ if (heading?.[2])
118
+ return heading[2].trim();
119
+ }
120
+ for (const line of lines) {
121
+ const trimmed = line.trim();
122
+ if (!trimmed)
123
+ continue;
124
+ if (/^---+$/.test(trimmed))
125
+ continue;
126
+ if (/^```/.test(trimmed))
127
+ continue;
128
+ if (/^<!--/.test(trimmed))
129
+ continue;
130
+ return trimmed.replace(/^>\s+/, "").slice(0, 120);
131
+ }
132
+ const base = fallbackFileName.replace(EXT_RE, "").trim();
133
+ return base || "需求";
134
+ }
135
+ export function slugifyDocumentName(value) {
136
+ const trimmed = value.trim().toLowerCase();
137
+ const slug = trimmed
138
+ .replace(/[^a-z0-9._-]+/g, "-")
139
+ .replace(/^-+|-+$/g, "")
140
+ .replace(/-{2,}/g, "-");
141
+ return slug || "requirement";
142
+ }
143
+ /**
144
+ * Logical import-prd --name from a file name. Non-ASCII-only stems become part-N.
145
+ */
146
+ export function documentNameFromFileName(fileName, index) {
147
+ const stem = fileName.replace(EXT_RE, "").trim();
148
+ const slug = slugifyDocumentName(stem);
149
+ if (slug === "requirement" && /[^\u0000-\u007f]/.test(stem)) {
150
+ return `part-${index + 1}`;
151
+ }
152
+ if (!slug || slug === "requirement") {
153
+ return stem ? `part-${index + 1}` : `part-${index + 1}`;
154
+ }
155
+ return slug;
156
+ }
157
+ export function ensureUniqueDocumentNames(docs) {
158
+ const used = new Set();
159
+ return docs.map((doc, index) => {
160
+ let name = documentNameFromFileName(doc.fileName, index);
161
+ if (used.has(name)) {
162
+ let n = 2;
163
+ while (used.has(`${name}-${n}`))
164
+ n += 1;
165
+ name = `${name}-${n}`;
166
+ }
167
+ used.add(name);
168
+ return {
169
+ name,
170
+ content: doc.text,
171
+ fileName: doc.fileName,
172
+ };
173
+ });
174
+ }
175
+ /**
176
+ * Compose editable PRD markdown from loaded documents.
177
+ * Single file: body equals original text (no source wrapper).
178
+ * Multiple: deterministic sections with primary-driven top title.
179
+ */
180
+ export function composePrdDocuments(docs, options) {
181
+ if (docs.length === 0)
182
+ return "";
183
+ if (docs.length === 1) {
184
+ return docs[0].text;
185
+ }
186
+ const primaryIndex = Math.min(Math.max(0, options?.primaryIndex ?? 0), docs.length - 1);
187
+ const primary = docs[primaryIndex];
188
+ const title = extractComposeTitle(primary.text, primary.fileName);
189
+ const names = docs.map((d) => d.fileName).join(", ");
190
+ const parts = [
191
+ `<!-- composed-from: ${names} -->`,
192
+ "",
193
+ `# ${title}`,
194
+ "",
195
+ ];
196
+ for (const doc of docs) {
197
+ parts.push(`## 来源:${doc.fileName}`, "", doc.text.trim(), "");
198
+ }
199
+ return `${parts.join("\n").trimEnd()}\n`;
200
+ }
201
+ /**
202
+ * Estimate whether an operator POST body fits under the HTTP JSON limit.
203
+ * Mirrors `postOperator` envelope shape.
204
+ */
205
+ export function operatorRequestFits(action, actionParams, options) {
206
+ const envelope = {
207
+ action,
208
+ actionParams,
209
+ };
210
+ if (options?.clientRequestId) {
211
+ envelope.clientRequestId = options.clientRequestId;
212
+ }
213
+ const bytes = new TextEncoder().encode(JSON.stringify(envelope)).byteLength;
214
+ return bytes <= MAX_OPERATOR_JSON_BYTES;
215
+ }
216
+ export function operatorRequestTooLargeMessage() {
217
+ return "最终内容导致请求体超过服务端限制,请缩小文档或减少文件数量";
218
+ }
@@ -1076,10 +1076,10 @@ export function renderBackendTestFacts(input) {
1076
1076
  "",
1077
1077
  "</details>",
1078
1078
  "",
1079
- "## 成熟度证据",
1079
+ "## 覆盖率证据",
1080
1080
  "",
1081
1081
  "- Code coverage: unavailable unless a separate validated coverage artifact exists.",
1082
- "- Stability: unavailable unless at least five independent runs are recorded.",
1082
+ "- Coverage is reported only when a separate validated coverage artifact exists; this single execution does not produce a coverage result.",
1083
1083
  ].join("\n") + "\n");
1084
1084
  }
1085
1085
  export async function writeRunReport(runDir, fileName, content) {
@@ -3190,13 +3190,12 @@ function buildTestRetrospectNode(sources) {
3190
3190
  "- AC coverage ratio / case counts MUST come from contracts/backend-test-case-manifest.json coverageSummary (or gate-derived fields). Do NOT invent coverage %.",
3191
3191
  "- Automation coverage MUST use coverageSummary.generatedCount / coverageSummary.caseCount. If either field is missing, write unavailable; do not estimate.",
3192
3192
  "- Code coverage MUST come only from the validated contracts/code-coverage-v1.json artifact generated by coverage.py/pytest-cov or JaCoCo. Show line, branch, function/method, covered, total, ratio, threshold, status, source scope, requirement IDs, tool, commit, and artifact hash.",
3193
- "- Stability MUST come from independent Stability Evidence: use successfulRuns / recordedRuns, same suite/version, and require n≥5; a single run is unavailable.",
3194
3193
  "- Use classify-backend-test-result-pi JSON as interpretive evidence only.",
3195
3194
  "- NEVER rewrite a failed result as passed. Result v1 is authoritative for testOutcome; pipeline completion and L-5 readiness are separate conclusions.",
3196
3195
  "",
3197
3196
  "## Report Structure:",
3198
3197
  "1. Maturity Rating with rationale",
3199
- "2. Test Coverage Summary (Result v1 pass rate, AC coverage, automation coverage, code coverage, and stability evidence)",
3198
+ "2. Test Coverage Summary (Result v1 pass rate, AC coverage, automation coverage, and code coverage)",
3200
3199
  "3. Failed Test Analysis (failure/error details, category, confidence, evidence, and owner direction)",
3201
3200
  "4. Defects (local Bug ledger in the same report directory; unavailable when absent)",
3202
3201
  "5. Risks (Critical/High/Medium/Low, impact, controls, residual risk, treatment; Critical risks block L-5, High risks do not automatically block)",
@@ -3204,7 +3203,7 @@ function buildTestRetrospectNode(sources) {
3204
3203
  "7. L-5 conclusion with blocking items",
3205
3204
  "",
3206
3205
  "## Rating Criteria:",
3207
- "- L-5 ready requires pass rate=100%, AC coverage=100%, automation coverage≥90%, stability≥95% with n≥5, line coverage≥80%, branch coverage≥70%, skipped=0, and no blocking Critical risk.",
3206
+ "- L-5 ready requires pass rate=100%, AC coverage=100%, automation coverage≥90%, line coverage≥80%, branch coverage≥70%, skipped=0, and no blocking Critical risk.",
3208
3207
  "- Any required metric fail or unavailable means L-5 not-ready. Function/method coverage is displayed but not a gate. Preserve the existing A/B/C/D single-run rating separately.",
3209
3208
  "",
3210
3209
  "## Constraints:",
@@ -3257,13 +3256,13 @@ function buildL5MetricsNode(sources) {
3257
3256
  outputContract: "Exactly one JSON object with status=ready|not-ready, metrics, and blockingItems; no file writes.",
3258
3257
  subtask_prompt: [
3259
3258
  "You are the independent L-5 metrics node at the end of the existing backend-test DAG.",
3260
- "The direct upstream test-retrospect-pi output is the primary report to assess. Read it together with the run-owned Result v1, Case Manifest v1, Code Coverage v1, and Stability Evidence artifacts when present.",
3259
+ "The direct upstream test-retrospect-pi output is the primary report to assess. Read it together with the run-owned Result v1, Case Manifest v1, and Code Coverage v1 artifacts when present.",
3261
3260
  "Do not create a new DAG, rewrite the retrospective report, change test outcome, or modify any repository file.",
3262
3261
  "Return exactly one JSON object and no surrounding prose.",
3263
- 'Required shape: {"status":"ready"|"not-ready","metrics":{"passRate":metric,"acCoverage":metric,"automationCoverage":metric,"stability":metric,"lineCoverage":metric,"branchCoverage":metric,"skipped":metric,"criticalRisks":metric},"blockingItems":[string]}.',
3262
+ 'Required shape: {"status":"ready"|"not-ready","metrics":{"passRate":metric,"acCoverage":metric,"automationCoverage":metric,"lineCoverage":metric,"branchCoverage":metric,"skipped":metric,"criticalRisks":metric},"blockingItems":[string]}.',
3264
3263
  "Each metric must contain numerator, denominator, ratio, threshold, status=pass|fail|unavailable, and reason (null only when passed).",
3265
3264
  "Use only explicit evidence. Missing or invalid required evidence is unavailable, never zero or an estimate.",
3266
- "L-5 ready requires pass rate=100%, AC coverage=100%, automation coverage>=90%, stability>=95% with n>=5, line coverage>=80%, branch coverage>=70%, skipped=0, and zero blocking Critical risks.",
3265
+ "L-5 ready requires pass rate=100%, AC coverage=100%, automation coverage>=90%, line coverage>=80%, branch coverage>=70%, skipped=0, and zero blocking Critical risks.",
3267
3266
  "Function/method coverage is display-only and does not gate L-5. Preserve the distinction between L-5 maturity and the Result v1 outcome gate.",
3268
3267
  ].join("\n\n"),
3269
3268
  };
@@ -3346,7 +3345,7 @@ async function buildBackendTestHybridDag(sources) {
3346
3345
  'mkdir -p "${HARNESS_DAG_RUN_DIR}/reports"',
3347
3346
  'echo "pytest targets are resolved at runtime from final Markdown 自动化映射"',
3348
3347
  ].join("; ");
3349
- const execute = shellNode("execute-backend-pytest-and-html-report-shell", [traceability.id], "markdown-execute-html", "Resolve the final Markdown Automation Notes/自动化映射 to a unique, safe set of testcase/**/test_*.py targets and execute only those scripts exactly once. Validate JUnit, then render the primary self-contained Chinese HTML report from the same JUnit plus final Markdown case metadata without rerun. Keep 测试结论, quality status, failure overview, and a polished per-case result card with concise scenario, automation test name, result, duration, and redacted bounded HTTP request parameters/response results for both passed and failed cases. Do not render a technical/execution evidence section in HTML; retain auditable paths and hashes in facts.", "One scoped pytest execution over Markdown-mapped scripts producing valid JUnit with per-case captured output, self-contained HTML, reports/backend-test.md and reports/backend-test-facts.md; exit 0/1 with valid evidence continues.", [pytestCommand], 300000);
3348
+ const execute = shellNode("execute-backend-pytest-and-html-report-shell", [traceability.id], "markdown-execute-html", "Resolve the final Markdown Automation Notes/自动化映射 to a unique, safe set of testcase/**/test_*.py targets and execute only those scripts exactly once. Validate JUnit, then render the primary self-contained Chinese HTML report from the same JUnit plus final Markdown case metadata without rerun. Keep 测试结论, quality status, failure overview, and a polished per-case result card with concise scenario, automation test name, result, duration, and redacted bounded HTTP request parameters/response results for both passed and failed cases. Do not render a technical/execution evidence section in HTML; retain auditable paths and hashes in facts.", "One scoped pytest execution over Markdown-mapped scripts producing valid JUnit with per-case captured output, self-contained reports/backend-test.html and reports/backend-test.md, plus internal reports/backend-test-facts.md evidence; exit 0/1 with valid evidence continues.", [pytestCommand], 300000);
3350
3349
  if (execute.shell) {
3351
3350
  execute.shell.envAllowlist = collectBackendTestShellEnvAllowlist(sources);
3352
3351
  }
@@ -3362,8 +3361,8 @@ async function buildBackendTestHybridDag(sources) {
3362
3361
  "Generate the final Markdown report from upstream facts and run-owned environment, advisory case-validation, advisory traceability, JUnit and HTML evidence. Do not emit JSON.",
3363
3362
  "Use this exact human-facing section order: 测试结论 → 执行概览 → 质量校验 → 失败分析 → 风险与建议 → 证据与 L-5. Put the decision and key numbers first, use compact tables/bullets, and keep headings concise. Do not paste entire upstream reports, duplicate per-case tables already present in facts, or repeat the same evidence in multiple sections; link to paths/hashes and quote only the findings needed for the conclusion.",
3364
3363
  "Always state the exact PASS/FAIL status and findings from nodes 4 and 6. Their FAIL status does not block pytest, but it must remain visible as a quality/traceability risk and must never be rewritten as PASS.",
3365
- "Include environment, case quality/review, automation mapping, exact pytest facts, failure classification/analysis, risks, regression recommendations, evidence paths/hashes, coverage/stability availability, and L-5 READY/NOT READY.",
3366
- "Never override Shell/JUnit facts. One run cannot prove FlakyTest. Missing coverage/stability is Unavailable. L-5 requires pass=100%, AC=100%, automation>=90%, stability>=95% n>=5, line>=80%, branch>=70%, skipped=0 and no blocking Critical risk.",
3364
+ "Include environment, case quality/review, automation mapping, exact pytest facts, failure classification/analysis, risks, regression recommendations, evidence paths/hashes, coverage availability, and L-5 READY/NOT READY.",
3365
+ "Never override Shell/JUnit facts. L-5 requires pass=100%, AC=100%, automation>=90%, line>=80%, branch>=70%, skipped=0 and no blocking Critical risk.",
3367
3366
  canWriteReport ? "Write only under docs/test-reports/**." : "Keep the full report in assistant output.",
3368
3367
  ].join("\n\n"),
3369
3368
  };
@@ -19,9 +19,6 @@ export function computeL5ReportMetrics(input) {
19
19
  automationCoverage: input.manifest.coverageSummary
20
20
  ? ratioMetric(input.manifest.coverageSummary.generatedCount, input.manifest.coverageSummary.caseCount, 0.9, "automation-coverage")
21
21
  : unavailable("case-manifest-coverage-missing", null, null, 0.9),
22
- stability: input.stability?.status === "available" && input.stability.ratio !== null
23
- ? { numerator: input.stability.successfulRuns, denominator: input.stability.recordedRuns, ratio: input.stability.ratio, threshold: 0.95, status: input.stability.ratio >= 0.95 ? "pass" : "fail", reason: input.stability.ratio >= 0.95 ? null : "stability-below-threshold" }
24
- : unavailable(input.stability?.reason ?? "stability-evidence-missing", input.stability?.successfulRuns ?? null, input.stability?.recordedRuns ?? null, 0.95),
25
22
  lineCoverage: input.coverage?.metrics.line.status === "available" && input.coverage.metrics.line.ratio !== null
26
23
  ? { numerator: input.coverage.metrics.line.covered, denominator: input.coverage.metrics.line.total, ratio: input.coverage.metrics.line.ratio, threshold: 0.8, status: input.coverage.metrics.line.ratio >= 0.8 ? "pass" : "fail", reason: input.coverage.metrics.line.ratio >= 0.8 ? null : "line-coverage-below-threshold" }
27
24
  : unavailable(input.coverage?.metrics.line.reason ?? "line-coverage-missing", input.coverage?.metrics.line.covered ?? null, input.coverage?.metrics.line.total ?? null, 0.8),
@@ -48,7 +48,7 @@
48
48
  | 云 Task Pool / SQL / Orchestrator | 规划 / 未实现 | 同上(第 2 月原始设计已调整为本地 Feature 闭环) |
49
49
  | 多仓库平台 | 规划 / 未实现 | 同上 |
50
50
  | 组织级服务 | 规划 / 未实现 | 同上 |
51
- | Web Console(远端多用户) | 规划 / 未实现;**不等于**已发布的本地 loopback Operator Console | 同上;对照 `docs/design/active/local-operator-console-from-pi-web.md` |
51
+ | Web Console(远端多用户) | 规划 / 未实现;**不等于**已发布的本地 loopback Operator Console | 同上;对照 `docs/design/archive/2026-07-21-local-operator-console-from-pi-web.md` |
52
52
  | fullstack Delivery / Final Verification live 闭环 | 进行中(active train) | `docs/exec-plans/active/2026-07-19-fullstack-dogfood-016x-release-train.md` |
53
53
  | Dynamic Workflow runtime limits 强执法、更广 profile | 设计输入 | `docs/design/active/dynamic-workflow-dag-engine-roadmap.md` |
54
54
  | Loop 与 Dynamic Workflow 更深双向集成 / 自动恢复 | 设计输入 | 同上;当前已有基础 `workflow` action |
@@ -64,7 +64,7 @@ workflow runtime(调度 Pi / shell / static 节点)
64
64
  - `agent-worker console serve|doctor` 提供 loopback Operator Console(默认 `127.0.0.1:8790`);Happy Path / Interview 策略面、Task Contract 落地与 recovery CTA 已随 **0.17.0–0.17.2** 发布。
65
65
  - canonical mutation 只经 sibling 已发布 `loop-agent`(`LoopAgentClient`),不 in-process 跑 DAG kernel。
66
66
  - Observe 保持**独立**只读进程(默认 `8787`);Console 通过 versioned Observe `/api/health`(`schemaVersion`、`repoFingerprint`、route capabilities)做深链 fail-closed,**不** mount / proxy。
67
- - openCode 等主会话仍是 Compatibility / Operator Assist,与 Official Console **不等同**。设计锚点:ADR 0005、`docs/design/active/local-operator-console-from-pi-web.md`;handoff `docs/reports/feature/2026-07-22-console-mvp-handoff.md`。
67
+ - openCode 等主会话仍是 Compatibility / Operator Assist,与 Official Console **不等同**。设计锚点:ADR 0005、`docs/design/archive/2026-07-21-local-operator-console-from-pi-web.md`;handoff `docs/reports/feature/2026-07-22-console-mvp-handoff.md`。
68
68
 
69
69
  ## 外部边界
70
70
 
@@ -2,6 +2,25 @@
2
2
 
3
3
  本文记录仅与特定开发环境有关、但可能反复影响验证结果的问题。它不是通用工程规则;标准验证命令仍以 [`verification-matrix.md`](../governance/verification-matrix.md) 为准。
4
4
 
5
+ ## Git hooks(本仓库)
6
+
7
+ 每个 clone 安装一次:
8
+
9
+ ```bash
10
+ bash scripts/install-git-hooks.sh
11
+ ```
12
+
13
+ | Hook | 行为 |
14
+ | --- | --- |
15
+ | `pre-commit` | `bash scripts/check-repo.sh`(快,不含 `npm test`) |
16
+ | `pre-push` | `bash scripts/ci.sh`(check-repo + typecheck + 全量 `npm test`) |
17
+
18
+ 编辑与实现阶段只跑 verification-matrix 的「最低验证」;全量测试由 push 前 hook 承担。紧急跳过一次:
19
+
20
+ ```bash
21
+ LOOP_AGENT_SKIP_PRE_PUSH_TESTS=1 git push
22
+ ```
23
+
5
24
  ## Cursor Cloud
6
25
 
7
26
  Cursor Cloud VM 当前有两个需要特别注意的环境问题。
@@ -250,7 +250,7 @@
250
250
  ".harness/dag-runs/**",
251
251
  "artifacts/**"
252
252
  ],
253
- "outputContract": "One scoped pytest execution over Markdown-mapped scripts producing valid JUnit with per-case captured output, self-contained HTML, reports/backend-test.md and reports/backend-test-facts.md; exit 0/1 with valid evidence continues.",
253
+ "outputContract": "One scoped pytest execution over Markdown-mapped scripts producing valid JUnit with per-case captured output, self-contained reports/backend-test.html and reports/backend-test.md, plus internal reports/backend-test-facts.md evidence; exit 0/1 with valid evidence continues.",
254
254
  "subtask_prompt": "Resolve the final Markdown Automation Notes/自动化映射 to a unique, safe set of testcase/**/test_*.py targets and execute only those scripts exactly once. Validate JUnit, then render the primary self-contained Chinese HTML report from the same JUnit plus final Markdown case metadata without rerun. Keep 测试结论, quality status, failure overview, and a polished per-case result card with concise scenario, automation test name, result, duration, and redacted bounded HTTP request parameters/response results for both passed and failed cases. Do not render a technical/execution evidence section in HTML; retain auditable paths and hashes in facts.",
255
255
  "shell": {
256
256
  "commands": [
@@ -284,7 +284,7 @@
284
284
  "artifacts/**"
285
285
  ],
286
286
  "outputContract": "Final Markdown report and L-5 conclusion under docs/test-reports/**; no JSON.",
287
- "subtask_prompt": "Generate the final Markdown report from upstream facts and run-owned environment, advisory case-validation, advisory traceability, JUnit and HTML evidence. Do not emit JSON.\n\nUse this exact human-facing section order: 测试结论 → 执行概览 → 质量校验 → 失败分析 → 风险与建议 → 证据与 L-5. Put the decision and key numbers first, use compact tables/bullets, and keep headings concise. Do not paste entire upstream reports, duplicate per-case tables already present in facts, or repeat the same evidence in multiple sections; link to paths/hashes and quote only the findings needed for the conclusion.\n\nAlways state the exact PASS/FAIL status and findings from nodes 4 and 6. Their FAIL status does not block pytest, but it must remain visible as a quality/traceability risk and must never be rewritten as PASS.\n\nInclude environment, case quality/review, automation mapping, exact pytest facts, failure classification/analysis, risks, regression recommendations, evidence paths/hashes, coverage/stability availability, and L-5 READY/NOT READY.\n\nNever override Shell/JUnit facts. One run cannot prove FlakyTest. Missing coverage/stability is Unavailable. L-5 requires pass=100%, AC=100%, automation>=90%, stability>=95% n>=5, line>=80%, branch>=70%, skipped=0 and no blocking Critical risk.\n\nWrite only under docs/test-reports/**."
287
+ "subtask_prompt": "Generate the final Markdown report from upstream facts and run-owned environment, advisory case-validation, advisory traceability, JUnit and HTML evidence. Do not emit JSON.\n\nUse this exact human-facing section order: 测试结论 → 执行概览 → 质量校验 → 失败分析 → 风险与建议 → 证据与 L-5. Put the decision and key numbers first, use compact tables/bullets, and keep headings concise. Do not paste entire upstream reports, duplicate per-case tables already present in facts, or repeat the same evidence in multiple sections; link to paths/hashes and quote only the findings needed for the conclusion.\n\nAlways state the exact PASS/FAIL status and findings from nodes 4 and 6. Their FAIL status does not block pytest, but it must remain visible as a quality/traceability risk and must never be rewritten as PASS.\n\nInclude environment, case quality/review, automation mapping, exact pytest facts, failure classification/analysis, risks, regression recommendations, evidence paths/hashes, coverage availability, and L-5 READY/NOT READY.\n\nNever override Shell/JUnit facts. L-5 requires pass=100%, AC=100%, automation>=90%, line>=80%, branch>=70%, skipped=0 and no blocking Critical risk.\n\nWrite only under docs/test-reports/**."
288
288
  }
289
289
  ],
290
290
  "sourceBinding": {
@@ -47,7 +47,6 @@ This node runs on **both pass and assertion-fail** paths after canonical context
47
47
  3. **Classification** — `classify-backend-test-result-pi` JSON (`category`, `confidence`, `evidence`). Interpretive only; does not override outcome.
48
48
  4. **Review report** — `review-backend-cases-pi` output (VERDICT, findings, coverage assessment).
49
49
  5. **Code Coverage v1** — optional validated `contracts/code-coverage-v1.json`, generated by coverage.py/pytest-cov or JaCoCo. Never infer it from Result v1.
50
- 6. **Stability Evidence** — optional independent contract for repeated runs of the same suite and version.
51
50
  7. Optional secondary: execute stdout markers / JUnit path (do not re-parse logs for counts when Result v1 exists).
52
51
 
53
52
  Do NOT re-read source documents. Use upstream outputs only.
@@ -58,7 +57,6 @@ Do NOT re-read source documents. Use upstream outputs only.
58
57
  - AC coverage = `coverageSummary.acCoverageRatio` from Case Manifest v1 only (do **not** recompute or invent percentages).
59
58
  - Automation coverage = `coverageSummary.generatedCount / coverageSummary.caseCount`; if either field is missing, output `unavailable`.
60
59
  - Code coverage = Code Coverage v1 only. Display line (gate ≥80%), branch (gate ≥70%), and function/method (display only), each with covered, total, ratio, status, reason, source scope, requirement IDs, tool/version, commit and artifact SHA-256. Missing artifacts or line/branch metrics are `unavailable` and block L-5.
61
- - Stability = Stability Evidence `successfulRuns / recordedRuns`, same suite and version, minimum `n≥5`; one run is `unavailable` and cannot prove `FlakyTest`.
62
60
  - Failed case table rows must match `failures[]` from Result v1.
63
61
  - If Result v1 `outcome` is not `passed`, the retrospective **must not** claim overall success.
64
62
 
@@ -105,7 +103,6 @@ Write the report as a Markdown file named `backend-test-retrospect-<date>.md` un
105
103
  | Code line coverage | N / N (X%), threshold ≥80%, status |
106
104
  | Code branch coverage | N / N (X%), threshold ≥70%, status |
107
105
  | Code function/method coverage | N / N (X%) or unavailable |
108
- | Stability | successfulRuns / recordedRuns (X%), n, or unavailable |
109
106
 
110
107
  Code coverage must include language, tool/version, requirement IDs, source scope, commit and artifact SHA-256.
111
108
 
package/harness.json CHANGED
@@ -59,7 +59,7 @@
59
59
  "pi": {
60
60
  "description": "Pi planning, review, diagnosis, and bounded writing when DAG toolProfile=write",
61
61
  "LOW": "gpt-5.3-codex-spark",
62
- "MED": "glm-5.2",
62
+ "MED": "grok-4.5",
63
63
  "HIGH": "gpt-5.6-sol"
64
64
  }
65
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.24.3",
3
+ "version": "0.24.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",