prompt_objects 0.1.0 → 0.2.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.
@@ -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:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,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}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.-right-1{right:-.25rem}.-top-1{top:-.25rem}.bottom-4{bottom:1rem}.left-2{left:.5rem}.right-0{right:0}.right-4{right:1rem}.top-0{top:0}.top-16{top:4rem}.top-full{top:100%}.z-10{z-index:10}.z-50{z-index:50}.my-3{margin-top:.75rem;margin-bottom:.75rem}.my-4{margin-top:1rem;margin-bottom:1rem}.-mb-px{margin-bottom:-1px}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.ml-1{margin-left:.25rem}.ml-11{margin-left:2.75rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-14{height:3.5rem}.h-2{height:.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-8{height:2rem}.h-96{height:24rem}.h-full{height:100%}.h-screen{height:100vh}.max-h-64{max-height:16rem}.max-h-\[60vh\]{max-height:60vh}.w-1\.5{width:.375rem}.w-2{width:.5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-56{width:14rem}.w-64{width:16rem}.w-8{width:2rem}.w-80{width:20rem}.w-96{width:24rem}.w-full{width:100%}.min-w-full{min-width:100%}.max-w-\[80\%\]{max-width:80%}.max-w-none{max-width:none}.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 bounce{0%,to{transform:translateY(-25%);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;animation-timing-function:cubic-bezier(0,0,.2,1)}}.animate-bounce{animation:bounce 1s infinite}@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}.resize-none{resize:none}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.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-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))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.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-b-2{border-bottom-width:2px}.border-l{border-left-width:1px}.border-l-4{border-left-width:4px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-po-accent{--tw-border-opacity: 1;border-color:rgb(124 58 237 / var(--tw-border-opacity, 1))}.border-po-border{--tw-border-opacity: 1;border-color:rgb(45 45 68 / var(--tw-border-opacity, 1))}.border-po-border\/30{border-color:#2d2d444d}.border-po-border\/50{border-color:#2d2d4480}.border-po-warning\/30{border-color:#f59e0b4d}.border-transparent{border-color:transparent}.bg-blue-600\/30{background-color:#2563eb4d}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity, 1))}.bg-gray-600\/30{background-color:#4b55634d}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-po-accent{--tw-bg-opacity: 1;background-color:rgb(124 58 237 / var(--tw-bg-opacity, 1))}.bg-po-accent\/20{background-color:#7c3aed33}.bg-po-bg{--tw-bg-opacity: 1;background-color:rgb(15 15 26 / var(--tw-bg-opacity, 1))}.bg-po-bg\/50{background-color:#0f0f1a80}.bg-po-bg\/80{background-color:#0f0f1acc}.bg-po-border{--tw-bg-opacity: 1;background-color:rgb(45 45 68 / var(--tw-bg-opacity, 1))}.bg-po-surface{--tw-bg-opacity: 1;background-color:rgb(26 26 46 / var(--tw-bg-opacity, 1))}.bg-po-surface\/50{background-color:#1a1a2e80}.bg-po-warning{--tw-bg-opacity: 1;background-color:rgb(245 158 11 / var(--tw-bg-opacity, 1))}.bg-po-warning\/10{background-color:#f59e0b1a}.bg-purple-600\/30{background-color:#9333ea4d}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.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-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pl-4{padding-left:1rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-\[10px\]{font-size:10px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.italic{font-style:italic}.leading-relaxed{line-height:1.625}.tracking-wide{letter-spacing:.025em}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-gray-100{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-po-accent{--tw-text-opacity: 1;color:rgb(124 58 237 / var(--tw-text-opacity, 1))}.text-po-accent\/70{color:#7c3aedb3}.text-po-warning{--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.text-purple-300{--tw-text-opacity: 1;color:rgb(216 180 254 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.placeholder-gray-500::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.placeholder-gray-500::placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.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)}.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-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}html{color-scheme:dark}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{--tw-bg-opacity: 1;background-color:rgb(15 15 26 / var(--tw-bg-opacity, 1))}::-webkit-scrollbar-thumb{border-radius:9999px;--tw-bg-opacity: 1;background-color:rgb(45 45 68 / var(--tw-bg-opacity, 1))}::-webkit-scrollbar-thumb:hover{--tw-bg-opacity: 1;background-color:rgb(124 58 237 / var(--tw-bg-opacity, 1))}.first\:mt-0:first-child{margin-top:0}.last\:mb-0:last-child{margin-bottom:0}.last\:border-0:last-child{border-width:0px}.hover\:border-po-accent:hover{--tw-border-opacity: 1;border-color:rgb(124 58 237 / var(--tw-border-opacity, 1))}.hover\:border-po-accent\/50:hover{border-color:#7c3aed80}.hover\:bg-po-accent\/50:hover{background-color:#7c3aed80}.hover\:bg-po-accent\/80:hover{background-color:#7c3aedcc}.hover\:bg-po-bg:hover{--tw-bg-opacity: 1;background-color:rgb(15 15 26 / var(--tw-bg-opacity, 1))}.hover\:bg-po-bg\/70:hover{background-color:#0f0f1ab3}.hover\:bg-po-border:hover{--tw-bg-opacity: 1;background-color:rgb(45 45 68 / var(--tw-bg-opacity, 1))}.hover\:bg-po-surface:hover{--tw-bg-opacity: 1;background-color:rgb(26 26 46 / var(--tw-bg-opacity, 1))}.hover\:bg-po-warning\/20:hover{background-color:#f59e0b33}.hover\:text-po-accent:hover{--tw-text-opacity: 1;color:rgb(124 58 237 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-po-accent:focus{--tw-border-opacity: 1;border-color:rgb(124 58 237 / var(--tw-border-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--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(1px + 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)}.focus\:ring-po-accent:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(124 58 237 / var(--tw-ring-opacity, 1))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:text-po-accent{--tw-text-opacity: 1;color:rgb(124 58 237 / var(--tw-text-opacity, 1))}@media(min-width:768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(min-width:1280px){.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}
@@ -5,8 +5,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
7
  <title>PromptObjects</title>
8
- <script type="module" crossorigin src="/assets/index-2acS2FYZ.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-DXU5uRXQ.css">
8
+ <script type="module" crossorigin src="/assets/index-CeNJvqLG.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-Vx4-uMOU.css">
10
10
  </head>
11
11
  <body class="bg-po-bg text-gray-100">
12
12
  <div id="root"></div>
@@ -219,6 +219,8 @@ module PromptObjects
219
219
  handle_get_llm_config
220
220
  when "switch_llm"
221
221
  handle_switch_llm(message["payload"])
222
+ when "update_prompt"
223
+ handle_update_prompt(message["payload"])
222
224
  when "ping"
223
225
  send_message(type: "pong", payload: {})
224
226
  else
@@ -295,9 +297,20 @@ module PromptObjects
295
297
  context = @runtime.context(tui_mode: true)
296
298
  context.current_capability = "human"
297
299
 
300
+ # Set up callback for real-time tool call updates
301
+ # This fires after each tool call iteration in the receive loop
302
+ po.on_history_updated = ->(po_obj, session_id, history) {
303
+ send_message(
304
+ type: "session_updated",
305
+ payload: {
306
+ target: po_obj.name,
307
+ session_id: session_id,
308
+ messages: history.map { |m| message_to_hash(m) }
309
+ }
310
+ )
311
+ }
312
+
298
313
  begin
299
- # TODO: Add streaming support to receive()
300
- # For now, just get the full response
301
314
  response = po.receive(content, context: context)
302
315
 
303
316
  # Auto-name the thread if it doesn't have a name yet
@@ -337,6 +350,9 @@ module PromptObjects
337
350
  rescue => e
338
351
  send_error("Error from #{po_name}: #{e.message}")
339
352
  ensure
353
+ # Clean up the callback
354
+ po.on_history_updated = nil
355
+
340
356
  # Update PO state back to idle (status only, not session data)
341
357
  send_message(
342
358
  type: "po_state",
@@ -523,6 +539,30 @@ module PromptObjects
523
539
  end
524
540
  end
525
541
 
542
+ def handle_update_prompt(payload)
543
+ po_name = payload["target"]
544
+ new_prompt = payload["prompt"]
545
+
546
+ po = @runtime.registry.get(po_name)
547
+ return send_error("Unknown prompt object: #{po_name}") unless po.is_a?(PromptObject)
548
+
549
+ # Update the body in memory
550
+ po.instance_variable_set(:@body, new_prompt)
551
+
552
+ # Persist to file
553
+ if po.save
554
+ # Notify for real-time UI update (broadcasts to all clients)
555
+ @runtime.notify_po_modified(po)
556
+
557
+ send_message(
558
+ type: "prompt_updated",
559
+ payload: { target: po_name, success: true }
560
+ )
561
+ else
562
+ send_error("Failed to save prompt for #{po_name}")
563
+ end
564
+ end
565
+
526
566
  # === Helpers ===
527
567
 
528
568
  def send_error(message)
@@ -533,7 +573,8 @@ module PromptObjects
533
573
  {
534
574
  status: po.instance_variable_get(:@state) || "idle",
535
575
  description: po.description,
536
- capabilities: po.config["capabilities"] || [],
576
+ capabilities: declared_capabilities_info(po),
577
+ universal_capabilities: universal_capabilities_info,
537
578
  current_session: current_session_hash(po),
538
579
  sessions: po.list_sessions.map { |s| session_summary(s) },
539
580
  # Include full prompt for inspection
@@ -542,6 +583,29 @@ module PromptObjects
542
583
  }
543
584
  end
544
585
 
586
+ def declared_capabilities_info(po)
587
+ declared = po.config["capabilities"] || []
588
+ declared.map do |name|
589
+ cap = @runtime.registry.get(name)
590
+ {
591
+ name: name,
592
+ description: cap&.description || "Capability not found",
593
+ parameters: cap&.parameters
594
+ }
595
+ end
596
+ end
597
+
598
+ def universal_capabilities_info
599
+ UNIVERSAL_CAPABILITIES.map do |name|
600
+ cap = @runtime.registry.get(name)
601
+ {
602
+ name: name,
603
+ description: cap&.description || "Universal capability",
604
+ parameters: cap&.parameters
605
+ }
606
+ end
607
+ end
608
+
545
609
  def current_session_hash(po)
546
610
  return nil unless po.session_id
547
611
 
@@ -46,6 +46,19 @@ module PromptObjects
46
46
  puts "Broadcast: PO registered - #{po.name}"
47
47
  }
48
48
 
49
+ # Register callback for PO modification notifications
50
+ # This fires when capabilities are added/removed programmatically
51
+ runtime.on_po_modified = ->(po) {
52
+ app.broadcast(
53
+ type: "po_modified",
54
+ payload: {
55
+ name: po.name,
56
+ state: po_state_hash(po)
57
+ }
58
+ )
59
+ puts "Broadcast: PO modified (programmatic) - #{po.name}"
60
+ }
61
+
49
62
  # Start file watcher for live updates (manual file edits)
50
63
  file_watcher = nil
51
64
  if env_path
@@ -63,7 +63,12 @@ module PromptObjects
63
63
  target_po.config["capabilities"] << capability
64
64
 
65
65
  # Persist to file so it's available on restart
66
- if target_po.save
66
+ saved = target_po.save
67
+
68
+ # Notify for real-time UI update (don't wait for file watcher)
69
+ context.env.notify_po_modified(target_po)
70
+
71
+ if saved
67
72
  "Added '#{capability}' to '#{target}' and saved to file. It can now use this capability."
68
73
  else
69
74
  "Added '#{capability}' to '#{target}' (in-memory only, could not save to file). It can now use this capability."
@@ -69,7 +69,12 @@ module PromptObjects
69
69
  target_po.config["capabilities"] << primitive_name
70
70
 
71
71
  # Persist to file so it's available on restart
72
- if target_po.save
72
+ saved = target_po.save
73
+
74
+ # Notify for real-time UI update
75
+ context.env.notify_po_modified(target_po)
76
+
77
+ if saved
73
78
  "Added '#{primitive_name}' to your capabilities and saved to file. You can now use it."
74
79
  else
75
80
  "Added '#{primitive_name}' to your capabilities (in-memory only). You can now use it."
@@ -110,6 +110,10 @@ module PromptObjects
110
110
  creator_po.config["capabilities"] << cap_name
111
111
  # Persist the change to file
112
112
  saved = creator_po.save ? " and saved" : ""
113
+
114
+ # Notify for real-time UI update
115
+ context.env.notify_po_modified(creator_po)
116
+
113
117
  return "Also added '#{cap_name}' to #{creator_name}'s capabilities#{saved}."
114
118
  end
115
119
 
@@ -160,6 +160,10 @@ module PromptObjects
160
160
  unless caller_po.config["capabilities"].include?(prim_name)
161
161
  caller_po.config["capabilities"] << prim_name
162
162
  saved = caller_po.save ? " and saved to file" : ""
163
+
164
+ # Notify for real-time UI update
165
+ context.env.notify_po_modified(caller_po)
166
+
163
167
  return "Added '#{prim_name}' to your capabilities#{saved}."
164
168
  end
165
169
 
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Universal
5
+ # Universal capability to delete a primitive file from the environment.
6
+ # This is for recovery when a broken primitive is causing problems.
7
+ # USE WITH CAUTION - this permanently deletes the primitive file.
8
+ class DeletePrimitive < Primitive
9
+ # Built-in primitives that cannot be deleted
10
+ PROTECTED_PRIMITIVES = %w[read_file list_files write_file http_get].freeze
11
+
12
+ def name
13
+ "delete_primitive"
14
+ end
15
+
16
+ def description
17
+ "Delete a primitive (Ruby tool) file from the environment. This is a DESTRUCTIVE operation - use for recovery when a broken primitive is causing problems. Cannot delete built-in primitives."
18
+ end
19
+
20
+ def parameters
21
+ {
22
+ type: "object",
23
+ properties: {
24
+ primitive: {
25
+ type: "string",
26
+ description: "Name of the primitive to delete"
27
+ },
28
+ confirm: {
29
+ type: "boolean",
30
+ description: "Must be true to confirm deletion. This is a safety check."
31
+ }
32
+ },
33
+ required: ["primitive", "confirm"]
34
+ }
35
+ end
36
+
37
+ def receive(message, context:)
38
+ primitive_name = message[:primitive] || message["primitive"]
39
+ confirm = message[:confirm] || message["confirm"]
40
+
41
+ # Safety check
42
+ unless confirm == true
43
+ return "Error: Must set confirm=true to delete a primitive. This is a destructive operation."
44
+ end
45
+
46
+ # Check if it's a protected primitive
47
+ if PROTECTED_PRIMITIVES.include?(primitive_name)
48
+ return "Error: Cannot delete built-in primitive '#{primitive_name}'."
49
+ end
50
+
51
+ # Check if it's a universal capability
52
+ if UNIVERSAL_CAPABILITIES.include?(primitive_name)
53
+ return "Error: Cannot delete universal capability '#{primitive_name}'."
54
+ end
55
+
56
+ # Find the primitive file
57
+ primitives_dir = context.env.primitives_dir
58
+ path = File.join(primitives_dir, "#{primitive_name}.rb")
59
+
60
+ unless File.exist?(path)
61
+ return "Error: Primitive file not found at #{path}. It may be a built-in primitive or not exist."
62
+ end
63
+
64
+ # Unregister from registry first
65
+ context.env.registry.unregister(primitive_name)
66
+
67
+ # Delete the file
68
+ begin
69
+ File.delete(path)
70
+ "Deleted primitive '#{primitive_name}' and removed from registry. File: #{path}"
71
+ rescue => e
72
+ "Error deleting file: #{e.message}. Primitive was unregistered from memory."
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Universal
5
+ # Universal capability for POs to modify their own or other POs' markdown body (prompt).
6
+ # This enables self-modification of behavior, learning, and identity.
7
+ # Note: This does NOT modify the frontmatter (capabilities, etc.) - use add_capability for that.
8
+ class ModifyPrompt < Primitive
9
+ def name
10
+ "modify_prompt"
11
+ end
12
+
13
+ def description
14
+ "Modify a prompt object's markdown body (its identity/behavior prompt). Can append, prepend, replace sections, or do a full rewrite. Does NOT change capabilities - use add_capability for that."
15
+ end
16
+
17
+ def parameters
18
+ {
19
+ type: "object",
20
+ properties: {
21
+ target: {
22
+ type: "string",
23
+ description: "Name of the prompt object to modify. Use 'self' for the current PO."
24
+ },
25
+ operation: {
26
+ type: "string",
27
+ enum: ["append", "prepend", "replace_section", "rewrite"],
28
+ description: "Type of modification: 'append' adds to end, 'prepend' adds to beginning, 'replace_section' replaces a specific section, 'rewrite' replaces the entire prompt"
29
+ },
30
+ content: {
31
+ type: "string",
32
+ description: "The new content to add or replace with"
33
+ },
34
+ section: {
35
+ type: "string",
36
+ description: "(For replace_section only) The section heading to replace (e.g., '## Learnings', '## Behavior'). The section and all content until the next heading of same or higher level will be replaced."
37
+ }
38
+ },
39
+ required: ["target", "operation", "content"]
40
+ }
41
+ end
42
+
43
+ def receive(message, context:)
44
+ target = message[:target] || message["target"]
45
+ operation = message[:operation] || message["operation"]
46
+ content = message[:content] || message["content"]
47
+ section = message[:section] || message["section"]
48
+
49
+ # Resolve 'self' to the calling PO
50
+ target = context.calling_po if target == "self"
51
+
52
+ # Find the target PO
53
+ target_po = context.env.registry.get(target)
54
+ unless target_po
55
+ return "Error: Prompt object '#{target}' not found"
56
+ end
57
+
58
+ unless target_po.is_a?(PromptObject)
59
+ return "Error: '#{target}' is not a prompt object"
60
+ end
61
+
62
+ # Get current body
63
+ current_body = target_po.body || ""
64
+
65
+ # Apply the operation
66
+ new_body = case operation
67
+ when "append"
68
+ append_content(current_body, content)
69
+ when "prepend"
70
+ prepend_content(current_body, content)
71
+ when "replace_section"
72
+ unless section
73
+ return "Error: 'section' parameter required for replace_section operation"
74
+ end
75
+ replace_section(current_body, section, content)
76
+ when "rewrite"
77
+ content
78
+ else
79
+ return "Error: Unknown operation '#{operation}'. Use: append, prepend, replace_section, or rewrite"
80
+ end
81
+
82
+ # Update the PO's body in memory
83
+ target_po.instance_variable_set(:@body, new_body)
84
+
85
+ # Persist to file
86
+ saved = target_po.save
87
+
88
+ # Notify for real-time UI update
89
+ context.env.notify_po_modified(target_po)
90
+
91
+ if saved
92
+ describe_change(operation, target, section)
93
+ else
94
+ "Modified '#{target}' prompt (in-memory only, could not save to file)."
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ def append_content(body, content)
101
+ body.empty? ? content : "#{body.rstrip}\n\n#{content}"
102
+ end
103
+
104
+ def prepend_content(body, content)
105
+ body.empty? ? content : "#{content}\n\n#{body.lstrip}"
106
+ end
107
+
108
+ def replace_section(body, section_heading, new_content)
109
+ # Normalize section heading (ensure it starts with #)
110
+ heading = section_heading.strip
111
+ heading = "## #{heading}" unless heading.start_with?("#")
112
+
113
+ # Determine heading level
114
+ heading_level = heading.match(/^(#+)/)[1].length
115
+
116
+ # Find the section
117
+ lines = body.lines
118
+ section_start = nil
119
+ section_end = nil
120
+
121
+ lines.each_with_index do |line, i|
122
+ if line.strip.downcase == heading.downcase || line.strip.downcase.start_with?("#{heading.downcase} ")
123
+ section_start = i
124
+ elsif section_start && !section_end
125
+ # Check if this is a heading of same or higher level
126
+ if line.match?(/^(#+)\s/)
127
+ line_level = line.match(/^(#+)/)[1].length
128
+ if line_level <= heading_level
129
+ section_end = i
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ unless section_start
136
+ # Section not found - append as new section
137
+ return append_content(body, "#{heading}\n\n#{new_content}")
138
+ end
139
+
140
+ # If section_end not found, section goes to end of document
141
+ section_end ||= lines.length
142
+
143
+ # Build new body
144
+ before = lines[0...section_start].join
145
+ after = lines[section_end..].join
146
+
147
+ "#{before.rstrip}\n\n#{heading}\n\n#{new_content}\n\n#{after.lstrip}".strip
148
+ end
149
+
150
+ def describe_change(operation, target, section)
151
+ case operation
152
+ when "append"
153
+ "Appended content to '#{target}' prompt and saved to file."
154
+ when "prepend"
155
+ "Prepended content to '#{target}' prompt and saved to file."
156
+ when "replace_section"
157
+ "Replaced section '#{section}' in '#{target}' prompt and saved to file."
158
+ when "rewrite"
159
+ "Rewrote '#{target}' prompt entirely and saved to file."
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Universal
5
+ # Universal capability to remove capabilities from a prompt object.
6
+ # Useful for cleanup or recovery when a capability is broken or no longer needed.
7
+ class RemoveCapability < Primitive
8
+ def name
9
+ "remove_capability"
10
+ end
11
+
12
+ def description
13
+ "Remove a capability from a prompt object. This removes it from the PO's declared capabilities but does NOT delete the underlying primitive/PO file. Use delete_primitive to fully remove a broken primitive."
14
+ end
15
+
16
+ def parameters
17
+ {
18
+ type: "object",
19
+ properties: {
20
+ target: {
21
+ type: "string",
22
+ description: "Name of the prompt object to remove the capability from. Use 'self' for the current PO."
23
+ },
24
+ capability: {
25
+ type: "string",
26
+ description: "Name of the capability to remove"
27
+ }
28
+ },
29
+ required: ["target", "capability"]
30
+ }
31
+ end
32
+
33
+ def receive(message, context:)
34
+ target = message[:target] || message["target"]
35
+ capability = message[:capability] || message["capability"]
36
+
37
+ # Resolve 'self' to the calling PO
38
+ target = context.calling_po if target == "self"
39
+
40
+ # Find the target PO
41
+ target_po = context.env.registry.get(target)
42
+ unless target_po
43
+ return "Error: Prompt object '#{target}' not found"
44
+ end
45
+
46
+ unless target_po.is_a?(PromptObject)
47
+ return "Error: '#{target}' is not a prompt object"
48
+ end
49
+
50
+ # Check if the capability is declared
51
+ current_caps = target_po.config["capabilities"] || []
52
+ unless current_caps.include?(capability)
53
+ return "'#{target}' does not have '#{capability}' in its declared capabilities."
54
+ end
55
+
56
+ # Remove the capability
57
+ target_po.config["capabilities"].delete(capability)
58
+
59
+ # Persist to file
60
+ saved = target_po.save
61
+
62
+ # Notify for real-time UI update
63
+ context.env.notify_po_modified(target_po)
64
+
65
+ if saved
66
+ "Removed '#{capability}' from '#{target}' and saved to file."
67
+ else
68
+ "Removed '#{capability}' from '#{target}' (in-memory only, could not save to file)."
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -11,7 +11,7 @@ module PromptObjects
11
11
  class Error < StandardError; end
12
12
 
13
13
  # Universal capabilities available to all prompt objects (don't need to be declared)
14
- UNIVERSAL_CAPABILITIES = %w[ask_human think create_capability add_capability list_capabilities list_primitives add_primitive create_primitive verify_primitive modify_primitive request_primitive].freeze
14
+ UNIVERSAL_CAPABILITIES = %w[ask_human think create_capability add_capability remove_capability list_capabilities list_primitives add_primitive create_primitive delete_primitive verify_primitive modify_primitive request_primitive modify_prompt].freeze
15
15
  end
16
16
 
17
17
  require_relative "prompt_objects/capability"
@@ -49,13 +49,16 @@ require_relative "prompt_objects/universal/ask_human"
49
49
  require_relative "prompt_objects/universal/think"
50
50
  require_relative "prompt_objects/universal/create_capability"
51
51
  require_relative "prompt_objects/universal/add_capability"
52
+ require_relative "prompt_objects/universal/remove_capability"
52
53
  require_relative "prompt_objects/universal/list_capabilities"
53
54
  require_relative "prompt_objects/universal/list_primitives"
54
55
  require_relative "prompt_objects/universal/add_primitive"
55
56
  require_relative "prompt_objects/universal/create_primitive"
57
+ require_relative "prompt_objects/universal/delete_primitive"
56
58
  require_relative "prompt_objects/universal/verify_primitive"
57
59
  require_relative "prompt_objects/universal/modify_primitive"
58
60
  require_relative "prompt_objects/universal/request_primitive"
61
+ require_relative "prompt_objects/universal/modify_prompt"
59
62
 
60
63
  # Connectors (different interfaces to environments)
61
64
  require_relative "prompt_objects/connectors/base"
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "prompt_objects"
5
- spec.version = "0.1.0"
5
+ spec.version = "0.2.0"
6
6
  spec.authors = ["Scott Werner"]
7
7
  spec.email = ["scott@sublayer.com"]
8
8
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prompt_objects
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Werner
@@ -276,8 +276,8 @@ files:
276
276
  - lib/prompt_objects/server/api/routes.rb
277
277
  - lib/prompt_objects/server/app.rb
278
278
  - lib/prompt_objects/server/file_watcher.rb
279
- - lib/prompt_objects/server/public/assets/index-2acS2FYZ.js
280
- - lib/prompt_objects/server/public/assets/index-DXU5uRXQ.css
279
+ - lib/prompt_objects/server/public/assets/index-CeNJvqLG.js
280
+ - lib/prompt_objects/server/public/assets/index-Vx4-uMOU.css
281
281
  - lib/prompt_objects/server/public/index.html
282
282
  - lib/prompt_objects/server/websocket_handler.rb
283
283
  - lib/prompt_objects/session/store.rb
@@ -286,9 +286,12 @@ files:
286
286
  - lib/prompt_objects/universal/ask_human.rb
287
287
  - lib/prompt_objects/universal/create_capability.rb
288
288
  - lib/prompt_objects/universal/create_primitive.rb
289
+ - lib/prompt_objects/universal/delete_primitive.rb
289
290
  - lib/prompt_objects/universal/list_capabilities.rb
290
291
  - lib/prompt_objects/universal/list_primitives.rb
291
292
  - lib/prompt_objects/universal/modify_primitive.rb
293
+ - lib/prompt_objects/universal/modify_prompt.rb
294
+ - lib/prompt_objects/universal/remove_capability.rb
292
295
  - lib/prompt_objects/universal/request_primitive.rb
293
296
  - lib/prompt_objects/universal/think.rb
294
297
  - lib/prompt_objects/universal/verify_primitive.rb