noctrace 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/index-CLA0rrye.js +30 -0
- package/dist/client/assets/{index-BWO5fecq.css → index-iQItZcUN.css} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/server/routes/api.js +23 -25
- package/dist/server/server/watcher.js +73 -1
- package/dist/server/server/ws.js +112 -1
- package/package.json +2 -1
- package/dist/client/assets/index-BhUwV-5i.js +0 -30
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */
|
|
2
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-semibold:600;--font-weight-bold:700;--tracking-wider:.05em;--radius-sm:.25rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.top-1\/2{top:50%}.top-2{top:calc(var(--spacing) * 2)}.top-8{top:calc(var(--spacing) * 8)}.right-0{right:calc(var(--spacing) * 0)}.right-1\.5{right:calc(var(--spacing) * 1.5)}.right-2{right:calc(var(--spacing) * 2)}.left-0{left:calc(var(--spacing) * 0)}.left-2{left:calc(var(--spacing) * 2)}.z-10{z-index:10}.z-50{z-index:50}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-3{margin-inline:calc(var(--spacing) * 3)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-full{height:100%}.w-full{width:100%}.min-w-0{min-width:calc(var(--spacing) * 0)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.resize{resize:both}.flex-col{flex-direction:column}.items-center{align-items:center}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-sm{border-radius:var(--radius-sm)}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.border{border-style:var(--tw-border-style);border-width:1px}.p-1{padding:calc(var(--spacing) * 1)}.p-2{padding:calc(var(--spacing) * 2)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pl-7{padding-left:calc(var(--spacing) * 7)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-all{word-break:break-all}.whitespace-pre-wrap{white-space:pre-wrap}.uppercase{text-transform:uppercase}.italic{font-style:italic}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.select-none{-webkit-user-select:none;user-select:none}.\[file\:line\]{file:line}@media (width>=48rem){.md\:hidden{display:none}}}:root{--ctp-base:#1e1e2e;--ctp-mantle:#181825;--ctp-crust:#11111b;--ctp-surface0:#313244;--ctp-surface1:#45475a;--ctp-surface2:#585b70;--ctp-overlay0:#6c7086;--ctp-overlay1:#7f849c;--ctp-text:#cdd6f4;--ctp-subtext0:#a6adc8;--ctp-subtext1:#bac2de;--ctp-blue:#89b4fa;--ctp-green:#a6e3a1;--ctp-yellow:#f9e2af;--ctp-peach:#fab387;--ctp-mauve:#cba6f7;--ctp-teal:#94e2d5;--ctp-red:#f38ba8;--ctp-pink:#f5c2e7;--ctp-lavender:#b4befe;--color-read:var(--ctp-blue);--color-write:var(--ctp-green);--color-edit:var(--ctp-yellow);--color-bash:var(--ctp-peach);--color-agent:var(--ctp-mauve);--color-grep:var(--ctp-teal);--color-error:var(--ctp-red);--color-running:var(--ctp-pink)}body{background-color:var(--ctp-base);color:var(--ctp-text);font-family:SF Mono,Cascadia Code,JetBrains Mono,Fira Code,ui-monospace,monospace}@keyframes pulse-edge{0%,to{opacity:.6}50%{opacity:.1}}.running-pulse{animation:1.2s ease-in-out infinite pulse-edge}@keyframes noc-pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.85)}}@keyframes noc-blink{0%,to{opacity:1}50%{opacity:0}}@media (width<=768px){.hidden-mobile{display:none!important}}.sidebar-panel{background-color:var(--ctp-mantle);flex-direction:column;flex-shrink:0;width:240px;display:flex;overflow:hidden}@media (width<=767px){.sidebar-panel{width:240px;transition:transform .2s;position:fixed;top:0;bottom:0;left:0;transform:translate(-100%)}.sidebar-panel[data-open=true]{transform:translate(0)}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-semibold:600;--font-weight-bold:700;--tracking-wider:.05em;--radius-sm:.25rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.top-1\/2{top:50%}.top-2{top:calc(var(--spacing) * 2)}.top-8{top:calc(var(--spacing) * 8)}.right-0{right:calc(var(--spacing) * 0)}.right-1\.5{right:calc(var(--spacing) * 1.5)}.right-2{right:calc(var(--spacing) * 2)}.left-0{left:calc(var(--spacing) * 0)}.left-2{left:calc(var(--spacing) * 2)}.z-10{z-index:10}.z-50{z-index:50}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-3{margin-inline:calc(var(--spacing) * 3)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-full{height:100%}.w-full{width:100%}.min-w-0{min-width:calc(var(--spacing) * 0)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.resize{resize:both}.flex-col{flex-direction:column}.items-center{align-items:center}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-sm{border-radius:var(--radius-sm)}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.border{border-style:var(--tw-border-style);border-width:1px}.p-1{padding:calc(var(--spacing) * 1)}.p-2{padding:calc(var(--spacing) * 2)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pl-7{padding-left:calc(var(--spacing) * 7)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-all{word-break:break-all}.whitespace-pre-wrap{white-space:pre-wrap}.uppercase{text-transform:uppercase}.italic{font-style:italic}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.invert{--tw-invert:invert(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.select-none{-webkit-user-select:none;user-select:none}.\[file\:line\]{file:line}@media (width>=48rem){.md\:hidden{display:none}}}:root{--ctp-base:#1e1e2e;--ctp-mantle:#181825;--ctp-crust:#11111b;--ctp-surface0:#313244;--ctp-surface1:#45475a;--ctp-surface2:#585b70;--ctp-overlay0:#6c7086;--ctp-overlay1:#7f849c;--ctp-text:#cdd6f4;--ctp-subtext0:#a6adc8;--ctp-subtext1:#bac2de;--ctp-blue:#89b4fa;--ctp-green:#a6e3a1;--ctp-yellow:#f9e2af;--ctp-peach:#fab387;--ctp-mauve:#cba6f7;--ctp-teal:#94e2d5;--ctp-red:#f38ba8;--ctp-pink:#f5c2e7;--ctp-lavender:#b4befe;--color-read:var(--ctp-blue);--color-write:var(--ctp-green);--color-edit:var(--ctp-yellow);--color-bash:var(--ctp-peach);--color-agent:var(--ctp-mauve);--color-grep:var(--ctp-teal);--color-error:var(--ctp-red);--color-running:var(--ctp-pink)}body{background-color:var(--ctp-base);color:var(--ctp-text);font-family:SF Mono,Cascadia Code,JetBrains Mono,Fira Code,ui-monospace,monospace}@keyframes pulse-edge{0%,to{opacity:.6}50%{opacity:.1}}.running-pulse{animation:1.2s ease-in-out infinite pulse-edge}@keyframes noc-pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.85)}}@keyframes noc-blink{0%,to{opacity:1}50%{opacity:0}}@media (width<=768px){.hidden-mobile{display:none!important}}.sidebar-panel{background-color:var(--ctp-mantle);flex-direction:column;flex-shrink:0;width:240px;display:flex;overflow:hidden}@media (width<=767px){.sidebar-panel{width:240px;transition:transform .2s;position:fixed;top:0;bottom:0;left:0;transform:translate(-100%)}.sidebar-panel[data-open=true]{transform:translate(0)}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
|
package/dist/client/index.html
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet" />
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-CLA0rrye.js"></script>
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-iQItZcUN.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="root"></div>
|
|
@@ -242,6 +242,29 @@ export function buildApiRouter(claudeHome, wss) {
|
|
|
242
242
|
}
|
|
243
243
|
});
|
|
244
244
|
// ---------------------------------------------------------------------------
|
|
245
|
+
// GET /api/sessions/registered (MUST be before /sessions/:slug to avoid param capture)
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
/**
|
|
248
|
+
* Return the list of currently registered MCP session paths.
|
|
249
|
+
* An empty array means standalone mode (show all sessions from disk).
|
|
250
|
+
* A non-empty array means MCP mode (show only registered sessions).
|
|
251
|
+
*/
|
|
252
|
+
router.get('/sessions/registered', async (_req, res) => {
|
|
253
|
+
const STALE_THRESHOLD_MS = 5 * 60 * 1000;
|
|
254
|
+
for (const registeredPath of registeredSessionPaths) {
|
|
255
|
+
try {
|
|
256
|
+
const stat = await fs.stat(registeredPath);
|
|
257
|
+
if (Date.now() - stat.mtime.getTime() > STALE_THRESHOLD_MS) {
|
|
258
|
+
registeredSessionPaths.delete(registeredPath);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
registeredSessionPaths.delete(registeredPath);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
res.json({ sessions: Array.from(registeredSessionPaths) });
|
|
266
|
+
});
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
245
268
|
// GET /api/sessions/:slug
|
|
246
269
|
// ---------------------------------------------------------------------------
|
|
247
270
|
/**
|
|
@@ -572,30 +595,5 @@ export function buildApiRouter(claudeHome, wss) {
|
|
|
572
595
|
}
|
|
573
596
|
});
|
|
574
597
|
// ---------------------------------------------------------------------------
|
|
575
|
-
// GET /api/sessions/registered
|
|
576
|
-
// ---------------------------------------------------------------------------
|
|
577
|
-
/**
|
|
578
|
-
* Return the list of currently registered MCP session paths.
|
|
579
|
-
* An empty array means standalone mode (show all sessions from disk).
|
|
580
|
-
* A non-empty array means MCP mode (show only registered sessions).
|
|
581
|
-
*/
|
|
582
|
-
router.get('/sessions/registered', async (_req, res) => {
|
|
583
|
-
// Prune phantom sessions whose JSONL has not been modified in the last 5 minutes.
|
|
584
|
-
// Active sessions are written to frequently; stale ones left by SIGKILL won't be.
|
|
585
|
-
const STALE_THRESHOLD_MS = 5 * 60 * 1000;
|
|
586
|
-
for (const registeredPath of registeredSessionPaths) {
|
|
587
|
-
try {
|
|
588
|
-
const stat = await fs.stat(registeredPath);
|
|
589
|
-
if (Date.now() - stat.mtime.getTime() > STALE_THRESHOLD_MS) {
|
|
590
|
-
registeredSessionPaths.delete(registeredPath);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
catch {
|
|
594
|
-
// File no longer exists — remove the phantom entry
|
|
595
|
-
registeredSessionPaths.delete(registeredPath);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
res.json({ sessions: Array.from(registeredSessionPaths) });
|
|
599
|
-
});
|
|
600
598
|
return router;
|
|
601
599
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import chokidar from 'chokidar';
|
|
6
6
|
import fs from 'node:fs';
|
|
7
|
-
import { parseJsonlContent, parseCompactionBoundaries } from '../shared/parser.js';
|
|
7
|
+
import { parseJsonlContent, parseCompactionBoundaries, parseSubAgentContent } from '../shared/parser.js';
|
|
8
8
|
import { computeContextHealth } from '../shared/health.js';
|
|
9
9
|
import { parseAssistantTurns, computeDrift } from '../shared/drift.js';
|
|
10
10
|
import { attachEfficiencyTips } from '../shared/tips.js';
|
|
@@ -87,3 +87,75 @@ export function watchSession(filePath, callbacks) {
|
|
|
87
87
|
},
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Watch a single sub-agent JSONL file for appended content.
|
|
92
|
+
* Uses byte-offset incremental reading (same pattern as watchSession) to avoid
|
|
93
|
+
* re-parsing the entire file on every write. Calls onUpdate with the full set of
|
|
94
|
+
* parsed rows each time new complete lines are available.
|
|
95
|
+
*
|
|
96
|
+
* @param filePath - Absolute path to the sub-agent JSONL file.
|
|
97
|
+
* @param agentId - The agent ID extracted from the filename (used in onUpdate callback).
|
|
98
|
+
* @param toolUseId - The tool_use_id from the parent session (the parent row's id).
|
|
99
|
+
* @param callbacks - Callback invoked when rows are parsed from new content.
|
|
100
|
+
*/
|
|
101
|
+
export function watchSubAgent(filePath, agentId, toolUseId, callbacks) {
|
|
102
|
+
let bytesRead = 0;
|
|
103
|
+
const watcher = chokidar.watch(filePath, {
|
|
104
|
+
persistent: true,
|
|
105
|
+
ignoreInitial: true,
|
|
106
|
+
});
|
|
107
|
+
watcher.on('change', () => {
|
|
108
|
+
try {
|
|
109
|
+
const stat = fs.statSync(filePath);
|
|
110
|
+
const fileSize = stat.size;
|
|
111
|
+
if (fileSize <= bytesRead) {
|
|
112
|
+
// File was truncated or unchanged — reset offset
|
|
113
|
+
bytesRead = 0;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Read only the new bytes to determine if there are complete new lines
|
|
117
|
+
const newByteCount = fileSize - bytesRead;
|
|
118
|
+
const buffer = Buffer.alloc(newByteCount);
|
|
119
|
+
const fd = fs.openSync(filePath, 'r');
|
|
120
|
+
try {
|
|
121
|
+
fs.readSync(fd, buffer, 0, newByteCount, bytesRead);
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
fs.closeSync(fd);
|
|
125
|
+
}
|
|
126
|
+
const raw = buffer.toString('utf8');
|
|
127
|
+
const lastNewline = raw.lastIndexOf('\n');
|
|
128
|
+
if (lastNewline === -1) {
|
|
129
|
+
// No complete line yet — wait for next change event
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Advance offset only for complete lines
|
|
133
|
+
bytesRead += Buffer.byteLength(raw.slice(0, lastNewline + 1), 'utf8');
|
|
134
|
+
// Read full file for accurate sub-agent row parsing (correct result map lookups)
|
|
135
|
+
let fullContent = '';
|
|
136
|
+
try {
|
|
137
|
+
fullContent = fs.readFileSync(filePath, 'utf8');
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
fullContent = buffer.toString('utf8');
|
|
141
|
+
}
|
|
142
|
+
const rows = parseSubAgentContent(fullContent);
|
|
143
|
+
if (rows.length === 0)
|
|
144
|
+
return;
|
|
145
|
+
callbacks.onUpdate(agentId, toolUseId, rows);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
console.warn('[noctrace] sub-agent watcher error:', err instanceof Error ? err.message : String(err));
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
watcher.on('error', (err) => {
|
|
152
|
+
console.warn('[noctrace] sub-agent chokidar error:', err instanceof Error ? err.message : String(err));
|
|
153
|
+
});
|
|
154
|
+
return {
|
|
155
|
+
stop: () => {
|
|
156
|
+
watcher.close().catch((err) => {
|
|
157
|
+
console.warn('[noctrace] sub-agent watcher close error:', err instanceof Error ? err.message : String(err));
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
package/dist/server/server/ws.js
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import chokidar from 'chokidar';
|
|
8
|
+
import fs from 'node:fs';
|
|
8
9
|
import path from 'node:path';
|
|
9
|
-
import { watchSession } from './watcher.js';
|
|
10
|
+
import { watchSession, watchSubAgent } from './watcher.js';
|
|
11
|
+
import { extractAgentIds } from '../shared/parser.js';
|
|
10
12
|
// ---------------------------------------------------------------------------
|
|
11
13
|
// Helpers
|
|
12
14
|
// ---------------------------------------------------------------------------
|
|
@@ -85,12 +87,18 @@ export function setupWebSocket(server, claudeHome) {
|
|
|
85
87
|
});
|
|
86
88
|
wss.on('connection', (ws, _req) => {
|
|
87
89
|
let stopWatcher = null;
|
|
90
|
+
/** Handles for each active sub-agent file watcher, keyed by agentId. */
|
|
91
|
+
const subAgentStoppers = new Map();
|
|
88
92
|
let resumeProc = null;
|
|
89
93
|
const stopCurrent = () => {
|
|
90
94
|
if (stopWatcher) {
|
|
91
95
|
stopWatcher();
|
|
92
96
|
stopWatcher = null;
|
|
93
97
|
}
|
|
98
|
+
for (const stop of subAgentStoppers.values()) {
|
|
99
|
+
stop();
|
|
100
|
+
}
|
|
101
|
+
subAgentStoppers.clear();
|
|
94
102
|
};
|
|
95
103
|
const killResume = () => {
|
|
96
104
|
if (resumeProc) {
|
|
@@ -236,12 +244,115 @@ export function setupWebSocket(server, claudeHome) {
|
|
|
236
244
|
send(ws, { type: 'error', message: 'Invalid path' });
|
|
237
245
|
return;
|
|
238
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Build a reverse lookup map from agentId to toolUseId by reading the main JSONL.
|
|
249
|
+
* Returns an empty map if the file cannot be read.
|
|
250
|
+
*/
|
|
251
|
+
const buildAgentIdToToolUseId = () => {
|
|
252
|
+
try {
|
|
253
|
+
const mainContent = fs.readFileSync(filePath, 'utf8');
|
|
254
|
+
// extractAgentIds returns Map<toolUseId, agentId> — we invert it
|
|
255
|
+
const forward = extractAgentIds(mainContent);
|
|
256
|
+
const reverse = new Map();
|
|
257
|
+
for (const [toolUseId, agentId] of forward) {
|
|
258
|
+
reverse.set(agentId, toolUseId);
|
|
259
|
+
}
|
|
260
|
+
return reverse;
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return new Map();
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
/**
|
|
267
|
+
* Start a file watcher for a single sub-agent JSONL path if not already watching.
|
|
268
|
+
* Validates that the path is within the expected subagents directory.
|
|
269
|
+
* Optionally accepts a pre-computed toolUseId to avoid re-reading the main JSONL.
|
|
270
|
+
*/
|
|
271
|
+
const startSubAgentWatcher = (subFilePath, knownToolUseId) => {
|
|
272
|
+
const resolvedSub = path.resolve(subFilePath);
|
|
273
|
+
const expectedPrefix = path.resolve(path.join(projectsBase, slug, id, 'subagents')) + path.sep;
|
|
274
|
+
if (!resolvedSub.startsWith(expectedPrefix))
|
|
275
|
+
return;
|
|
276
|
+
// Extract agentId from filename: agent-<agentId>.jsonl
|
|
277
|
+
const baseName = path.basename(subFilePath);
|
|
278
|
+
const match = baseName.match(/^agent-([a-zA-Z0-9_-]+)\.jsonl$/);
|
|
279
|
+
if (!match)
|
|
280
|
+
return;
|
|
281
|
+
const agentId = match[1];
|
|
282
|
+
// Skip if already watching this agent
|
|
283
|
+
if (subAgentStoppers.has(agentId))
|
|
284
|
+
return;
|
|
285
|
+
// Resolve the toolUseId (parent row.id) for this agentId
|
|
286
|
+
const toolUseId = knownToolUseId ?? buildAgentIdToToolUseId().get(agentId) ?? agentId;
|
|
287
|
+
const subHandle = watchSubAgent(subFilePath, agentId, toolUseId, {
|
|
288
|
+
onUpdate: (updatedAgentId, updatedToolUseId, subRows) => {
|
|
289
|
+
send(ws, {
|
|
290
|
+
type: 'subagent-update',
|
|
291
|
+
sessionId: id,
|
|
292
|
+
agentId: updatedAgentId,
|
|
293
|
+
toolUseId: updatedToolUseId,
|
|
294
|
+
rows: subRows,
|
|
295
|
+
});
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
subAgentStoppers.set(agentId, subHandle.stop);
|
|
299
|
+
};
|
|
300
|
+
/**
|
|
301
|
+
* Read the main JSONL to extract agentId entries and start file watchers
|
|
302
|
+
* for any sub-agent JSONL files we haven't started watching yet.
|
|
303
|
+
* This supplements the directory watcher for cases where agentId appears
|
|
304
|
+
* in the main JSONL before the corresponding sub-agent file is created on disk.
|
|
305
|
+
*/
|
|
306
|
+
const startSubAgentWatchersFromFile = () => {
|
|
307
|
+
try {
|
|
308
|
+
const mainContent = fs.readFileSync(filePath, 'utf8');
|
|
309
|
+
const agentIdMap = extractAgentIds(mainContent);
|
|
310
|
+
for (const [toolUseId, agentId] of agentIdMap) {
|
|
311
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(agentId))
|
|
312
|
+
continue;
|
|
313
|
+
if (subAgentStoppers.has(agentId))
|
|
314
|
+
continue;
|
|
315
|
+
const subFilePath = path.join(projectsBase, slug, id, 'subagents', `agent-${agentId}.jsonl`);
|
|
316
|
+
startSubAgentWatcher(subFilePath, toolUseId);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
// If we can't read the main file, skip — non-critical
|
|
321
|
+
}
|
|
322
|
+
};
|
|
239
323
|
const handle = watchSession(filePath, {
|
|
240
324
|
onNewRows: (rows, health, boundaries, drift) => {
|
|
241
325
|
send(ws, { type: 'rows', rows, health, boundaries, drift });
|
|
326
|
+
// Whenever the main session file changes, check for new sub-agent IDs
|
|
327
|
+
// (toolUseResult.agentId) and start watching any files we haven't seen yet.
|
|
328
|
+
// The directory watcher below handles newly created files; this handles
|
|
329
|
+
// the case where agentId appears in main JSONL before the file is created.
|
|
330
|
+
startSubAgentWatchersFromFile();
|
|
242
331
|
},
|
|
243
332
|
});
|
|
244
333
|
stopWatcher = handle.stop;
|
|
334
|
+
// Also start watching the sub-agent directory using a chokidar glob watcher
|
|
335
|
+
// so that new agent files are picked up as they are created.
|
|
336
|
+
const subagentsDir = path.join(projectsBase, slug, id, 'subagents');
|
|
337
|
+
const subagentsDirResolved = path.resolve(subagentsDir);
|
|
338
|
+
const allowedPrefix = path.resolve(projectsBase) + path.sep;
|
|
339
|
+
if (subagentsDirResolved.startsWith(allowedPrefix)) {
|
|
340
|
+
const subDirWatcher = chokidar.watch(path.join(subagentsDir, '*.jsonl'), {
|
|
341
|
+
persistent: true,
|
|
342
|
+
// Fire for pre-existing files too so we pick up agents that started before the watch
|
|
343
|
+
ignoreInitial: false,
|
|
344
|
+
});
|
|
345
|
+
subDirWatcher.on('add', (subFilePath) => {
|
|
346
|
+
startSubAgentWatcher(subFilePath);
|
|
347
|
+
});
|
|
348
|
+
subDirWatcher.on('error', (err) => {
|
|
349
|
+
console.warn('[noctrace] subagents dir watcher error:', err instanceof Error ? err.message : String(err));
|
|
350
|
+
});
|
|
351
|
+
// Register a stop function for the directory watcher itself
|
|
352
|
+
subAgentStoppers.set('__dir__', () => {
|
|
353
|
+
subDirWatcher.close().catch(() => { });
|
|
354
|
+
});
|
|
355
|
+
}
|
|
245
356
|
});
|
|
246
357
|
ws.on('close', () => {
|
|
247
358
|
stopCurrent();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "noctrace",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "Chrome DevTools Network-tab-style waterfall visualizer for Claude Code agent workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"dev": "concurrently -k -n server,client -c blue,green \"tsx watch src/server/index.ts\" \"vite\"",
|
|
44
44
|
"build": "vite build && tsc -p tsconfig.server.json",
|
|
45
45
|
"test": "vitest run",
|
|
46
|
+
"test:smoke": "npm run build && vitest run tests/smoke/",
|
|
46
47
|
"lint": "eslint src/",
|
|
47
48
|
"typecheck": "tsc -p tsconfig.server.json --noEmit && tsc -p tsconfig.client.json --noEmit",
|
|
48
49
|
"postinstall": "node bin/postinstall.js",
|