prompt_objects 0.5.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 +16 -0
- data/CLAUDE.md +7 -6
- data/README.md +140 -19
- data/frontend/src/App.tsx +2 -1
- data/frontend/src/components/EnvDataPane.tsx +69 -0
- data/frontend/src/components/Inspector.tsx +32 -1
- data/frontend/src/hooks/useWebSocket.ts +42 -0
- data/frontend/src/index.css +2 -3
- data/frontend/src/store/index.ts +32 -0
- data/frontend/src/types/index.ts +11 -0
- data/lib/prompt_objects/environment.rb +15 -0
- data/lib/prompt_objects/prompt_object.rb +113 -7
- data/lib/prompt_objects/server/api/routes.rb +13 -0
- data/lib/prompt_objects/server/app.rb +14 -0
- data/lib/prompt_objects/server/public/assets/{index-D1myxE0l.js → index-DEPawnfZ.js} +209 -209
- data/lib/prompt_objects/server/public/assets/index-oMrRce1m.css +1 -0
- data/lib/prompt_objects/server/public/index.html +2 -2
- data/lib/prompt_objects/server/websocket_handler.rb +20 -0
- 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/tools/thread-explorer.html +27 -0
- metadata +9 -6
- data/Gemfile.lock +0 -233
- data/IMPLEMENTATION_PLAN.md +0 -1073
- data/design-doc-v2.md +0 -1232
- data/lib/prompt_objects/server/public/assets/index-DdCcwC-Z.css +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1e9bb13a5f2b407279723343fe43ce570119e64a3fe2a115e4f427d195ec7040
|
|
4
|
+
data.tar.gz: fc1300754c1774ec388928752b84dd8a60286659904b8b72053fde457842ea71
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 56e4a427b43b49fc7520f9268a3e31c4a81d39c50ad0329985ab9d678631a2a88c41d070a7e13d97c01e2328de56e917d59b2b06c078fef9174a5a7fdc5db87b
|
|
7
|
+
data.tar.gz: a6bb7914d1b58c8c445deae870c2a06c004b9e7604b72107574f89c7ea9da2b8172034c358078b920d52b57eefe24acb68668d7057f16e61ed2e62ee2a85ca95
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to PromptObjects are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.6.0] - 2026-02-17
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Shared environment data** — 5 new universal capabilities (`store_env_data`, `get_env_data`, `list_env_data`, `update_env_data`, `delete_env_data`) provide a thread-scoped key-value store for delegation chains. Data is scoped to the root thread so separate conversations stay isolated. Entries include a `short_description` for lightweight LLM discovery without fetching full values.
|
|
10
|
+
- **Live environment data pane** — New collapsible pane in the Inspector shows shared env data updating in real-time as POs store and modify entries during delegation chains. WebSocket broadcasting (`env_data_changed`, `env_data_list`), a REST endpoint (`GET /api/sessions/:id/env_data`), and env data rendering in the Thread Explorer.
|
|
11
|
+
- **Delegation context** — POs now receive context about their delegation chain. An expanded system prompt teaches POs about their nature, a delegation preamble prepends caller context to delegated messages, and the full delegation chain is built from thread lineage.
|
|
12
|
+
- **Capability guard** — `execute_tool_calls` now rejects tools not in a PO's allowed set (declared capabilities + universals). Previously the LLM could hallucinate calls to any registered tool and they would execute. Now it receives an error directing it to use `add_capability` first.
|
|
13
|
+
- **Env data in thread exports** — `serialize_tree_for_export` includes env data entries at the root level of exported thread trees. Thread Explorer renders these in an amber-colored section.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **Root font-size causing undersized text** — Removed a `font-size: 14px` on the root `html` element that made all rem-based Tailwind sizes ~12% smaller than intended (e.g. `text-xs` computed to 10.5px instead of 12px).
|
|
18
|
+
- **Invisible resize handle boundaries** — Added border styling to horizontal and vertical resize handles so pane boundaries are visible without hovering.
|
|
19
|
+
- **Stale MCP tools tests** — Fixed test expectations broken by PO serialization centralization in 0.5.0.
|
|
20
|
+
|
|
5
21
|
## [0.5.0] - 2026-02-13
|
|
6
22
|
|
|
7
23
|
### Added
|
data/CLAUDE.md
CHANGED
|
@@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
6
6
|
|
|
7
7
|
**PromptObjects** is a Ruby framework where markdown files with LLM-backed behavior act as first-class autonomous entities. The core insight: **everything is a capability**—primitives (Ruby code) and Prompt-Objects (markdown files) share the same interface, differing only in interpretation complexity.
|
|
8
8
|
|
|
9
|
-
**Current Status**: v0.5.0 — The core framework is fully implemented and functional. The original 6-phase implementation plan is complete. Active development is focused on visualization, developer experience, and exploring new primitives. See `CHANGELOG.md` for release history and `
|
|
9
|
+
**Current Status**: v0.5.0 — The core framework is fully implemented and functional. The original 6-phase implementation plan is complete. Active development is focused on visualization, developer experience, and exploring new primitives. See `CHANGELOG.md` for release history and `docs/archive/` for original design context.
|
|
10
10
|
|
|
11
11
|
## Architecture
|
|
12
12
|
|
|
@@ -19,7 +19,7 @@ RUNTIME (Environment)
|
|
|
19
19
|
├── MESSAGE BUS - routes messages, logs to SQLite for replay
|
|
20
20
|
├── SESSION STORE (SQLite) - persistent conversation threads, delegation tracking
|
|
21
21
|
├── HUMAN QUEUE - non-blocking ask_human requests
|
|
22
|
-
├── WEB SERVER (
|
|
22
|
+
├── WEB SERVER (Falcon + async-websocket) - serves React frontend
|
|
23
23
|
└── MCP SERVER - exposes POs as tools via Model Context Protocol
|
|
24
24
|
```
|
|
25
25
|
|
|
@@ -61,6 +61,7 @@ Available to all Prompt-Objects automatically (no frontmatter declaration needed
|
|
|
61
61
|
- `list_capabilities` / `list_primitives` - introspection
|
|
62
62
|
- `create_primitive` / `add_primitive` / `delete_primitive` / `verify_primitive` / `modify_primitive` / `request_primitive` - primitive management
|
|
63
63
|
- `modify_prompt` - rewrite own system prompt at runtime
|
|
64
|
+
- `store_env_data` / `get_env_data` / `list_env_data` / `update_env_data` / `delete_env_data` - thread-scoped shared key-value store for delegation chains
|
|
64
65
|
|
|
65
66
|
### PO-to-PO Delegation
|
|
66
67
|
When a PO calls another PO, the system creates an isolated delegation thread in the target PO. The caller's context is tracked so messages show correct provenance. Delegation start/complete events are broadcast via WebSocket for real-time UI updates.
|
|
@@ -69,9 +70,9 @@ When a PO calls another PO, the system creates an isolated delegation thread in
|
|
|
69
70
|
|
|
70
71
|
- **Ruby** (>= 3.2, tested through Ruby 4) - core implementation
|
|
71
72
|
- **LLM APIs** - OpenAI, Anthropic, Gemini, Ollama, OpenRouter (adapter pattern via `LLM::Factory`)
|
|
72
|
-
- **
|
|
73
|
-
- **
|
|
74
|
-
- **React + TypeScript** - web frontend (
|
|
73
|
+
- **Falcon** - async HTTP server for REST API and static file serving
|
|
74
|
+
- **async-websocket** - real-time WebSocket communication
|
|
75
|
+
- **React + TypeScript** - web frontend (Smalltalk System Browser-inspired multi-pane UI)
|
|
75
76
|
- **Three.js** - spatial canvas visualization (force-directed PO graph)
|
|
76
77
|
- **SQLite** - session persistence and event log storage
|
|
77
78
|
- **MCP** - Model Context Protocol server mode
|
|
@@ -121,7 +122,7 @@ prompt_objects/
|
|
|
121
122
|
├── frontend/ # React + TypeScript web UI
|
|
122
123
|
│ └── src/
|
|
123
124
|
│ ├── App.tsx
|
|
124
|
-
│ ├── components/ #
|
|
125
|
+
│ ├── components/ # SystemBar, ObjectList, Inspector, Workspace, Transcript
|
|
125
126
|
│ ├── canvas/ # Three.js spatial visualization
|
|
126
127
|
│ ├── hooks/ # WebSocket, state management hooks
|
|
127
128
|
│ ├── store/ # Frontend state
|
data/README.md
CHANGED
|
@@ -12,11 +12,6 @@ Prompt Objects applies this to AI. Instead of treating LLMs as external APIs you
|
|
|
12
12
|
|
|
13
13
|
This is a new computing primitive: semantic late binding at runtime, where natural language becomes the interface between intelligent components.
|
|
14
14
|
|
|
15
|
-
Blog Posts
|
|
16
|
-
==========
|
|
17
|
-
|
|
18
|
-
- [What if we took message passing seriously?](https://worksonmymachine.ai/p/what-if-we-took-message-passing-seriously) — The origin story and motivation behind Prompt Objects.
|
|
19
|
-
|
|
20
15
|
Who
|
|
21
16
|
===
|
|
22
17
|
|
|
@@ -32,8 +27,86 @@ A Ruby framework where:
|
|
|
32
27
|
- **Markdown files** define autonomous entities (Prompt Objects)
|
|
33
28
|
- **YAML frontmatter** declares capabilities and configuration
|
|
34
29
|
- **Markdown body** becomes identity and behavior (the system prompt)
|
|
35
|
-
- **Capabilities** are shared between primitives (Ruby) and Prompt Objects (markdown)
|
|
36
|
-
- **Environments** isolate collections of objects with their own memory
|
|
30
|
+
- **Capabilities** are shared between primitives (Ruby) and Prompt Objects (markdown) -- everything is a capability
|
|
31
|
+
- **Environments** isolate collections of objects with their own memory, git history, and configuration
|
|
32
|
+
- **PO-to-PO delegation** lets objects call each other through isolated threads with full provenance tracking
|
|
33
|
+
|
|
34
|
+
### Prompt Object structure
|
|
35
|
+
|
|
36
|
+
```markdown
|
|
37
|
+
---
|
|
38
|
+
name: reader
|
|
39
|
+
description: Helps people understand files
|
|
40
|
+
capabilities:
|
|
41
|
+
- read_file
|
|
42
|
+
- list_files
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
# Reader
|
|
46
|
+
## Identity
|
|
47
|
+
You are a careful, thoughtful file reader...
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Web UI
|
|
51
|
+
|
|
52
|
+
The web interface is modeled after the Smalltalk System Browser -- a multi-pane environment for inspecting and interacting with live objects:
|
|
53
|
+
|
|
54
|
+
- **ObjectList** -- permanent left pane listing all POs in the environment
|
|
55
|
+
- **Inspector** -- split into a MethodList (capabilities) and SourcePane (the prompt markdown or capability source)
|
|
56
|
+
- **Workspace** -- a REPL-style chat pane for sending messages to the selected PO
|
|
57
|
+
- **Transcript** -- bottom pane showing message bus events in real time
|
|
58
|
+
|
|
59
|
+
All panels are resizable. The inspector's top pane collapses so the Workspace can fill the full height when you just want to talk.
|
|
60
|
+
|
|
61
|
+
### Spatial Canvas
|
|
62
|
+
|
|
63
|
+
Navigate to `/canvas` for a Three.js 2D visualization of your environment. POs appear as glowing hexagonal nodes in a force-directed layout. Tool calls show as diamonds. Message arcs animate with traveling particles. Delegation lights up the target PO with a cyan glow. Click any node to inspect it. Keyboard shortcuts: F to fit, Escape to deselect.
|
|
64
|
+
|
|
65
|
+
### MCP Server mode
|
|
66
|
+
|
|
67
|
+
Run any environment as an MCP (Model Context Protocol) server for integration with Claude Desktop, Cursor, or any MCP-compatible client:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
prompt_objects serve my-assistant --mcp
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Add to your `claude_desktop_config.json`:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"mcpServers": {
|
|
78
|
+
"my-assistant": {
|
|
79
|
+
"command": "prompt_objects",
|
|
80
|
+
"args": ["serve", "--mcp", "my-assistant"]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Exposes tools for sending messages, listing POs, inspecting state, getting conversations, and responding to pending `ask_human` requests.
|
|
87
|
+
|
|
88
|
+
### Multi-provider LLM support
|
|
89
|
+
|
|
90
|
+
Swap providers with an environment variable. Adapters for:
|
|
91
|
+
|
|
92
|
+
- **OpenAI** -- GPT-5.2, GPT-4.1, o3-mini, o1
|
|
93
|
+
- **Anthropic** -- Claude Haiku 4.5, Claude Sonnet 4.5, Claude Opus 4
|
|
94
|
+
- **Gemini** -- Gemini 3 Flash, Gemini 2.5 Pro, Gemini 2.5 Flash
|
|
95
|
+
- **Ollama** -- any locally installed model, discovered automatically
|
|
96
|
+
- **OpenRouter** -- access any model through a single API key
|
|
97
|
+
|
|
98
|
+
### Thread Explorer
|
|
99
|
+
|
|
100
|
+
A standalone HTML visualizer for exported conversation threads. Open it from the CLI to browse delegation chains, message flow, and tool call sequences:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
prompt_objects explore my-env
|
|
104
|
+
prompt_objects explore my-env --session abc123
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Note on the TUI
|
|
108
|
+
|
|
109
|
+
A terminal UI (built with Charm libraries) exists in the codebase but is deprioritized. The web UI is the primary interface for day-to-day use.
|
|
37
110
|
|
|
38
111
|
How
|
|
39
112
|
===
|
|
@@ -54,25 +127,73 @@ prompt_objects env create my-project --template basic
|
|
|
54
127
|
prompt_objects serve my-project --open
|
|
55
128
|
```
|
|
56
129
|
|
|
57
|
-
###
|
|
130
|
+
### Commands
|
|
58
131
|
|
|
59
|
-
```
|
|
60
|
-
prompt_objects env
|
|
61
|
-
prompt_objects
|
|
62
|
-
prompt_objects
|
|
63
|
-
prompt_objects
|
|
132
|
+
```
|
|
133
|
+
prompt_objects serve <env> Start web server (default)
|
|
134
|
+
prompt_objects serve <env> --open Start and open browser
|
|
135
|
+
prompt_objects serve <env> --mcp Start as MCP server
|
|
136
|
+
prompt_objects serve <env> --port 4000 Custom port (default: 3000)
|
|
137
|
+
|
|
138
|
+
prompt_objects repl [name] [objects_dir] Interactive REPL with a prompt object
|
|
139
|
+
prompt_objects repl --sandbox REPL in sandbox mode (isolates changes)
|
|
140
|
+
|
|
141
|
+
prompt_objects message <env> <po> "text" Send a message and print the response
|
|
142
|
+
prompt_objects message <env> <po> "text" --json JSON output
|
|
143
|
+
prompt_objects message <env> <po> "text" --events Include event log
|
|
144
|
+
|
|
145
|
+
prompt_objects events <env> Show recent message bus events
|
|
146
|
+
prompt_objects events <env> --session ID Events for a specific session
|
|
147
|
+
prompt_objects events <env> --json JSON output
|
|
148
|
+
|
|
149
|
+
prompt_objects explore [env] Open Thread Explorer in browser
|
|
150
|
+
prompt_objects explore <env> --session ID Visualize a specific thread
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Environment management
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
prompt_objects env list List all environments
|
|
157
|
+
prompt_objects env create <name> Create new environment
|
|
158
|
+
prompt_objects env create <name> -t <tmpl> Create from template
|
|
159
|
+
prompt_objects env info <name> Show environment details
|
|
160
|
+
prompt_objects env clone <src> <dst> Clone an environment
|
|
161
|
+
prompt_objects env export <name> Export as .poenv bundle
|
|
162
|
+
prompt_objects env import <file.poenv> Import from bundle
|
|
163
|
+
prompt_objects env archive <name> Soft-delete (archive)
|
|
164
|
+
prompt_objects env restore <name> Restore archived environment
|
|
165
|
+
prompt_objects env delete <name> --permanent Permanently delete archived env
|
|
166
|
+
prompt_objects env default <name> Set the default environment
|
|
64
167
|
```
|
|
65
168
|
|
|
66
169
|
### Templates
|
|
67
170
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
171
|
+
Create an environment from a template with `prompt_objects env create <name> --template <template>`:
|
|
172
|
+
|
|
173
|
+
| Template | Description |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `basic` | No capabilities, learns as needed -- great for demos |
|
|
176
|
+
| `minimal` | Basic assistant with file reading |
|
|
177
|
+
| `developer` | Code review, debugging, testing specialists |
|
|
178
|
+
| `writer` | Editor, researcher for content creation |
|
|
179
|
+
| `empty` | Start from scratch with just a bootstrap assistant |
|
|
180
|
+
| `arc-agi-1` | ARC-AGI-1 challenge solver with grid primitives |
|
|
72
181
|
|
|
73
182
|
Extras
|
|
74
183
|
======
|
|
75
184
|
|
|
76
|
-
|
|
77
|
-
|
|
185
|
+
### Community
|
|
186
|
+
|
|
187
|
+
Join the [Discord](https://discord.gg/fcMvcwdrZS) for support, discussion, and updates.
|
|
188
|
+
|
|
189
|
+
### Blog Posts
|
|
190
|
+
|
|
191
|
+
- [What if we took message passing seriously?](https://worksonmymachine.ai/p/what-if-we-took-message-passing-seriously) -- the origin story and motivation behind Prompt Objects
|
|
192
|
+
- [As complexity grows, architecture](https://worksonmymachine.ai/p/as-complexity-grows-architecture) -- on why the hard problems are structural, not generative
|
|
193
|
+
|
|
194
|
+
### Links
|
|
195
|
+
|
|
78
196
|
- **Repository**: https://github.com/works-on-your-machine/prompt_objects
|
|
197
|
+
- **Changelog**: https://github.com/works-on-your-machine/prompt_objects/blob/main/CHANGELOG.md
|
|
198
|
+
- **License**: MIT
|
|
199
|
+
- **Ruby**: >= 3.2.0 (tested through Ruby 4)
|
data/frontend/src/App.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import { UsagePanel } from './components/UsagePanel'
|
|
|
10
10
|
import { CanvasView } from './canvas/CanvasView'
|
|
11
11
|
|
|
12
12
|
export default function App() {
|
|
13
|
-
const { sendMessage, respondToNotification, createSession, switchSession, switchLLM, createThread, updatePrompt, requestUsage, exportThread } =
|
|
13
|
+
const { sendMessage, respondToNotification, createSession, switchSession, switchLLM, createThread, updatePrompt, requestUsage, exportThread, requestEnvData } =
|
|
14
14
|
useWebSocket()
|
|
15
15
|
const { selectedPO, busOpen, notifications, usageData, clearUsageData, currentView } = useStore()
|
|
16
16
|
const selectedPOData = useSelectedPO()
|
|
@@ -64,6 +64,7 @@ export default function App() {
|
|
|
64
64
|
updatePrompt={updatePrompt}
|
|
65
65
|
requestUsage={requestUsage}
|
|
66
66
|
exportThread={exportThread}
|
|
67
|
+
requestEnvData={requestEnvData}
|
|
67
68
|
/>
|
|
68
69
|
) : (
|
|
69
70
|
<div className="h-full flex items-center justify-center text-po-text-ghost">
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
import { useStore, useEnvData } from '../store'
|
|
3
|
+
import type { EnvDataEntry } from '../types'
|
|
4
|
+
|
|
5
|
+
interface EnvDataPaneProps {
|
|
6
|
+
sessionId: string | undefined
|
|
7
|
+
requestEnvData: (sessionId: string) => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function EnvDataPane({ sessionId, requestEnvData }: EnvDataPaneProps) {
|
|
11
|
+
const sessionRootMap = useStore((s) => s.sessionRootMap)
|
|
12
|
+
const rootThreadId = sessionId ? sessionRootMap[sessionId] : undefined
|
|
13
|
+
const entries = useEnvData(rootThreadId)
|
|
14
|
+
const [expandedKey, setExpandedKey] = useState<string | null>(null)
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (sessionId) {
|
|
18
|
+
requestEnvData(sessionId)
|
|
19
|
+
}
|
|
20
|
+
}, [sessionId, requestEnvData])
|
|
21
|
+
|
|
22
|
+
if (entries.length === 0) {
|
|
23
|
+
return (
|
|
24
|
+
<div className="h-full flex items-center justify-center">
|
|
25
|
+
<span className="font-mono text-xs text-po-text-ghost">No shared data</span>
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="h-full overflow-auto px-2 py-1">
|
|
32
|
+
{entries.map((entry) => (
|
|
33
|
+
<EnvDataRow
|
|
34
|
+
key={entry.key}
|
|
35
|
+
entry={entry}
|
|
36
|
+
expanded={expandedKey === entry.key}
|
|
37
|
+
onToggle={() => setExpandedKey(expandedKey === entry.key ? null : entry.key)}
|
|
38
|
+
/>
|
|
39
|
+
))}
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function EnvDataRow({ entry, expanded, onToggle }: { entry: EnvDataEntry; expanded: boolean; onToggle: () => void }) {
|
|
45
|
+
return (
|
|
46
|
+
<div className="border-b border-po-border last:border-b-0">
|
|
47
|
+
<button
|
|
48
|
+
onClick={onToggle}
|
|
49
|
+
className="w-full text-left px-1.5 py-1.5 hover:bg-po-surface-3 transition-colors duration-150 flex items-center gap-2"
|
|
50
|
+
>
|
|
51
|
+
<span className="text-2xs text-po-text-ghost">{expanded ? '▼' : '▶'}</span>
|
|
52
|
+
<span className="font-mono text-sm text-po-accent truncate">{entry.key}</span>
|
|
53
|
+
<span className="text-xs text-po-text-ghost truncate flex-1">{entry.short_description}</span>
|
|
54
|
+
<span className="text-xs text-po-text-ghost flex-shrink-0">{entry.stored_by}</span>
|
|
55
|
+
</button>
|
|
56
|
+
{expanded && (
|
|
57
|
+
<div className="px-2 pb-2">
|
|
58
|
+
<div className="text-xs text-po-text-ghost mb-1">
|
|
59
|
+
stored by <span className="text-po-text-secondary">{entry.stored_by}</span>
|
|
60
|
+
{entry.updated_at && <> · {new Date(entry.updated_at).toLocaleTimeString()}</>}
|
|
61
|
+
</div>
|
|
62
|
+
<pre className="font-mono text-xs text-po-text-primary bg-po-surface-1 rounded p-2 overflow-auto max-h-40 whitespace-pre-wrap break-all">
|
|
63
|
+
{typeof entry.value === 'string' ? entry.value : JSON.stringify(entry.value, null, 2)}
|
|
64
|
+
</pre>
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useState, useMemo } from 'react'
|
|
2
|
-
import { useStore, usePONotifications } from '../store'
|
|
2
|
+
import { useStore, usePONotifications, useEnvData } from '../store'
|
|
3
3
|
import { useResize } from '../hooks/useResize'
|
|
4
4
|
import { MethodList } from './MethodList'
|
|
5
5
|
import { SourcePane } from './SourcePane'
|
|
6
|
+
import { EnvDataPane } from './EnvDataPane'
|
|
6
7
|
import { Workspace } from './Workspace'
|
|
7
8
|
import { ContextMenu } from './ContextMenu'
|
|
8
9
|
import { PaneSlot } from './PaneSlot'
|
|
@@ -17,6 +18,7 @@ interface InspectorProps {
|
|
|
17
18
|
updatePrompt: (target: string, prompt: string) => void
|
|
18
19
|
requestUsage?: (sessionId: string, includeTree?: boolean) => void
|
|
19
20
|
exportThread?: (sessionId: string, format?: string) => void
|
|
21
|
+
requestEnvData: (sessionId: string) => void
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export function Inspector({
|
|
@@ -27,6 +29,7 @@ export function Inspector({
|
|
|
27
29
|
updatePrompt,
|
|
28
30
|
requestUsage,
|
|
29
31
|
exportThread,
|
|
32
|
+
requestEnvData,
|
|
30
33
|
}: InspectorProps) {
|
|
31
34
|
const [selectedCapability, setSelectedCapability] = useState<CapabilityInfo | null>(null)
|
|
32
35
|
const [threadMenuOpen, setThreadMenuOpen] = useState(false)
|
|
@@ -34,6 +37,8 @@ export function Inspector({
|
|
|
34
37
|
const notifications = usePONotifications(po.name)
|
|
35
38
|
const topPaneCollapsed = useStore((s) => s.topPaneCollapsed)
|
|
36
39
|
const toggleTopPane = useStore((s) => s.toggleTopPane)
|
|
40
|
+
const envDataPaneCollapsed = useStore((s) => s.envDataPaneCollapsed)
|
|
41
|
+
const toggleEnvDataPane = useStore((s) => s.toggleEnvDataPane)
|
|
37
42
|
|
|
38
43
|
const topPaneResize = useResize({
|
|
39
44
|
direction: 'vertical',
|
|
@@ -42,6 +47,13 @@ export function Inspector({
|
|
|
42
47
|
maxSize: 600,
|
|
43
48
|
})
|
|
44
49
|
|
|
50
|
+
const envDataResize = useResize({
|
|
51
|
+
direction: 'vertical',
|
|
52
|
+
initialSize: 160,
|
|
53
|
+
minSize: 80,
|
|
54
|
+
maxSize: 400,
|
|
55
|
+
})
|
|
56
|
+
|
|
45
57
|
const methodListResize = useResize({
|
|
46
58
|
direction: 'horizontal',
|
|
47
59
|
initialSize: 192,
|
|
@@ -51,6 +63,9 @@ export function Inspector({
|
|
|
51
63
|
|
|
52
64
|
const sessions = po.sessions || []
|
|
53
65
|
const currentSessionId = po.current_session?.id
|
|
66
|
+
const sessionRootMap = useStore((s) => s.sessionRootMap)
|
|
67
|
+
const rootThreadId = currentSessionId ? sessionRootMap[currentSessionId] : undefined
|
|
68
|
+
const envDataEntries = useEnvData(rootThreadId)
|
|
54
69
|
|
|
55
70
|
// Sort sessions: current first, then by updated_at desc
|
|
56
71
|
const sortedSessions = useMemo(() => {
|
|
@@ -204,6 +219,22 @@ export function Inspector({
|
|
|
204
219
|
</div>
|
|
205
220
|
</PaneSlot>
|
|
206
221
|
|
|
222
|
+
{/* Middle: Env Data (collapsible, resizable height) */}
|
|
223
|
+
<PaneSlot
|
|
224
|
+
label={`Env Data${envDataEntries.length > 0 ? ` (${envDataEntries.length})` : ''}`}
|
|
225
|
+
collapsed={envDataPaneCollapsed}
|
|
226
|
+
onToggle={toggleEnvDataPane}
|
|
227
|
+
height={envDataResize.size}
|
|
228
|
+
resizeHandle={
|
|
229
|
+
<div
|
|
230
|
+
className="resize-handle-h"
|
|
231
|
+
onMouseDown={envDataResize.onMouseDown}
|
|
232
|
+
/>
|
|
233
|
+
}
|
|
234
|
+
>
|
|
235
|
+
<EnvDataPane sessionId={currentSessionId} requestEnvData={requestEnvData} />
|
|
236
|
+
</PaneSlot>
|
|
237
|
+
|
|
207
238
|
{/* Bottom: Workspace */}
|
|
208
239
|
<div className="flex-1 overflow-hidden">
|
|
209
240
|
<Workspace po={po} sendMessage={sendMessage} />
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
Environment,
|
|
9
9
|
Message,
|
|
10
10
|
LLMConfig,
|
|
11
|
+
EnvDataEntry,
|
|
11
12
|
SendMessagePayload,
|
|
12
13
|
RespondToNotificationPayload,
|
|
13
14
|
CreateSessionPayload,
|
|
@@ -43,6 +44,8 @@ export function useWebSocket() {
|
|
|
43
44
|
setLLMConfig,
|
|
44
45
|
updateCurrentLLM,
|
|
45
46
|
setUsageData,
|
|
47
|
+
setEnvData,
|
|
48
|
+
setSessionRoot,
|
|
46
49
|
} = useStore()
|
|
47
50
|
|
|
48
51
|
// Keep the handler ref up to date every render
|
|
@@ -243,6 +246,29 @@ export function useWebSocket() {
|
|
|
243
246
|
// Session switch confirmed — po_state update follows with full state
|
|
244
247
|
break
|
|
245
248
|
|
|
249
|
+
case 'env_data_changed': {
|
|
250
|
+
const { root_thread_id, entries } = message.payload as {
|
|
251
|
+
action: string
|
|
252
|
+
root_thread_id: string
|
|
253
|
+
key: string
|
|
254
|
+
stored_by: string
|
|
255
|
+
entries: EnvDataEntry[]
|
|
256
|
+
}
|
|
257
|
+
setEnvData(root_thread_id, entries)
|
|
258
|
+
break
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
case 'env_data_list': {
|
|
262
|
+
const { session_id, root_thread_id, entries } = message.payload as {
|
|
263
|
+
session_id: string
|
|
264
|
+
root_thread_id: string
|
|
265
|
+
entries: EnvDataEntry[]
|
|
266
|
+
}
|
|
267
|
+
setEnvData(root_thread_id, entries)
|
|
268
|
+
setSessionRoot(session_id, root_thread_id)
|
|
269
|
+
break
|
|
270
|
+
}
|
|
271
|
+
|
|
246
272
|
case 'error': {
|
|
247
273
|
const { message: errorMsg } = message.payload as { message: string }
|
|
248
274
|
console.error('Server error:', errorMsg)
|
|
@@ -488,6 +514,21 @@ export function useWebSocket() {
|
|
|
488
514
|
)
|
|
489
515
|
}, [])
|
|
490
516
|
|
|
517
|
+
// Request env data for a session
|
|
518
|
+
const requestEnvData = useCallback((sessionId: string) => {
|
|
519
|
+
if (!ws.current || ws.current.readyState !== WebSocket.OPEN) {
|
|
520
|
+
console.error('WebSocket not connected')
|
|
521
|
+
return
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
ws.current.send(
|
|
525
|
+
JSON.stringify({
|
|
526
|
+
type: 'get_env_data_list',
|
|
527
|
+
payload: { session_id: sessionId },
|
|
528
|
+
})
|
|
529
|
+
)
|
|
530
|
+
}, [])
|
|
531
|
+
|
|
491
532
|
// Update a PO's prompt (markdown body)
|
|
492
533
|
const updatePrompt = useCallback((target: string, prompt: string) => {
|
|
493
534
|
if (!ws.current || ws.current.readyState !== WebSocket.OPEN) {
|
|
@@ -513,5 +554,6 @@ export function useWebSocket() {
|
|
|
513
554
|
updatePrompt,
|
|
514
555
|
requestUsage,
|
|
515
556
|
exportThread,
|
|
557
|
+
requestEnvData,
|
|
516
558
|
}
|
|
517
559
|
}
|
data/frontend/src/index.css
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
/* Base styles */
|
|
6
6
|
html {
|
|
7
7
|
color-scheme: dark;
|
|
8
|
-
font-size: 14px;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
body {
|
|
@@ -45,11 +44,11 @@ body {
|
|
|
45
44
|
|
|
46
45
|
/* Resize handles */
|
|
47
46
|
.resize-handle {
|
|
48
|
-
@apply w-1 cursor-col-resize hover:bg-po-accent/30 active:bg-po-accent/50 transition-colors;
|
|
47
|
+
@apply w-1 cursor-col-resize hover:bg-po-accent/30 active:bg-po-accent/50 transition-colors border-l border-po-border;
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
.resize-handle-h {
|
|
52
|
-
@apply h-1 cursor-row-resize hover:bg-po-accent/30 active:bg-po-accent/50 transition-colors;
|
|
51
|
+
@apply h-1 cursor-row-resize hover:bg-po-accent/30 active:bg-po-accent/50 transition-colors border-t border-po-border;
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
/* Canvas CSS2DRenderer labels */
|
data/frontend/src/store/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
Environment,
|
|
8
8
|
Message,
|
|
9
9
|
LLMConfig,
|
|
10
|
+
EnvDataEntry,
|
|
10
11
|
} from '../types'
|
|
11
12
|
|
|
12
13
|
interface Store {
|
|
@@ -68,6 +69,15 @@ interface Store {
|
|
|
68
69
|
usageData: Record<string, unknown> | null
|
|
69
70
|
setUsageData: (data: Record<string, unknown>) => void
|
|
70
71
|
clearUsageData: () => void
|
|
72
|
+
|
|
73
|
+
// Env Data
|
|
74
|
+
envData: Record<string, EnvDataEntry[]>
|
|
75
|
+
setEnvData: (rootThreadId: string, entries: EnvDataEntry[]) => void
|
|
76
|
+
clearEnvData: (rootThreadId: string) => void
|
|
77
|
+
sessionRootMap: Record<string, string>
|
|
78
|
+
setSessionRoot: (sessionId: string, rootThreadId: string) => void
|
|
79
|
+
envDataPaneCollapsed: boolean
|
|
80
|
+
toggleEnvDataPane: () => void
|
|
71
81
|
}
|
|
72
82
|
|
|
73
83
|
export const useStore = create<Store>((set) => ({
|
|
@@ -263,6 +273,25 @@ export const useStore = create<Store>((set) => ({
|
|
|
263
273
|
usageData: null,
|
|
264
274
|
setUsageData: (data) => set({ usageData: data }),
|
|
265
275
|
clearUsageData: () => set({ usageData: null }),
|
|
276
|
+
|
|
277
|
+
// Env Data
|
|
278
|
+
envData: {},
|
|
279
|
+
setEnvData: (rootThreadId, entries) =>
|
|
280
|
+
set((s) => ({
|
|
281
|
+
envData: { ...s.envData, [rootThreadId]: entries },
|
|
282
|
+
})),
|
|
283
|
+
clearEnvData: (rootThreadId) =>
|
|
284
|
+
set((s) => {
|
|
285
|
+
const { [rootThreadId]: _, ...rest } = s.envData
|
|
286
|
+
return { envData: rest }
|
|
287
|
+
}),
|
|
288
|
+
sessionRootMap: {},
|
|
289
|
+
setSessionRoot: (sessionId, rootThreadId) =>
|
|
290
|
+
set((s) => ({
|
|
291
|
+
sessionRootMap: { ...s.sessionRootMap, [sessionId]: rootThreadId },
|
|
292
|
+
})),
|
|
293
|
+
envDataPaneCollapsed: true,
|
|
294
|
+
toggleEnvDataPane: () => set((s) => ({ envDataPaneCollapsed: !s.envDataPaneCollapsed })),
|
|
266
295
|
}))
|
|
267
296
|
|
|
268
297
|
// Selectors - use useShallow to prevent infinite re-renders with derived arrays
|
|
@@ -277,3 +306,6 @@ export const useNotificationCount = () =>
|
|
|
277
306
|
|
|
278
307
|
export const usePONotifications = (poName: string) =>
|
|
279
308
|
useStore(useShallow((s) => s.notifications.filter((n) => n.po_name === poName)))
|
|
309
|
+
|
|
310
|
+
export const useEnvData = (rootThreadId: string | undefined) =>
|
|
311
|
+
useStore(useShallow((s) => (rootThreadId ? s.envData[rootThreadId] || [] : [])))
|
data/frontend/src/types/index.ts
CHANGED
|
@@ -102,6 +102,15 @@ export interface LLMConfig {
|
|
|
102
102
|
providers: LLMProvider[]
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
export interface EnvDataEntry {
|
|
106
|
+
key: string
|
|
107
|
+
short_description: string
|
|
108
|
+
value: unknown
|
|
109
|
+
stored_by: string
|
|
110
|
+
created_at: string
|
|
111
|
+
updated_at: string
|
|
112
|
+
}
|
|
113
|
+
|
|
105
114
|
// WebSocket message types
|
|
106
115
|
export type WSMessageType =
|
|
107
116
|
| 'environment'
|
|
@@ -128,6 +137,8 @@ export type WSMessageType =
|
|
|
128
137
|
| 'thread_export'
|
|
129
138
|
| 'prompt_updated'
|
|
130
139
|
| 'llm_error'
|
|
140
|
+
| 'env_data_changed'
|
|
141
|
+
| 'env_data_list'
|
|
131
142
|
| 'error'
|
|
132
143
|
| 'pong'
|
|
133
144
|
|
|
@@ -42,6 +42,7 @@ module PromptObjects
|
|
|
42
42
|
attr_accessor :on_po_registered # Callback for when a PO is registered
|
|
43
43
|
attr_accessor :on_po_modified # Callback for when a PO is modified (capabilities changed, etc.)
|
|
44
44
|
attr_accessor :on_delegation_event # Callback for PO-to-PO delegation start/complete
|
|
45
|
+
attr_accessor :on_env_data_changed # Callback for env data store/update/delete
|
|
45
46
|
|
|
46
47
|
# Initialize from an environment path (with manifest) or objects directory.
|
|
47
48
|
# @param env_path [String, nil] Path to environment directory (preferred)
|
|
@@ -223,6 +224,15 @@ module PromptObjects
|
|
|
223
224
|
@on_delegation_event&.call(event_type, payload)
|
|
224
225
|
end
|
|
225
226
|
|
|
227
|
+
# Notify that environment data has changed (stored, updated, or deleted).
|
|
228
|
+
# @param action [String] "store", "update", or "delete"
|
|
229
|
+
# @param root_thread_id [String] Root thread scope
|
|
230
|
+
# @param key [String] The data key that changed
|
|
231
|
+
# @param stored_by [String] PO name that made the change
|
|
232
|
+
def notify_env_data_changed(action:, root_thread_id:, key:, stored_by:)
|
|
233
|
+
@on_env_data_changed&.call(action: action, root_thread_id: root_thread_id, key: key, stored_by: stored_by)
|
|
234
|
+
end
|
|
235
|
+
|
|
226
236
|
# Load a prompt object by name from the objects directory.
|
|
227
237
|
# @param name [String] Name of the prompt object (without .md extension)
|
|
228
238
|
# @return [PromptObject]
|
|
@@ -316,6 +326,11 @@ module PromptObjects
|
|
|
316
326
|
@registry.register(Universal::ModifyPrimitive.new)
|
|
317
327
|
@registry.register(Universal::RequestPrimitive.new)
|
|
318
328
|
@registry.register(Universal::ModifyPrompt.new)
|
|
329
|
+
@registry.register(Universal::StoreEnvData.new)
|
|
330
|
+
@registry.register(Universal::GetEnvData.new)
|
|
331
|
+
@registry.register(Universal::ListEnvData.new)
|
|
332
|
+
@registry.register(Universal::UpdateEnvData.new)
|
|
333
|
+
@registry.register(Universal::DeleteEnvData.new)
|
|
319
334
|
end
|
|
320
335
|
end
|
|
321
336
|
|