prompt_objects 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -0
- data/CLAUDE.md +113 -44
- data/README.md +140 -14
- data/frontend/index.html +5 -1
- data/frontend/src/App.tsx +72 -79
- data/frontend/src/canvas/CanvasView.tsx +5 -5
- data/frontend/src/canvas/constants.ts +31 -31
- data/frontend/src/canvas/inspector/InspectorPanel.tsx +4 -4
- data/frontend/src/canvas/inspector/POInspector.tsx +35 -35
- data/frontend/src/canvas/inspector/ToolCallInspector.tsx +13 -13
- data/frontend/src/canvas/nodes/PONode.ts +2 -2
- data/frontend/src/components/ContextMenu.tsx +5 -4
- data/frontend/src/components/EnvDataPane.tsx +69 -0
- data/frontend/src/components/Inspector.tsx +263 -0
- data/frontend/src/components/MarkdownMessage.tsx +22 -20
- data/frontend/src/components/MethodList.tsx +90 -0
- data/frontend/src/components/ModelSelector.tsx +13 -14
- data/frontend/src/components/NotificationPanel.tsx +29 -33
- data/frontend/src/components/ObjectList.tsx +78 -0
- data/frontend/src/components/PaneSlot.tsx +30 -0
- data/frontend/src/components/SourcePane.tsx +202 -0
- data/frontend/src/components/SystemBar.tsx +74 -0
- data/frontend/src/components/Transcript.tsx +76 -0
- data/frontend/src/components/UsagePanel.tsx +27 -27
- data/frontend/src/components/Workspace.tsx +260 -0
- data/frontend/src/components/index.ts +10 -9
- data/frontend/src/hooks/useResize.ts +55 -0
- data/frontend/src/hooks/useWebSocket.ts +70 -0
- data/frontend/src/index.css +27 -10
- data/frontend/src/store/index.ts +36 -0
- data/frontend/src/types/index.ts +13 -0
- data/frontend/tailwind.config.js +28 -9
- data/lib/prompt_objects/capability.rb +23 -1
- data/lib/prompt_objects/connectors/mcp.rb +2 -16
- data/lib/prompt_objects/environment.rb +15 -0
- data/lib/prompt_objects/llm/openai_adapter.rb +22 -0
- data/lib/prompt_objects/mcp/tools/inspect_po.rb +1 -31
- data/lib/prompt_objects/mcp/tools/list_prompt_objects.rb +1 -6
- data/lib/prompt_objects/prompt_object.rb +239 -7
- data/lib/prompt_objects/server/api/routes.rb +16 -48
- data/lib/prompt_objects/server/app.rb +14 -0
- data/lib/prompt_objects/server/public/assets/{index-xvyeb-5Z.js → index-DEPawnfZ.js} +206 -206
- data/lib/prompt_objects/server/public/assets/index-oMrRce1m.css +1 -0
- data/lib/prompt_objects/server/public/index.html +7 -3
- data/lib/prompt_objects/server/websocket_handler.rb +41 -98
- data/lib/prompt_objects/server.rb +6 -62
- data/lib/prompt_objects/session/store.rb +176 -4
- data/lib/prompt_objects/universal/delete_env_data.rb +70 -0
- data/lib/prompt_objects/universal/get_env_data.rb +64 -0
- data/lib/prompt_objects/universal/list_env_data.rb +61 -0
- data/lib/prompt_objects/universal/store_env_data.rb +87 -0
- data/lib/prompt_objects/universal/update_env_data.rb +88 -0
- data/lib/prompt_objects.rb +6 -1
- data/prompt_objects.gemspec +1 -1
- data/templates/arc-agi-1/objects/observer.md +4 -0
- data/templates/arc-agi-1/objects/solver.md +10 -1
- data/templates/arc-agi-1/objects/verifier.md +4 -0
- data/templates/arc-agi-1/primitives/find_objects.rb +1 -1
- data/templates/arc-agi-1/primitives/grid_diff.rb +2 -2
- data/templates/arc-agi-1/primitives/grid_info.rb +1 -1
- data/templates/arc-agi-1/primitives/grid_transform.rb +1 -1
- data/templates/arc-agi-1/primitives/render_grid.rb +1 -0
- data/templates/arc-agi-1/primitives/test_solution.rb +3 -0
- data/tools/thread-explorer.html +27 -0
- metadata +18 -16
- data/Gemfile.lock +0 -233
- data/IMPLEMENTATION_PLAN.md +0 -1073
- data/design-doc-v2.md +0 -1232
- data/frontend/src/components/CapabilitiesPanel.tsx +0 -141
- data/frontend/src/components/ChatPanel.tsx +0 -296
- data/frontend/src/components/Dashboard.tsx +0 -83
- data/frontend/src/components/Header.tsx +0 -153
- data/frontend/src/components/MessageBus.tsx +0 -56
- data/frontend/src/components/POCard.tsx +0 -56
- data/frontend/src/components/PODetail.tsx +0 -124
- data/frontend/src/components/PromptPanel.tsx +0 -156
- data/frontend/src/components/SessionsPanel.tsx +0 -174
- data/frontend/src/components/ThreadsSidebar.tsx +0 -163
- data/lib/prompt_objects/server/public/assets/index-6y64NXFy.css +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}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;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:Geist Mono,IBM Plex Mono,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media(min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media(min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media(min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media(min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media(min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-3{bottom:.75rem}.bottom-4{bottom:1rem}.left-3{left:.75rem}.right-0{right:0}.right-4{right:1rem}.top-0{top:0}.top-3{top:.75rem}.top-full{top:100%}.z-10{z-index:10}.z-50{z-index:50}.my-0\.5{margin-top:.125rem;margin-bottom:.125rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.mb-0\.5{margin-bottom:.125rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.ml-0\.5{margin-left:.125rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-40{height:10rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-full{height:100%}.h-screen{height:100vh}.max-h-32{max-height:8rem}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-64{max-height:16rem}.max-h-\[60vh\]{max-height:60vh}.max-h-\[80vh\]{max-height:80vh}.w-1\.5{width:.375rem}.w-2{width:.5rem}.w-3{width:.75rem}.w-56{width:14rem}.w-60{width:15rem}.w-80{width:20rem}.w-96{width:24rem}.w-\[400px\]{width:400px}.w-full{width:100%}.min-w-\[140px\]{min-width:140px}.min-w-full{min-width:100%}.max-w-\[60px\]{max-width:60px}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.resize{resize:both}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-b{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-b-0{border-bottom-width:0px}.border-l{border-left-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-po-accent{--tw-border-opacity: 1;border-color:rgb(212 149 42 / var(--tw-border-opacity, 1))}.border-po-border{--tw-border-opacity: 1;border-color:rgb(61 58 55 / var(--tw-border-opacity, 1))}.border-po-status-calling{--tw-border-opacity: 1;border-color:rgb(59 154 110 / var(--tw-border-opacity, 1))}.border-po-warning{--tw-border-opacity: 1;border-color:rgb(212 149 42 / var(--tw-border-opacity, 1))}.border-po-warning\/30{border-color:#d4952a4d}.border-transparent{border-color:transparent}.bg-black\/50{background-color:#00000080}.bg-po-accent{--tw-bg-opacity: 1;background-color:rgb(212 149 42 / var(--tw-bg-opacity, 1))}.bg-po-accent-wash{background-color:#d4952a14}.bg-po-bg{--tw-bg-opacity: 1;background-color:rgb(26 25 24 / var(--tw-bg-opacity, 1))}.bg-po-error{--tw-bg-opacity: 1;background-color:rgb(196 92 74 / var(--tw-bg-opacity, 1))}.bg-po-status-active{--tw-bg-opacity: 1;background-color:rgb(212 149 42 / var(--tw-bg-opacity, 1))}.bg-po-status-calling{--tw-bg-opacity: 1;background-color:rgb(59 154 110 / var(--tw-bg-opacity, 1))}.bg-po-status-idle{--tw-bg-opacity: 1;background-color:rgb(120 114 106 / var(--tw-bg-opacity, 1))}.bg-po-success{--tw-bg-opacity: 1;background-color:rgb(59 154 110 / var(--tw-bg-opacity, 1))}.bg-po-surface{--tw-bg-opacity: 1;background-color:rgb(34 33 32 / var(--tw-bg-opacity, 1))}.bg-po-surface-2{--tw-bg-opacity: 1;background-color:rgb(44 42 40 / var(--tw-bg-opacity, 1))}.bg-po-surface-2\/80{background-color:#2c2a28cc}.bg-po-text-ghost{--tw-bg-opacity: 1;background-color:rgb(82 78 72 / var(--tw-bg-opacity, 1))}.bg-po-warning{--tw-bg-opacity: 1;background-color:rgb(212 149 42 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-4{padding-top:1rem;padding-bottom:1rem}.pb-1\.5{padding-bottom:.375rem}.pb-2{padding-bottom:.5rem}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pl-\[4\.5rem\]{padding-left:4.5rem}.text-left{text-align:left}.text-center{text-align:center}.align-text-bottom{vertical-align:text-bottom}.font-mono{font-family:Geist Mono,IBM Plex Mono,monospace}.font-ui{font-family:Geist,system-ui,sans-serif}.text-2xs{font-size:11px;line-height:15px}.text-\[0\.9em\]{font-size:.9em}.text-base{font-size:1rem;line-height:1.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.italic{font-style:italic}.leading-relaxed{line-height:1.625}.tracking-wider{letter-spacing:.05em}.text-po-accent{--tw-text-opacity: 1;color:rgb(212 149 42 / var(--tw-text-opacity, 1))}.text-po-bg{--tw-text-opacity: 1;color:rgb(26 25 24 / var(--tw-text-opacity, 1))}.text-po-error{--tw-text-opacity: 1;color:rgb(196 92 74 / var(--tw-text-opacity, 1))}.text-po-status-active{--tw-text-opacity: 1;color:rgb(212 149 42 / var(--tw-text-opacity, 1))}.text-po-status-calling{--tw-text-opacity: 1;color:rgb(59 154 110 / var(--tw-text-opacity, 1))}.text-po-status-delegated{--tw-text-opacity: 1;color:rgb(90 143 194 / var(--tw-text-opacity, 1))}.text-po-success{--tw-text-opacity: 1;color:rgb(59 154 110 / var(--tw-text-opacity, 1))}.text-po-text-ghost{--tw-text-opacity: 1;color:rgb(82 78 72 / var(--tw-text-opacity, 1))}.text-po-text-primary{--tw-text-opacity: 1;color:rgb(232 226 218 / var(--tw-text-opacity, 1))}.text-po-text-secondary{--tw-text-opacity: 1;color:rgb(168 162 154 / var(--tw-text-opacity, 1))}.text-po-text-tertiary{--tw-text-opacity: 1;color:rgb(120 114 106 / var(--tw-text-opacity, 1))}.text-po-warning{--tw-text-opacity: 1;color:rgb(212 149 42 / var(--tw-text-opacity, 1))}.placeholder-po-text-ghost::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(82 78 72 / var(--tw-placeholder-opacity, 1))}.placeholder-po-text-ghost::placeholder{--tw-placeholder-opacity: 1;color:rgb(82 78 72 / var(--tw-placeholder-opacity, 1))}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_5px_rgba\(212\,149\,42\,0\.6\)\]{--tw-shadow: 0 0 5px rgba(212,149,42,.6);--tw-shadow-colored: 0 0 5px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_5px_rgba\(59\,154\,110\,0\.6\)\]{--tw-shadow: 0 0 5px rgba(59,154,110,.6);--tw-shadow-colored: 0 0 5px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_6px_rgba\(212\,149\,42\,0\.7\)\]{--tw-shadow: 0 0 6px rgba(212,149,42,.7);--tw-shadow-colored: 0 0 6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_6px_rgba\(59\,154\,110\,0\.7\)\]{--tw-shadow: 0 0 6px rgba(59,154,110,.7);--tw-shadow-colored: 0 0 6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline{outline-style:solid}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.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)}.backdrop-blur{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-150{transition-duration:.15s}html{color-scheme:dark}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{--tw-bg-opacity: 1;background-color:rgb(26 25 24 / var(--tw-bg-opacity, 1))}::-webkit-scrollbar-thumb{border-radius:9999px;--tw-bg-opacity: 1;background-color:rgb(61 58 55 / var(--tw-bg-opacity, 1))}::-webkit-scrollbar-thumb:hover{--tw-bg-opacity: 1;background-color:rgb(92 87 82 / var(--tw-bg-opacity, 1))}::-moz-selection{background:#d4952a4d}::selection{background:#d4952a4d}.resize-handle{width:.25rem;cursor:col-resize;border-left-width:1px;--tw-border-opacity: 1;border-color:rgb(61 58 55 / var(--tw-border-opacity, 1));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.resize-handle:hover{background-color:#d4952a4d}.resize-handle:active{background-color:#d4952a80}.resize-handle-h{height:.25rem;cursor:row-resize;border-top-width:1px;--tw-border-opacity: 1;border-color:rgb(61 58 55 / var(--tw-border-opacity, 1));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.resize-handle-h:hover{background-color:#d4952a4d}.resize-handle-h:active{background-color:#d4952a80}.canvas-node-label{text-align:center;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.canvas-node-name{display:block;color:#e8e2da;font-size:12px;font-weight:500;font-family:Geist Mono,IBM Plex Mono,monospace;text-shadow:0 1px 4px rgba(0,0,0,.8)}.canvas-node-status{display:block;color:#78726a;font-size:10px;font-family:Geist Mono,IBM Plex Mono,monospace;text-shadow:0 1px 4px rgba(0,0,0,.8)}.canvas-node-badge{display:flex;align-items:center;justify-content:center;width:20px;height:20px;background:#d4952a;color:#1a1918;font-size:10px;font-weight:700;border-radius:50%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;box-shadow:0 0 8px #d4952a99}.canvas-toolcall-label{color:#3b9a6e;font-size:10px;font-family:Geist Mono,IBM Plex Mono,monospace;text-align:center;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;text-shadow:0 1px 4px rgba(0,0,0,.8)}.first\:mt-0:first-child{margin-top:0}.last\:mb-0:last-child{margin-bottom:0}.last\:border-b-0:last-child{border-bottom-width:0px}.hover\:border-po-accent:hover{--tw-border-opacity: 1;border-color:rgb(212 149 42 / var(--tw-border-opacity, 1))}.hover\:border-po-border-focus:hover{--tw-border-opacity: 1;border-color:rgb(92 87 82 / var(--tw-border-opacity, 1))}.hover\:bg-po-accent-muted:hover{--tw-bg-opacity: 1;background-color:rgb(154 109 32 / var(--tw-bg-opacity, 1))}.hover\:bg-po-surface-2:hover{--tw-bg-opacity: 1;background-color:rgb(44 42 40 / var(--tw-bg-opacity, 1))}.hover\:bg-po-surface-3:hover{--tw-bg-opacity: 1;background-color:rgb(54 52 50 / var(--tw-bg-opacity, 1))}.hover\:text-po-accent:hover{--tw-text-opacity: 1;color:rgb(212 149 42 / var(--tw-text-opacity, 1))}.hover\:text-po-text-primary:hover{--tw-text-opacity: 1;color:rgb(232 226 218 / var(--tw-text-opacity, 1))}.hover\:text-po-text-secondary:hover{--tw-text-opacity: 1;color:rgb(168 162 154 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-po-accent:focus{--tw-border-opacity: 1;border-color:rgb(212 149 42 / var(--tw-border-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:640px){.sm\:inline{display:inline}}
|
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
|
|
8
|
+
<link href="https://cdn.jsdelivr.net/fontsource/fonts/geist@latest/latin-400-normal.woff2" as="font" type="font/woff2" crossorigin />
|
|
9
|
+
<link href="https://cdn.jsdelivr.net/npm/geist@1/dist/fonts/geist-sans/style.css" rel="stylesheet" />
|
|
10
|
+
<link href="https://cdn.jsdelivr.net/npm/geist@1/dist/fonts/geist-mono/style.css" rel="stylesheet" />
|
|
7
11
|
<title>PromptObjects</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
12
|
+
<script type="module" crossorigin src="/assets/index-DEPawnfZ.js"></script>
|
|
13
|
+
<link rel="stylesheet" crossorigin href="/assets/index-oMrRce1m.css">
|
|
10
14
|
</head>
|
|
11
|
-
<body class="bg-po-bg text-
|
|
15
|
+
<body class="bg-po-bg text-po-text-primary font-ui">
|
|
12
16
|
<div id="root"></div>
|
|
13
17
|
</body>
|
|
14
18
|
</html>
|
|
@@ -156,7 +156,7 @@ module PromptObjects
|
|
|
156
156
|
type: "po_state",
|
|
157
157
|
payload: {
|
|
158
158
|
name: po.name,
|
|
159
|
-
state:
|
|
159
|
+
state: po.to_state_hash(registry: @runtime.registry).merge(status: "idle")
|
|
160
160
|
}
|
|
161
161
|
)
|
|
162
162
|
end
|
|
@@ -227,6 +227,8 @@ module PromptObjects
|
|
|
227
227
|
handle_get_session_usage(message["payload"])
|
|
228
228
|
when "export_thread"
|
|
229
229
|
handle_export_thread(message["payload"])
|
|
230
|
+
when "get_env_data_list"
|
|
231
|
+
handle_get_env_data_list(message["payload"])
|
|
230
232
|
when "ping"
|
|
231
233
|
send_message(type: "pong", payload: {})
|
|
232
234
|
else
|
|
@@ -269,7 +271,7 @@ module PromptObjects
|
|
|
269
271
|
type: "po_state",
|
|
270
272
|
payload: {
|
|
271
273
|
name: po_name,
|
|
272
|
-
state: { sessions: po.list_sessions.map { |s|
|
|
274
|
+
state: { sessions: po.list_sessions.map { |s| PromptObject.serialize_session(s) } }
|
|
273
275
|
}
|
|
274
276
|
)
|
|
275
277
|
end
|
|
@@ -311,7 +313,7 @@ module PromptObjects
|
|
|
311
313
|
payload: {
|
|
312
314
|
target: po_obj.name,
|
|
313
315
|
session_id: session_id,
|
|
314
|
-
messages: history.map { |m|
|
|
316
|
+
messages: history.map { |m| PromptObject.serialize_message(m) }
|
|
315
317
|
}
|
|
316
318
|
)
|
|
317
319
|
}
|
|
@@ -350,11 +352,24 @@ module PromptObjects
|
|
|
350
352
|
type: "po_state",
|
|
351
353
|
payload: {
|
|
352
354
|
name: po_name,
|
|
353
|
-
state: { sessions: po.list_sessions.map { |s|
|
|
355
|
+
state: { sessions: po.list_sessions.map { |s| PromptObject.serialize_session(s) } }
|
|
354
356
|
}
|
|
355
357
|
)
|
|
356
358
|
rescue => e
|
|
357
359
|
send_error("Error from #{po_name}: #{e.message}")
|
|
360
|
+
|
|
361
|
+
# Broadcast rich error context for diagnostics
|
|
362
|
+
llm_config = @runtime.llm_config
|
|
363
|
+
send_message(
|
|
364
|
+
type: "llm_error",
|
|
365
|
+
payload: {
|
|
366
|
+
po_name: po_name,
|
|
367
|
+
provider: llm_config[:provider],
|
|
368
|
+
model: llm_config[:model],
|
|
369
|
+
error: e.message,
|
|
370
|
+
error_class: e.class.name
|
|
371
|
+
}
|
|
372
|
+
)
|
|
358
373
|
ensure
|
|
359
374
|
# Clean up the callback
|
|
360
375
|
po.on_history_updated = nil
|
|
@@ -425,7 +440,7 @@ module PromptObjects
|
|
|
425
440
|
# Also send updated PO state
|
|
426
441
|
send_message(
|
|
427
442
|
type: "po_state",
|
|
428
|
-
payload: { name: po_name, state:
|
|
443
|
+
payload: { name: po_name, state: po.to_state_hash(registry: @runtime.registry) }
|
|
429
444
|
)
|
|
430
445
|
end
|
|
431
446
|
|
|
@@ -445,7 +460,7 @@ module PromptObjects
|
|
|
445
460
|
# Send updated PO state with new session's messages
|
|
446
461
|
send_message(
|
|
447
462
|
type: "po_state",
|
|
448
|
-
payload: { name: po_name, state:
|
|
463
|
+
payload: { name: po_name, state: po.to_state_hash(registry: @runtime.registry) }
|
|
449
464
|
)
|
|
450
465
|
else
|
|
451
466
|
send_error("Could not switch to session: #{session_id}")
|
|
@@ -475,7 +490,7 @@ module PromptObjects
|
|
|
475
490
|
# Also send updated PO state
|
|
476
491
|
send_message(
|
|
477
492
|
type: "po_state",
|
|
478
|
-
payload: { name: po_name, state:
|
|
493
|
+
payload: { name: po_name, state: po.to_state_hash(registry: @runtime.registry) }
|
|
479
494
|
)
|
|
480
495
|
end
|
|
481
496
|
|
|
@@ -501,7 +516,7 @@ module PromptObjects
|
|
|
501
516
|
info = LLM::Factory.provider_info(provider)
|
|
502
517
|
{
|
|
503
518
|
name: provider,
|
|
504
|
-
models:
|
|
519
|
+
models: LLM::Factory.models_for(provider),
|
|
505
520
|
default_model: info[:default_model],
|
|
506
521
|
available: LLM::Factory.available_providers[provider]
|
|
507
522
|
}
|
|
@@ -626,56 +641,28 @@ module PromptObjects
|
|
|
626
641
|
)
|
|
627
642
|
end
|
|
628
643
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
end
|
|
634
|
-
|
|
635
|
-
def po_state_hash(po)
|
|
636
|
-
{
|
|
637
|
-
status: po.instance_variable_get(:@state) || "idle",
|
|
638
|
-
description: po.description,
|
|
639
|
-
capabilities: declared_capabilities_info(po),
|
|
640
|
-
universal_capabilities: universal_capabilities_info,
|
|
641
|
-
current_session: current_session_hash(po),
|
|
642
|
-
sessions: po.list_sessions.map { |s| session_summary(s) },
|
|
643
|
-
# Include full prompt for inspection
|
|
644
|
-
prompt: po.body,
|
|
645
|
-
config: po.config
|
|
646
|
-
}
|
|
647
|
-
end
|
|
644
|
+
def handle_get_env_data_list(payload)
|
|
645
|
+
session_id = payload["session_id"]
|
|
646
|
+
return send_error("Session ID required") unless session_id
|
|
647
|
+
return send_error("No session store available") unless @runtime.session_store
|
|
648
648
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
declared.map do |name|
|
|
652
|
-
cap = @runtime.registry.get(name)
|
|
653
|
-
{
|
|
654
|
-
name: name,
|
|
655
|
-
description: cap&.description || "Capability not found",
|
|
656
|
-
parameters: cap&.parameters
|
|
657
|
-
}
|
|
658
|
-
end
|
|
659
|
-
end
|
|
649
|
+
root_thread_id = @runtime.session_store.resolve_root_thread(session_id)
|
|
650
|
+
entries = @runtime.session_store.list_env_data_full(root_thread_id: root_thread_id)
|
|
660
651
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
parameters: cap&.parameters
|
|
652
|
+
send_message(
|
|
653
|
+
type: "env_data_list",
|
|
654
|
+
payload: {
|
|
655
|
+
session_id: session_id,
|
|
656
|
+
root_thread_id: root_thread_id,
|
|
657
|
+
entries: entries
|
|
668
658
|
}
|
|
669
|
-
|
|
659
|
+
)
|
|
670
660
|
end
|
|
671
661
|
|
|
672
|
-
|
|
673
|
-
return nil unless po.session_id
|
|
662
|
+
# === Helpers ===
|
|
674
663
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
messages: po.history.map { |m| message_to_hash(m) }
|
|
678
|
-
}
|
|
664
|
+
def send_error(message)
|
|
665
|
+
send_message(type: "error", payload: { message: message })
|
|
679
666
|
end
|
|
680
667
|
|
|
681
668
|
# Get messages for a specific session (may not be current session)
|
|
@@ -683,20 +670,7 @@ module PromptObjects
|
|
|
683
670
|
return [] unless @runtime.session_store
|
|
684
671
|
|
|
685
672
|
messages = @runtime.session_store.get_messages(session_id)
|
|
686
|
-
messages.map { |m|
|
|
687
|
-
end
|
|
688
|
-
|
|
689
|
-
def session_summary(session)
|
|
690
|
-
{
|
|
691
|
-
id: session[:id],
|
|
692
|
-
name: session[:name],
|
|
693
|
-
message_count: session[:message_count] || 0,
|
|
694
|
-
updated_at: session[:updated_at]&.iso8601,
|
|
695
|
-
# Thread fields
|
|
696
|
-
parent_session_id: session[:parent_session_id],
|
|
697
|
-
parent_po: session[:parent_po],
|
|
698
|
-
thread_type: session[:thread_type] || "root"
|
|
699
|
-
}
|
|
673
|
+
messages.map { |m| PromptObject.serialize_message(m) }
|
|
700
674
|
end
|
|
701
675
|
|
|
702
676
|
# Recursively serialize a thread tree for JSON
|
|
@@ -704,42 +678,11 @@ module PromptObjects
|
|
|
704
678
|
return nil unless tree
|
|
705
679
|
|
|
706
680
|
{
|
|
707
|
-
session:
|
|
681
|
+
session: PromptObject.serialize_session(tree[:session]),
|
|
708
682
|
children: (tree[:children] || []).map { |child| serialize_thread_tree(child) }
|
|
709
683
|
}
|
|
710
684
|
end
|
|
711
685
|
|
|
712
|
-
def message_to_hash(msg)
|
|
713
|
-
case msg[:role]
|
|
714
|
-
when :user
|
|
715
|
-
# In-memory messages use :from, SQLite-loaded messages use :from_po
|
|
716
|
-
from = msg[:from] || msg[:from_po]
|
|
717
|
-
{ role: "user", content: msg[:content], from: from }
|
|
718
|
-
when :assistant
|
|
719
|
-
hash = { role: "assistant", content: msg[:content] }
|
|
720
|
-
if msg[:tool_calls]
|
|
721
|
-
hash[:tool_calls] = msg[:tool_calls].map do |tc|
|
|
722
|
-
# Handle both ToolCall objects and Hashes (from DB with symbol or string keys)
|
|
723
|
-
if tc.is_a?(LLM::ToolCall)
|
|
724
|
-
{ id: tc.id, name: tc.name, arguments: tc.arguments }
|
|
725
|
-
else
|
|
726
|
-
tc_id = tc[:id] || tc["id"]
|
|
727
|
-
tc_name = tc[:name] || tc["name"]
|
|
728
|
-
tc_args = tc[:arguments] || tc["arguments"] || {}
|
|
729
|
-
{ id: tc_id, name: tc_name, arguments: tc_args }
|
|
730
|
-
end
|
|
731
|
-
end
|
|
732
|
-
end
|
|
733
|
-
hash
|
|
734
|
-
when :tool
|
|
735
|
-
# In-memory messages use :results, SQLite-loaded messages use :tool_results
|
|
736
|
-
results = msg[:results] || msg[:tool_results]
|
|
737
|
-
{ role: "tool", results: results }
|
|
738
|
-
else
|
|
739
|
-
{ role: msg[:role].to_s, content: msg[:content] }
|
|
740
|
-
end
|
|
741
|
-
end
|
|
742
|
-
|
|
743
686
|
def request_to_hash(request)
|
|
744
687
|
{
|
|
745
688
|
id: request.id,
|
|
@@ -40,7 +40,7 @@ module PromptObjects
|
|
|
40
40
|
type: "po_added",
|
|
41
41
|
payload: {
|
|
42
42
|
name: po.name,
|
|
43
|
-
state:
|
|
43
|
+
state: po.to_state_hash(registry: runtime.registry)
|
|
44
44
|
}
|
|
45
45
|
)
|
|
46
46
|
puts "Broadcast: PO registered - #{po.name}"
|
|
@@ -53,7 +53,7 @@ module PromptObjects
|
|
|
53
53
|
type: "po_modified",
|
|
54
54
|
payload: {
|
|
55
55
|
name: po.name,
|
|
56
|
-
state:
|
|
56
|
+
state: po.to_state_hash(registry: runtime.registry)
|
|
57
57
|
}
|
|
58
58
|
)
|
|
59
59
|
puts "Broadcast: PO modified (programmatic) - #{po.name}"
|
|
@@ -64,7 +64,7 @@ module PromptObjects
|
|
|
64
64
|
if env_path
|
|
65
65
|
file_watcher = FileWatcher.new(runtime: runtime, env_path: env_path)
|
|
66
66
|
file_watcher.subscribe do |event, data|
|
|
67
|
-
handle_file_event(app, event, data)
|
|
67
|
+
handle_file_event(app, event, data, runtime: runtime)
|
|
68
68
|
end
|
|
69
69
|
file_watcher.start
|
|
70
70
|
end
|
|
@@ -140,7 +140,7 @@ module PromptObjects
|
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
# Handle file change events and broadcast to connected clients.
|
|
143
|
-
def self.handle_file_event(app, event, data)
|
|
143
|
+
def self.handle_file_event(app, event, data, runtime: nil)
|
|
144
144
|
case event
|
|
145
145
|
when :po_added
|
|
146
146
|
po = data
|
|
@@ -148,7 +148,7 @@ module PromptObjects
|
|
|
148
148
|
type: "po_added",
|
|
149
149
|
payload: {
|
|
150
150
|
name: po.name,
|
|
151
|
-
state:
|
|
151
|
+
state: po.to_state_hash(registry: runtime&.registry)
|
|
152
152
|
}
|
|
153
153
|
)
|
|
154
154
|
puts "Broadcast: PO added - #{po.name}"
|
|
@@ -159,7 +159,7 @@ module PromptObjects
|
|
|
159
159
|
type: "po_modified",
|
|
160
160
|
payload: {
|
|
161
161
|
name: po.name,
|
|
162
|
-
state:
|
|
162
|
+
state: po.to_state_hash(registry: runtime&.registry)
|
|
163
163
|
}
|
|
164
164
|
)
|
|
165
165
|
puts "Broadcast: PO modified - #{po.name}"
|
|
@@ -173,61 +173,5 @@ module PromptObjects
|
|
|
173
173
|
end
|
|
174
174
|
end
|
|
175
175
|
|
|
176
|
-
# Helper to convert PO to state hash for broadcasting.
|
|
177
|
-
def self.po_state_hash(po)
|
|
178
|
-
{
|
|
179
|
-
status: po.instance_variable_get(:@state) || "idle",
|
|
180
|
-
description: po.description,
|
|
181
|
-
capabilities: po.config["capabilities"] || [],
|
|
182
|
-
current_session: current_session_hash(po),
|
|
183
|
-
sessions: po.list_sessions.map do |s|
|
|
184
|
-
{
|
|
185
|
-
id: s[:id],
|
|
186
|
-
name: s[:name],
|
|
187
|
-
message_count: s[:message_count] || 0,
|
|
188
|
-
updated_at: s[:updated_at]&.iso8601,
|
|
189
|
-
# Thread fields
|
|
190
|
-
parent_session_id: s[:parent_session_id],
|
|
191
|
-
parent_po: s[:parent_po],
|
|
192
|
-
thread_type: s[:thread_type] || "root"
|
|
193
|
-
}
|
|
194
|
-
end,
|
|
195
|
-
prompt: po.body,
|
|
196
|
-
config: po.config
|
|
197
|
-
}
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
# Helper to get current session data for a PO.
|
|
201
|
-
def self.current_session_hash(po)
|
|
202
|
-
return nil unless po.session_id
|
|
203
|
-
|
|
204
|
-
{
|
|
205
|
-
id: po.session_id,
|
|
206
|
-
messages: po.history.map { |m| message_to_hash(m) }
|
|
207
|
-
}
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
# Helper to convert a message to JSON-serializable hash.
|
|
211
|
-
def self.message_to_hash(msg)
|
|
212
|
-
case msg[:role]
|
|
213
|
-
when :user
|
|
214
|
-
{ role: "user", content: msg[:content], from: msg[:from] }
|
|
215
|
-
when :assistant
|
|
216
|
-
hash = { role: "assistant", content: msg[:content] }
|
|
217
|
-
if msg[:tool_calls]
|
|
218
|
-
hash[:tool_calls] = msg[:tool_calls].map do |tc|
|
|
219
|
-
tc_id = tc.respond_to?(:id) ? tc.id : (tc[:id] || tc["id"])
|
|
220
|
-
tc_name = tc.respond_to?(:name) ? tc.name : (tc[:name] || tc["name"])
|
|
221
|
-
tc_args = tc.respond_to?(:arguments) ? tc.arguments : (tc[:arguments] || tc["arguments"] || {})
|
|
222
|
-
{ id: tc_id, name: tc_name, arguments: tc_args }
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
hash
|
|
226
|
-
when :tool
|
|
227
|
-
{ role: "tool", results: msg[:results] }
|
|
228
|
-
else
|
|
229
|
-
{ role: msg[:role].to_s, content: msg[:content] }
|
|
230
|
-
end
|
|
231
|
-
end
|
|
232
176
|
end
|
|
233
177
|
end
|
|
@@ -9,7 +9,7 @@ module PromptObjects
|
|
|
9
9
|
# SQLite-based session storage for conversation history.
|
|
10
10
|
# Each environment has its own sessions.db file (gitignored for privacy).
|
|
11
11
|
class Store
|
|
12
|
-
SCHEMA_VERSION =
|
|
12
|
+
SCHEMA_VERSION = 7
|
|
13
13
|
|
|
14
14
|
# Thread types for conversation branching
|
|
15
15
|
THREAD_TYPES = %w[root continuation delegation fork].freeze
|
|
@@ -595,6 +595,137 @@ module PromptObjects
|
|
|
595
595
|
row["count"]
|
|
596
596
|
end
|
|
597
597
|
|
|
598
|
+
# --- Environment Data (Shared Key-Value Store) ---
|
|
599
|
+
|
|
600
|
+
# Resolve the root thread ID for a session by walking up the delegation chain.
|
|
601
|
+
# @param session_id [String] Any session ID in a delegation chain
|
|
602
|
+
# @return [String] The root thread's session ID
|
|
603
|
+
def resolve_root_thread(session_id)
|
|
604
|
+
lineage = get_thread_lineage(session_id)
|
|
605
|
+
return session_id if lineage.empty?
|
|
606
|
+
|
|
607
|
+
lineage.first[:id]
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
# Store a key-value pair scoped to a root thread.
|
|
611
|
+
# Uses INSERT OR REPLACE to create or overwrite.
|
|
612
|
+
# @param root_thread_id [String] Root thread scope
|
|
613
|
+
# @param key [String] Data key
|
|
614
|
+
# @param short_description [String] Brief description for discoverability
|
|
615
|
+
# @param value [Object] Data value (will be JSON-serialized)
|
|
616
|
+
# @param stored_by [String] PO name that stored this
|
|
617
|
+
def store_env_data(root_thread_id:, key:, short_description:, value:, stored_by:)
|
|
618
|
+
now = Time.now.utc.iso8601
|
|
619
|
+
json_value = JSON.generate(value)
|
|
620
|
+
|
|
621
|
+
@db.execute(<<~SQL, [root_thread_id, key, short_description, json_value, stored_by, now, now])
|
|
622
|
+
INSERT INTO env_data (root_thread_id, key, short_description, value, stored_by, created_at, updated_at)
|
|
623
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
624
|
+
ON CONFLICT(root_thread_id, key) DO UPDATE SET
|
|
625
|
+
short_description = excluded.short_description,
|
|
626
|
+
value = excluded.value,
|
|
627
|
+
stored_by = excluded.stored_by,
|
|
628
|
+
updated_at = excluded.updated_at
|
|
629
|
+
SQL
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
# Get a single env data entry by key.
|
|
633
|
+
# @param root_thread_id [String] Root thread scope
|
|
634
|
+
# @param key [String] Data key
|
|
635
|
+
# @return [Hash, nil] Entry with parsed value, or nil if not found
|
|
636
|
+
def get_env_data(root_thread_id:, key:)
|
|
637
|
+
row = @db.get_first_row(<<~SQL, [root_thread_id, key])
|
|
638
|
+
SELECT key, short_description, value, stored_by, created_at, updated_at
|
|
639
|
+
FROM env_data
|
|
640
|
+
WHERE root_thread_id = ? AND key = ?
|
|
641
|
+
SQL
|
|
642
|
+
|
|
643
|
+
return nil unless row
|
|
644
|
+
|
|
645
|
+
{
|
|
646
|
+
key: row["key"],
|
|
647
|
+
short_description: row["short_description"],
|
|
648
|
+
value: JSON.parse(row["value"], symbolize_names: true),
|
|
649
|
+
stored_by: row["stored_by"],
|
|
650
|
+
created_at: row["created_at"],
|
|
651
|
+
updated_at: row["updated_at"]
|
|
652
|
+
}
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
# List all env data keys and descriptions for a root thread (no values).
|
|
656
|
+
# @param root_thread_id [String] Root thread scope
|
|
657
|
+
# @return [Array<Hash>] Entries with key and short_description only
|
|
658
|
+
def list_env_data(root_thread_id:)
|
|
659
|
+
rows = @db.execute(<<~SQL, [root_thread_id])
|
|
660
|
+
SELECT key, short_description FROM env_data
|
|
661
|
+
WHERE root_thread_id = ?
|
|
662
|
+
ORDER BY key ASC
|
|
663
|
+
SQL
|
|
664
|
+
|
|
665
|
+
rows.map { |row| { key: row["key"], short_description: row["short_description"] } }
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
# List all env data entries with full values for a root thread.
|
|
669
|
+
# @param root_thread_id [String] Root thread scope
|
|
670
|
+
# @return [Array<Hash>] Full entries with parsed values
|
|
671
|
+
def list_env_data_full(root_thread_id:)
|
|
672
|
+
rows = @db.execute(<<~SQL, [root_thread_id])
|
|
673
|
+
SELECT key, short_description, value, stored_by, created_at, updated_at
|
|
674
|
+
FROM env_data WHERE root_thread_id = ? ORDER BY key ASC
|
|
675
|
+
SQL
|
|
676
|
+
|
|
677
|
+
rows.map do |row|
|
|
678
|
+
{
|
|
679
|
+
key: row["key"],
|
|
680
|
+
short_description: row["short_description"],
|
|
681
|
+
value: JSON.parse(row["value"]),
|
|
682
|
+
stored_by: row["stored_by"],
|
|
683
|
+
created_at: row["created_at"],
|
|
684
|
+
updated_at: row["updated_at"]
|
|
685
|
+
}
|
|
686
|
+
end
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
# Update an existing env data entry.
|
|
690
|
+
# @param root_thread_id [String] Root thread scope
|
|
691
|
+
# @param key [String] Data key
|
|
692
|
+
# @param short_description [String, nil] New description (keeps existing if nil)
|
|
693
|
+
# @param value [Object, nil] New value (keeps existing if nil)
|
|
694
|
+
# @param stored_by [String] PO name performing the update
|
|
695
|
+
# @return [Boolean] True if updated, false if key not found
|
|
696
|
+
def update_env_data(root_thread_id:, key:, short_description: nil, value: nil, stored_by:)
|
|
697
|
+
existing = get_env_data(root_thread_id: root_thread_id, key: key)
|
|
698
|
+
return false unless existing
|
|
699
|
+
|
|
700
|
+
updates = ["updated_at = ?", "stored_by = ?"]
|
|
701
|
+
params = [Time.now.utc.iso8601, stored_by]
|
|
702
|
+
|
|
703
|
+
if short_description
|
|
704
|
+
updates << "short_description = ?"
|
|
705
|
+
params << short_description
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
if value
|
|
709
|
+
updates << "value = ?"
|
|
710
|
+
params << JSON.generate(value)
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
params << root_thread_id
|
|
714
|
+
params << key
|
|
715
|
+
|
|
716
|
+
@db.execute("UPDATE env_data SET #{updates.join(', ')} WHERE root_thread_id = ? AND key = ?", params)
|
|
717
|
+
true
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
# Delete an env data entry.
|
|
721
|
+
# @param root_thread_id [String] Root thread scope
|
|
722
|
+
# @param key [String] Data key
|
|
723
|
+
# @return [Boolean] True if deleted, false if key not found
|
|
724
|
+
def delete_env_data(root_thread_id:, key:)
|
|
725
|
+
@db.execute("DELETE FROM env_data WHERE root_thread_id = ? AND key = ?", [root_thread_id, key])
|
|
726
|
+
@db.changes > 0
|
|
727
|
+
end
|
|
728
|
+
|
|
598
729
|
# --- Usage Aggregation ---
|
|
599
730
|
|
|
600
731
|
# Get total token usage for a session.
|
|
@@ -763,7 +894,7 @@ module PromptObjects
|
|
|
763
894
|
tree = get_thread_tree(session_id)
|
|
764
895
|
return nil unless tree
|
|
765
896
|
|
|
766
|
-
serialize_tree_for_export(tree)
|
|
897
|
+
serialize_tree_for_export(tree, is_root: true)
|
|
767
898
|
end
|
|
768
899
|
|
|
769
900
|
# --- Import ---
|
|
@@ -908,11 +1039,11 @@ module PromptObjects
|
|
|
908
1039
|
end
|
|
909
1040
|
end
|
|
910
1041
|
|
|
911
|
-
def serialize_tree_for_export(node)
|
|
1042
|
+
def serialize_tree_for_export(node, is_root: false)
|
|
912
1043
|
session = node[:session]
|
|
913
1044
|
messages = get_messages(session[:id])
|
|
914
1045
|
|
|
915
|
-
{
|
|
1046
|
+
result = {
|
|
916
1047
|
session: {
|
|
917
1048
|
id: session[:id],
|
|
918
1049
|
po_name: session[:po_name],
|
|
@@ -934,6 +1065,13 @@ module PromptObjects
|
|
|
934
1065
|
},
|
|
935
1066
|
children: (node[:children] || []).map { |c| serialize_tree_for_export(c) }
|
|
936
1067
|
}
|
|
1068
|
+
|
|
1069
|
+
if is_root
|
|
1070
|
+
env_data = list_env_data_full(root_thread_id: session[:id])
|
|
1071
|
+
result[:env_data] = env_data unless env_data.empty?
|
|
1072
|
+
end
|
|
1073
|
+
|
|
1074
|
+
result
|
|
937
1075
|
end
|
|
938
1076
|
|
|
939
1077
|
def setup_schema
|
|
@@ -1030,6 +1168,21 @@ module PromptObjects
|
|
|
1030
1168
|
|
|
1031
1169
|
CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
|
|
1032
1170
|
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
|
|
1171
|
+
|
|
1172
|
+
-- Shared environment data for delegation chains (v7)
|
|
1173
|
+
CREATE TABLE IF NOT EXISTS env_data (
|
|
1174
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1175
|
+
root_thread_id TEXT NOT NULL,
|
|
1176
|
+
key TEXT NOT NULL,
|
|
1177
|
+
short_description TEXT NOT NULL,
|
|
1178
|
+
value TEXT NOT NULL,
|
|
1179
|
+
stored_by TEXT NOT NULL,
|
|
1180
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
1181
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
1182
|
+
UNIQUE(root_thread_id, key)
|
|
1183
|
+
);
|
|
1184
|
+
|
|
1185
|
+
CREATE INDEX IF NOT EXISTS idx_env_data_root ON env_data(root_thread_id);
|
|
1033
1186
|
SQL
|
|
1034
1187
|
end
|
|
1035
1188
|
|
|
@@ -1107,6 +1260,25 @@ module PromptObjects
|
|
|
1107
1260
|
# Add usage column for token tracking
|
|
1108
1261
|
@db.execute("ALTER TABLE messages ADD COLUMN usage TEXT")
|
|
1109
1262
|
end
|
|
1263
|
+
|
|
1264
|
+
if from_version < 7
|
|
1265
|
+
# Add shared environment data table for delegation chains
|
|
1266
|
+
@db.execute_batch(<<~SQL)
|
|
1267
|
+
CREATE TABLE IF NOT EXISTS env_data (
|
|
1268
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1269
|
+
root_thread_id TEXT NOT NULL,
|
|
1270
|
+
key TEXT NOT NULL,
|
|
1271
|
+
short_description TEXT NOT NULL,
|
|
1272
|
+
value TEXT NOT NULL,
|
|
1273
|
+
stored_by TEXT NOT NULL,
|
|
1274
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
1275
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
1276
|
+
UNIQUE(root_thread_id, key)
|
|
1277
|
+
);
|
|
1278
|
+
|
|
1279
|
+
CREATE INDEX IF NOT EXISTS idx_env_data_root ON env_data(root_thread_id);
|
|
1280
|
+
SQL
|
|
1281
|
+
end
|
|
1110
1282
|
end
|
|
1111
1283
|
|
|
1112
1284
|
def empty_usage
|