prompt_objects 0.1.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.
Files changed (117) hide show
  1. checksums.yaml +7 -0
  2. data/CLAUDE.md +108 -0
  3. data/Gemfile +10 -0
  4. data/Gemfile.lock +231 -0
  5. data/IMPLEMENTATION_PLAN.md +1073 -0
  6. data/LICENSE +21 -0
  7. data/README.md +73 -0
  8. data/Rakefile +27 -0
  9. data/design-doc-v2.md +1232 -0
  10. data/exe/prompt_objects +572 -0
  11. data/exe/prompt_objects_mcp +34 -0
  12. data/frontend/.gitignore +3 -0
  13. data/frontend/index.html +13 -0
  14. data/frontend/package-lock.json +4417 -0
  15. data/frontend/package.json +32 -0
  16. data/frontend/postcss.config.js +6 -0
  17. data/frontend/src/App.tsx +95 -0
  18. data/frontend/src/components/CapabilitiesPanel.tsx +44 -0
  19. data/frontend/src/components/ChatPanel.tsx +251 -0
  20. data/frontend/src/components/Dashboard.tsx +83 -0
  21. data/frontend/src/components/Header.tsx +141 -0
  22. data/frontend/src/components/MarkdownMessage.tsx +153 -0
  23. data/frontend/src/components/MessageBus.tsx +55 -0
  24. data/frontend/src/components/ModelSelector.tsx +112 -0
  25. data/frontend/src/components/NotificationPanel.tsx +134 -0
  26. data/frontend/src/components/POCard.tsx +56 -0
  27. data/frontend/src/components/PODetail.tsx +117 -0
  28. data/frontend/src/components/PromptPanel.tsx +51 -0
  29. data/frontend/src/components/SessionsPanel.tsx +174 -0
  30. data/frontend/src/components/ThreadsSidebar.tsx +119 -0
  31. data/frontend/src/components/index.ts +11 -0
  32. data/frontend/src/hooks/useWebSocket.ts +363 -0
  33. data/frontend/src/index.css +37 -0
  34. data/frontend/src/main.tsx +10 -0
  35. data/frontend/src/store/index.ts +246 -0
  36. data/frontend/src/types/index.ts +146 -0
  37. data/frontend/tailwind.config.js +25 -0
  38. data/frontend/tsconfig.json +30 -0
  39. data/frontend/vite.config.ts +29 -0
  40. data/lib/prompt_objects/capability.rb +46 -0
  41. data/lib/prompt_objects/cli.rb +431 -0
  42. data/lib/prompt_objects/connectors/base.rb +73 -0
  43. data/lib/prompt_objects/connectors/mcp.rb +524 -0
  44. data/lib/prompt_objects/environment/exporter.rb +83 -0
  45. data/lib/prompt_objects/environment/git.rb +118 -0
  46. data/lib/prompt_objects/environment/importer.rb +159 -0
  47. data/lib/prompt_objects/environment/manager.rb +401 -0
  48. data/lib/prompt_objects/environment/manifest.rb +218 -0
  49. data/lib/prompt_objects/environment.rb +283 -0
  50. data/lib/prompt_objects/human_queue.rb +144 -0
  51. data/lib/prompt_objects/llm/anthropic_adapter.rb +137 -0
  52. data/lib/prompt_objects/llm/factory.rb +84 -0
  53. data/lib/prompt_objects/llm/gemini_adapter.rb +209 -0
  54. data/lib/prompt_objects/llm/openai_adapter.rb +104 -0
  55. data/lib/prompt_objects/llm/response.rb +61 -0
  56. data/lib/prompt_objects/loader.rb +32 -0
  57. data/lib/prompt_objects/mcp/server.rb +167 -0
  58. data/lib/prompt_objects/mcp/tools/get_conversation.rb +60 -0
  59. data/lib/prompt_objects/mcp/tools/get_pending_requests.rb +54 -0
  60. data/lib/prompt_objects/mcp/tools/inspect_po.rb +73 -0
  61. data/lib/prompt_objects/mcp/tools/list_prompt_objects.rb +37 -0
  62. data/lib/prompt_objects/mcp/tools/respond_to_request.rb +68 -0
  63. data/lib/prompt_objects/mcp/tools/send_message.rb +71 -0
  64. data/lib/prompt_objects/message_bus.rb +97 -0
  65. data/lib/prompt_objects/primitive.rb +13 -0
  66. data/lib/prompt_objects/primitives/http_get.rb +72 -0
  67. data/lib/prompt_objects/primitives/list_files.rb +95 -0
  68. data/lib/prompt_objects/primitives/read_file.rb +81 -0
  69. data/lib/prompt_objects/primitives/write_file.rb +73 -0
  70. data/lib/prompt_objects/prompt_object.rb +415 -0
  71. data/lib/prompt_objects/registry.rb +88 -0
  72. data/lib/prompt_objects/server/api/routes.rb +297 -0
  73. data/lib/prompt_objects/server/app.rb +174 -0
  74. data/lib/prompt_objects/server/file_watcher.rb +113 -0
  75. data/lib/prompt_objects/server/public/assets/index-2acS2FYZ.js +77 -0
  76. data/lib/prompt_objects/server/public/assets/index-DXU5uRXQ.css +1 -0
  77. data/lib/prompt_objects/server/public/index.html +14 -0
  78. data/lib/prompt_objects/server/websocket_handler.rb +619 -0
  79. data/lib/prompt_objects/server.rb +166 -0
  80. data/lib/prompt_objects/session/store.rb +826 -0
  81. data/lib/prompt_objects/universal/add_capability.rb +74 -0
  82. data/lib/prompt_objects/universal/add_primitive.rb +113 -0
  83. data/lib/prompt_objects/universal/ask_human.rb +109 -0
  84. data/lib/prompt_objects/universal/create_capability.rb +219 -0
  85. data/lib/prompt_objects/universal/create_primitive.rb +170 -0
  86. data/lib/prompt_objects/universal/list_capabilities.rb +55 -0
  87. data/lib/prompt_objects/universal/list_primitives.rb +145 -0
  88. data/lib/prompt_objects/universal/modify_primitive.rb +180 -0
  89. data/lib/prompt_objects/universal/request_primitive.rb +287 -0
  90. data/lib/prompt_objects/universal/think.rb +41 -0
  91. data/lib/prompt_objects/universal/verify_primitive.rb +173 -0
  92. data/lib/prompt_objects.rb +62 -0
  93. data/objects/coordinator.md +48 -0
  94. data/objects/greeter.md +30 -0
  95. data/objects/reader.md +33 -0
  96. data/prompt_objects.gemspec +50 -0
  97. data/templates/basic/.gitignore +2 -0
  98. data/templates/basic/manifest.yml +7 -0
  99. data/templates/basic/objects/basic.md +32 -0
  100. data/templates/developer/.gitignore +5 -0
  101. data/templates/developer/manifest.yml +17 -0
  102. data/templates/developer/objects/code_reviewer.md +33 -0
  103. data/templates/developer/objects/coordinator.md +39 -0
  104. data/templates/developer/objects/debugger.md +35 -0
  105. data/templates/empty/.gitignore +5 -0
  106. data/templates/empty/manifest.yml +14 -0
  107. data/templates/empty/objects/.gitkeep +0 -0
  108. data/templates/empty/objects/assistant.md +41 -0
  109. data/templates/minimal/.gitignore +5 -0
  110. data/templates/minimal/manifest.yml +7 -0
  111. data/templates/minimal/objects/assistant.md +41 -0
  112. data/templates/writer/.gitignore +5 -0
  113. data/templates/writer/manifest.yml +17 -0
  114. data/templates/writer/objects/coordinator.md +33 -0
  115. data/templates/writer/objects/editor.md +33 -0
  116. data/templates/writer/objects/researcher.md +34 -0
  117. metadata +343 -0
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import App from './App'
4
+ import './index.css'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>
10
+ )
@@ -0,0 +1,246 @@
1
+ import { create } from 'zustand'
2
+ import { useShallow } from 'zustand/react/shallow'
3
+ import type {
4
+ PromptObject,
5
+ BusMessage,
6
+ Notification,
7
+ Environment,
8
+ Message,
9
+ LLMConfig,
10
+ } from '../types'
11
+
12
+ interface Store {
13
+ // Connection state
14
+ connected: boolean
15
+ setConnected: (connected: boolean) => void
16
+
17
+ // Environment
18
+ environment: Environment | null
19
+ setEnvironment: (env: Environment) => void
20
+
21
+ // Prompt Objects
22
+ promptObjects: Record<string, PromptObject>
23
+ setPromptObject: (name: string, state: Partial<PromptObject>) => void
24
+ removePromptObject: (name: string) => void
25
+ updatePromptObjectStatus: (name: string, status: PromptObject['status']) => void
26
+ addMessageToPO: (poName: string, message: Message) => void
27
+ updateSessionMessages: (poName: string, sessionId: string, messages: Message[]) => void
28
+ switchPOSession: (poName: string, sessionId: string) => void
29
+
30
+ // Navigation
31
+ selectedPO: string | null
32
+ selectPO: (name: string | null) => void
33
+ activeTab: 'chat' | 'sessions' | 'capabilities' | 'prompt'
34
+ setActiveTab: (tab: Store['activeTab']) => void
35
+
36
+ // Message Bus
37
+ busMessages: BusMessage[]
38
+ addBusMessage: (message: BusMessage) => void
39
+ busOpen: boolean
40
+ toggleBus: () => void
41
+
42
+ // Notifications
43
+ notifications: Notification[]
44
+ addNotification: (notification: Notification) => void
45
+ removeNotification: (id: string) => void
46
+
47
+ // Streaming
48
+ streamingContent: Record<string, string>
49
+ appendStreamChunk: (poName: string, chunk: string) => void
50
+ clearStream: (poName: string) => void
51
+
52
+ // Response handling
53
+ pendingResponse: Record<string, string>
54
+ setPendingResponse: (poName: string, content: string) => void
55
+ clearPendingResponse: (poName: string) => void
56
+
57
+ // LLM Config
58
+ llmConfig: LLMConfig | null
59
+ setLLMConfig: (config: LLMConfig) => void
60
+ updateCurrentLLM: (provider: string, model: string) => void
61
+ }
62
+
63
+ export const useStore = create<Store>((set) => ({
64
+ // Connection
65
+ connected: false,
66
+ setConnected: (connected) => set({ connected }),
67
+
68
+ // Environment
69
+ environment: null,
70
+ setEnvironment: (environment) => set({ environment }),
71
+
72
+ // Prompt Objects
73
+ promptObjects: {},
74
+ setPromptObject: (name, state) =>
75
+ set((s) => ({
76
+ promptObjects: {
77
+ ...s.promptObjects,
78
+ [name]: {
79
+ ...s.promptObjects[name],
80
+ ...state,
81
+ name, // Ensure name is always set
82
+ } as PromptObject,
83
+ },
84
+ })),
85
+ removePromptObject: (name) =>
86
+ set((s) => {
87
+ const { [name]: _, ...rest } = s.promptObjects
88
+ // If we're viewing this PO, deselect it
89
+ const selectedPO = s.selectedPO === name ? null : s.selectedPO
90
+ return { promptObjects: rest, selectedPO }
91
+ }),
92
+ updatePromptObjectStatus: (name, status) =>
93
+ set((s) => ({
94
+ promptObjects: {
95
+ ...s.promptObjects,
96
+ [name]: {
97
+ ...s.promptObjects[name],
98
+ status,
99
+ },
100
+ },
101
+ })),
102
+ addMessageToPO: (poName, message) =>
103
+ set((s) => {
104
+ const po = s.promptObjects[poName]
105
+ if (!po) return s
106
+
107
+ // Handle case where current_session doesn't exist yet (new POs)
108
+ const currentMessages = po.current_session?.messages || []
109
+ return {
110
+ promptObjects: {
111
+ ...s.promptObjects,
112
+ [poName]: {
113
+ ...po,
114
+ current_session: {
115
+ id: po.current_session?.id || 'pending',
116
+ messages: [...currentMessages, message],
117
+ },
118
+ },
119
+ },
120
+ }
121
+ }),
122
+ updateSessionMessages: (poName, sessionId, messages) =>
123
+ set((s) => {
124
+ const po = s.promptObjects[poName]
125
+ if (!po) return s
126
+
127
+ // Update current_session if it matches the sessionId, OR if current_session is null
128
+ // (handles newly created POs that didn't have session info yet)
129
+ if (po.current_session?.id === sessionId || !po.current_session) {
130
+ return {
131
+ promptObjects: {
132
+ ...s.promptObjects,
133
+ [poName]: {
134
+ ...po,
135
+ current_session: {
136
+ id: sessionId,
137
+ messages,
138
+ },
139
+ },
140
+ },
141
+ }
142
+ }
143
+
144
+ // Otherwise, just update the session in the sessions list (message count)
145
+ // The full messages will be loaded when the user switches to that session
146
+ return s
147
+ }),
148
+ switchPOSession: (poName, sessionId) =>
149
+ set((s) => {
150
+ const po = s.promptObjects[poName]
151
+ if (!po) return s
152
+
153
+ // Switch to a new session - clear messages until session_updated arrives
154
+ return {
155
+ promptObjects: {
156
+ ...s.promptObjects,
157
+ [poName]: {
158
+ ...po,
159
+ current_session: {
160
+ id: sessionId,
161
+ messages: [], // Will be populated by session_updated
162
+ },
163
+ },
164
+ },
165
+ }
166
+ }),
167
+
168
+ // Navigation
169
+ selectedPO: null,
170
+ selectPO: (name) => set({ selectedPO: name, activeTab: 'chat' }),
171
+ activeTab: 'chat',
172
+ setActiveTab: (activeTab) => set({ activeTab }),
173
+
174
+ // Message Bus
175
+ busMessages: [],
176
+ addBusMessage: (message) =>
177
+ set((s) => ({
178
+ busMessages: [...s.busMessages.slice(-99), message], // Keep last 100
179
+ })),
180
+ busOpen: false,
181
+ toggleBus: () => set((s) => ({ busOpen: !s.busOpen })),
182
+
183
+ // Notifications
184
+ notifications: [],
185
+ addNotification: (notification) =>
186
+ set((s) => ({
187
+ notifications: [...s.notifications, notification],
188
+ })),
189
+ removeNotification: (id) =>
190
+ set((s) => ({
191
+ notifications: s.notifications.filter((n) => n.id !== id),
192
+ })),
193
+
194
+ // Streaming
195
+ streamingContent: {},
196
+ appendStreamChunk: (poName, chunk) =>
197
+ set((s) => ({
198
+ streamingContent: {
199
+ ...s.streamingContent,
200
+ [poName]: (s.streamingContent[poName] || '') + chunk,
201
+ },
202
+ })),
203
+ clearStream: (poName) =>
204
+ set((s) => {
205
+ const { [poName]: _, ...rest } = s.streamingContent
206
+ return { streamingContent: rest }
207
+ }),
208
+
209
+ // Response handling
210
+ pendingResponse: {},
211
+ setPendingResponse: (poName, content) =>
212
+ set((s) => ({
213
+ pendingResponse: {
214
+ ...s.pendingResponse,
215
+ [poName]: content,
216
+ },
217
+ })),
218
+ clearPendingResponse: (poName) =>
219
+ set((s) => {
220
+ const { [poName]: _, ...rest } = s.pendingResponse
221
+ return { pendingResponse: rest }
222
+ }),
223
+
224
+ // LLM Config
225
+ llmConfig: null,
226
+ setLLMConfig: (config) => set({ llmConfig: config }),
227
+ updateCurrentLLM: (provider, model) =>
228
+ set((s) => ({
229
+ llmConfig: s.llmConfig
230
+ ? { ...s.llmConfig, current_provider: provider, current_model: model }
231
+ : null,
232
+ })),
233
+ }))
234
+
235
+ // Selectors - use useShallow to prevent infinite re-renders with derived arrays
236
+ export const usePromptObjects = () =>
237
+ useStore(useShallow((s) => Object.values(s.promptObjects)))
238
+
239
+ export const useSelectedPO = () =>
240
+ useStore((s) => (s.selectedPO ? s.promptObjects[s.selectedPO] : null))
241
+
242
+ export const useNotificationCount = () =>
243
+ useStore((s) => s.notifications.length)
244
+
245
+ export const usePONotifications = (poName: string) =>
246
+ useStore(useShallow((s) => s.notifications.filter((n) => n.po_name === poName)))
@@ -0,0 +1,146 @@
1
+ // Core types for PromptObjects frontend
2
+
3
+ export interface Message {
4
+ role: 'user' | 'assistant' | 'tool'
5
+ content: string | null
6
+ from?: string
7
+ tool_calls?: ToolCall[]
8
+ results?: ToolResult[]
9
+ }
10
+
11
+ export interface ToolCall {
12
+ id: string
13
+ name: string
14
+ arguments: Record<string, unknown>
15
+ }
16
+
17
+ export interface ToolResult {
18
+ tool_call_id: string
19
+ content: string
20
+ }
21
+
22
+ export type ThreadType = 'root' | 'continuation' | 'delegation' | 'fork'
23
+
24
+ export interface Session {
25
+ id: string
26
+ name: string | null
27
+ message_count: number
28
+ updated_at?: string
29
+ // Thread fields
30
+ parent_session_id?: string
31
+ parent_po?: string
32
+ thread_type: ThreadType
33
+ }
34
+
35
+ export interface ThreadTree {
36
+ session: Session
37
+ children: ThreadTree[]
38
+ }
39
+
40
+ export interface CurrentSession {
41
+ id: string
42
+ messages: Message[]
43
+ }
44
+
45
+ export interface PromptObject {
46
+ name: string
47
+ description: string
48
+ status: 'idle' | 'thinking' | 'calling_tool'
49
+ capabilities: string[]
50
+ current_session: CurrentSession | null
51
+ sessions: Session[]
52
+ prompt?: string // The markdown body/prompt
53
+ config?: Record<string, unknown> // The YAML frontmatter config
54
+ }
55
+
56
+ export interface BusMessage {
57
+ from: string
58
+ to: string
59
+ content: string
60
+ timestamp: string
61
+ }
62
+
63
+ export interface Notification {
64
+ id: string
65
+ po_name: string
66
+ type: string
67
+ message: string
68
+ options: string[]
69
+ }
70
+
71
+ export interface Environment {
72
+ name: string
73
+ path: string
74
+ po_count: number
75
+ primitive_count: number
76
+ }
77
+
78
+ // LLM Provider configuration
79
+ export interface LLMProvider {
80
+ name: string
81
+ models: string[]
82
+ default_model: string
83
+ available: boolean
84
+ }
85
+
86
+ export interface LLMConfig {
87
+ current_provider: string
88
+ current_model: string
89
+ providers: LLMProvider[]
90
+ }
91
+
92
+ // WebSocket message types
93
+ export type WSMessageType =
94
+ | 'environment'
95
+ | 'po_state'
96
+ | 'po_response'
97
+ | 'po_added'
98
+ | 'po_modified'
99
+ | 'po_removed'
100
+ | 'stream'
101
+ | 'stream_end'
102
+ | 'bus_message'
103
+ | 'notification'
104
+ | 'notification_resolved'
105
+ | 'session_created'
106
+ | 'session_switched'
107
+ | 'session_updated'
108
+ | 'thread_created'
109
+ | 'thread_tree'
110
+ | 'llm_config'
111
+ | 'llm_switched'
112
+ | 'error'
113
+ | 'pong'
114
+
115
+ export interface WSMessage<T = unknown> {
116
+ type: WSMessageType
117
+ payload: T
118
+ }
119
+
120
+ // Client -> Server message types
121
+ export interface SendMessagePayload {
122
+ target: string
123
+ content: string
124
+ new_thread?: boolean // If true, create a new thread before sending
125
+ }
126
+
127
+ export interface RespondToNotificationPayload {
128
+ id: string
129
+ response: string
130
+ }
131
+
132
+ export interface CreateSessionPayload {
133
+ target: string
134
+ name?: string
135
+ }
136
+
137
+ export interface SwitchSessionPayload {
138
+ target: string
139
+ session_id: string
140
+ }
141
+
142
+ export interface CreateThreadPayload {
143
+ target: string
144
+ name?: string
145
+ thread_type?: ThreadType
146
+ }
@@ -0,0 +1,25 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./index.html",
5
+ "./src/**/*.{js,ts,jsx,tsx}",
6
+ ],
7
+ theme: {
8
+ extend: {
9
+ colors: {
10
+ // Custom colors for PromptObjects
11
+ po: {
12
+ bg: '#0f0f1a',
13
+ surface: '#1a1a2e',
14
+ border: '#2d2d44',
15
+ accent: '#7c3aed',
16
+ 'accent-hover': '#9061f9',
17
+ success: '#22c55e',
18
+ warning: '#f59e0b',
19
+ error: '#ef4444',
20
+ },
21
+ },
22
+ },
23
+ },
24
+ plugins: [],
25
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "isolatedModules": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
15
+ "jsx": "react-jsx",
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+
23
+ /* Paths */
24
+ "baseUrl": ".",
25
+ "paths": {
26
+ "@/*": ["src/*"]
27
+ }
28
+ },
29
+ "include": ["src"]
30
+ }
@@ -0,0 +1,29 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+
8
+ // Output to Ruby server's public directory for production
9
+ build: {
10
+ outDir: '../lib/prompt_objects/server/public',
11
+ emptyOutDir: true,
12
+ },
13
+
14
+ // Development proxy to Ruby server
15
+ server: {
16
+ port: 5173,
17
+ proxy: {
18
+ '/api': {
19
+ target: 'http://localhost:3000',
20
+ changeOrigin: true,
21
+ },
22
+ // WebSocket proxy
23
+ '/ws': {
24
+ target: 'ws://localhost:3000',
25
+ ws: true,
26
+ },
27
+ },
28
+ },
29
+ })
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ # Base class for all capabilities (both primitives and prompt objects).
5
+ # Everything in the system implements this interface.
6
+ class Capability
7
+ attr_reader :name, :description
8
+ attr_accessor :state
9
+
10
+ def initialize
11
+ @state = :idle
12
+ end
13
+
14
+ # Handle a message and return a response.
15
+ # @param message [String, Hash] The incoming message
16
+ # @param context [Context] Execution context with environment reference
17
+ # @return [String] The response
18
+ def receive(message, context:)
19
+ raise NotImplementedError, "#{self.class} must implement #receive"
20
+ end
21
+
22
+ # Generate a tool descriptor for LLM function calling.
23
+ # @return [Hash] OpenAI-compatible tool descriptor
24
+ def descriptor
25
+ {
26
+ type: "function",
27
+ function: {
28
+ name: name,
29
+ description: description,
30
+ parameters: parameters
31
+ }
32
+ }
33
+ end
34
+
35
+ # Define the parameters this capability accepts.
36
+ # Override in subclasses for specific parameter schemas.
37
+ # @return [Hash] JSON Schema for parameters
38
+ def parameters
39
+ {
40
+ type: "object",
41
+ properties: {},
42
+ required: []
43
+ }
44
+ end
45
+ end
46
+ end