@hachej/boring-workspace 0.1.9 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{CommandPalette-CWcgmHpK.js → CommandPalette-Dme9em28.js} +2101 -1996
- package/dist/{FileTree-CKG6iiCM.js → FileTree-BVfqs3rR.js} +1 -1
- package/dist/{MarkdownEditor-C77O0ZVD.js → MarkdownEditor-CcCDF65H.js} +1 -1
- package/dist/{WorkspaceLoadingState-DV8V-zC2.js → WorkspaceLoadingState-CPQBxYFG.js} +1 -1
- package/dist/app-front.js +2 -2
- package/dist/app-server.js +25 -6
- package/dist/server.js +25 -6
- package/dist/testing.js +1 -1
- package/dist/workspace.css +3 -0
- package/dist/workspace.d.ts +3 -1
- package/dist/workspace.js +718 -692
- package/docs/plans/ASK_USER_QUESTIONS_PLUGIN_SPEC.md +322 -0
- package/package.json +3 -3
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# Ask User Questions Plugin Spec
|
|
2
|
+
|
|
3
|
+
Last updated: 2026-05-08
|
|
4
|
+
|
|
5
|
+
## Summary
|
|
6
|
+
|
|
7
|
+
Add a blocking `ask_user` capability for agent sessions that opens a Workspace
|
|
8
|
+
page named **Questions**, renders an agent-generated form, lets the user submit
|
|
9
|
+
answers, and returns those answers to the waiting agent tool call.
|
|
10
|
+
|
|
11
|
+
The Workspace implementation owns the page/plugin UI, form primitives, and UI
|
|
12
|
+
bridge command handling. The Agent implementation owns the blocking tool runtime
|
|
13
|
+
and session-level waiter. The browser must submit answers through the Workspace
|
|
14
|
+
UI bridge command path, not through ad-hoc form POST semantics.
|
|
15
|
+
|
|
16
|
+
## Goals
|
|
17
|
+
|
|
18
|
+
- Provide an agent tool that asks the user for structured input and blocks until
|
|
19
|
+
the user answers or cancels.
|
|
20
|
+
- Open or focus a Workspace page/panel named `Questions` whenever a question is
|
|
21
|
+
pending.
|
|
22
|
+
- Render forms from a portable, JSON-serializable schema generated by the agent.
|
|
23
|
+
- Provide primitive React UI components and hooks so app shells can render these
|
|
24
|
+
forms in a standard way.
|
|
25
|
+
- Persist the single pending question per agent session across browser reloads.
|
|
26
|
+
- Send submitted answers back to the waiting agent through `UiBridge.postCommand`
|
|
27
|
+
as the single user-dispatch source.
|
|
28
|
+
- Keep Workspace base front/shared code package-neutral; no value imports from
|
|
29
|
+
`@boring/agent` outside allowed app composition boundaries.
|
|
30
|
+
|
|
31
|
+
## Non-goals
|
|
32
|
+
|
|
33
|
+
- Multiple simultaneous pending questions per agent session.
|
|
34
|
+
- File upload fields in the first version.
|
|
35
|
+
- Full process-crash restoration of an already-blocked in-memory tool call,
|
|
36
|
+
unless the current agent session runtime already supports rehydrating waiters.
|
|
37
|
+
- Using chat-stream `data-ui-command` parts as the source of truth for answers.
|
|
38
|
+
- Making `pi-ask-user` the Workspace UX implementation dependency.
|
|
39
|
+
|
|
40
|
+
## User flow
|
|
41
|
+
|
|
42
|
+
1. Agent calls `ask_user` with title, optional context, and form schema.
|
|
43
|
+
2. Server creates a pending question record for the current agent session.
|
|
44
|
+
3. Agent tool blocks on an `AskUserCoordinator` waiter.
|
|
45
|
+
4. Workspace receives/loads the pending question and opens or focuses the
|
|
46
|
+
`Questions` page.
|
|
47
|
+
5. User fills the form and clicks Submit.
|
|
48
|
+
6. Questions page calls `uiBridge.postCommand({ type: "questions.submit", ... })`.
|
|
49
|
+
7. Workspace bridge server handler validates the command and records the answer.
|
|
50
|
+
8. `AskUserCoordinator` resolves the waiter.
|
|
51
|
+
9. Tool returns the structured answer to the agent and the agent continues.
|
|
52
|
+
|
|
53
|
+
Cancel follows the same path with `type: "questions.cancel"` and returns a
|
|
54
|
+
stable cancelled result to the tool caller.
|
|
55
|
+
|
|
56
|
+
## Package placement
|
|
57
|
+
|
|
58
|
+
### `@boring/workspace/shared`
|
|
59
|
+
|
|
60
|
+
Define browser-safe contracts only:
|
|
61
|
+
|
|
62
|
+
- `AskUserQuestion`
|
|
63
|
+
- `AskUserFormSchema`
|
|
64
|
+
- `AskUserField`
|
|
65
|
+
- `AskUserAnswer`
|
|
66
|
+
- `QuestionsSubmitCommand`
|
|
67
|
+
- `QuestionsCancelCommand`
|
|
68
|
+
- stable error codes imported from the canonical error-code enum
|
|
69
|
+
|
|
70
|
+
No `node:*`, no `Buffer`, no `@boring/agent` value imports.
|
|
71
|
+
|
|
72
|
+
### `@boring/workspace/plugins/questionsPlugin`
|
|
73
|
+
|
|
74
|
+
New default Workspace plugin that owns:
|
|
75
|
+
|
|
76
|
+
- `front/QuestionsPage.tsx`
|
|
77
|
+
- `front/QuestionsPanel.tsx`
|
|
78
|
+
- `front/usePendingQuestion.ts`
|
|
79
|
+
- `front/primitives/QuestionForm.tsx`
|
|
80
|
+
- `front/primitives/QuestionField.tsx`
|
|
81
|
+
- field primitive components
|
|
82
|
+
- `server/questionsBridge.ts`
|
|
83
|
+
- `shared/types.ts`
|
|
84
|
+
|
|
85
|
+
Plugin outputs should include at minimum:
|
|
86
|
+
|
|
87
|
+
- a panel/page output for `Questions`
|
|
88
|
+
- a command output or bridge binding for `questions.submit`
|
|
89
|
+
- a command output or bridge binding for `questions.cancel`
|
|
90
|
+
- a surface resolver if the workspace opens the page through `openSurface`
|
|
91
|
+
|
|
92
|
+
### `@boring/workspace/app/*`
|
|
93
|
+
|
|
94
|
+
App composition may wire the questions plugin to `@boring/agent/server` tool
|
|
95
|
+
registration APIs. This is the allowed boundary for Workspace + Agent product
|
|
96
|
+
composition.
|
|
97
|
+
|
|
98
|
+
### `@boring/agent/server`
|
|
99
|
+
|
|
100
|
+
Agent package should expose the blocking `ask_user` tool factory and an adapter
|
|
101
|
+
interface that Workspace can satisfy:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
interface AskUserRuntime {
|
|
105
|
+
askUser(request: AskUserRequest): Promise<AskUserToolResult>
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Workspace app/server composition injects a runtime that stores the pending
|
|
110
|
+
question and resolves through UI bridge answers.
|
|
111
|
+
|
|
112
|
+
## Form schema
|
|
113
|
+
|
|
114
|
+
Use a small JSON-schema-lite contract. It is serializable, easy to render in
|
|
115
|
+
React, and avoids forcing Zod or another runtime validator across the shared
|
|
116
|
+
browser/server boundary. A later helper may compile from Zod or JSON Schema into
|
|
117
|
+
this format.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
type AskUserFormSchema = {
|
|
121
|
+
fields: AskUserField[]
|
|
122
|
+
submitLabel?: string
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
type AskUserField =
|
|
126
|
+
| {
|
|
127
|
+
type: "text"
|
|
128
|
+
name: string
|
|
129
|
+
label: string
|
|
130
|
+
required?: boolean
|
|
131
|
+
placeholder?: string
|
|
132
|
+
defaultValue?: string
|
|
133
|
+
helpText?: string
|
|
134
|
+
}
|
|
135
|
+
| {
|
|
136
|
+
type: "textarea"
|
|
137
|
+
name: string
|
|
138
|
+
label: string
|
|
139
|
+
required?: boolean
|
|
140
|
+
placeholder?: string
|
|
141
|
+
defaultValue?: string
|
|
142
|
+
helpText?: string
|
|
143
|
+
}
|
|
144
|
+
| {
|
|
145
|
+
type: "select"
|
|
146
|
+
name: string
|
|
147
|
+
label: string
|
|
148
|
+
required?: boolean
|
|
149
|
+
options: AskUserOption[]
|
|
150
|
+
defaultValue?: string
|
|
151
|
+
helpText?: string
|
|
152
|
+
}
|
|
153
|
+
| {
|
|
154
|
+
type: "multiselect"
|
|
155
|
+
name: string
|
|
156
|
+
label: string
|
|
157
|
+
required?: boolean
|
|
158
|
+
options: AskUserOption[]
|
|
159
|
+
defaultValue?: string[]
|
|
160
|
+
helpText?: string
|
|
161
|
+
}
|
|
162
|
+
| {
|
|
163
|
+
type: "checkbox"
|
|
164
|
+
name: string
|
|
165
|
+
label: string
|
|
166
|
+
defaultValue?: boolean
|
|
167
|
+
helpText?: string
|
|
168
|
+
}
|
|
169
|
+
| {
|
|
170
|
+
type: "radio"
|
|
171
|
+
name: string
|
|
172
|
+
label: string
|
|
173
|
+
required?: boolean
|
|
174
|
+
options: AskUserOption[]
|
|
175
|
+
defaultValue?: string
|
|
176
|
+
helpText?: string
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
type AskUserOption = {
|
|
180
|
+
value: string
|
|
181
|
+
label: string
|
|
182
|
+
description?: string
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Answer shape:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
type AskUserAnswer = {
|
|
190
|
+
questionId: string
|
|
191
|
+
sessionId: string
|
|
192
|
+
values: Record<string, string | string[] | boolean | null>
|
|
193
|
+
submittedAt: string
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Tool result shape:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
type AskUserToolResult =
|
|
201
|
+
| { status: "answered"; answer: AskUserAnswer }
|
|
202
|
+
| { status: "cancelled"; questionId: string; sessionId: string }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Persistence
|
|
206
|
+
|
|
207
|
+
Only one pending question is allowed per agent session. Creating a new pending
|
|
208
|
+
question for a session that already has one should fail with a stable error code
|
|
209
|
+
or replace only if an explicit future `replacePending` option is added.
|
|
210
|
+
|
|
211
|
+
Store interface:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
interface AskUserStore {
|
|
215
|
+
getPending(sessionId: string): Promise<AskUserQuestion | null>
|
|
216
|
+
createPending(question: AskUserQuestion): Promise<void>
|
|
217
|
+
answer(questionId: string, answer: AskUserAnswer): Promise<void>
|
|
218
|
+
cancel(questionId: string): Promise<void>
|
|
219
|
+
clearPending(sessionId: string): Promise<void>
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Initial implementation should provide a file-backed or existing workspace-store
|
|
224
|
+
adapter. Core/cloud can later inject a DB-backed implementation.
|
|
225
|
+
|
|
226
|
+
Persistence requirement for v1: pending question survives browser reload and the
|
|
227
|
+
Questions page can re-render it. Full server-process crash resumption of the
|
|
228
|
+
blocked tool waiter is a later concern unless already provided by the current
|
|
229
|
+
agent session runtime.
|
|
230
|
+
|
|
231
|
+
## UI bridge commands
|
|
232
|
+
|
|
233
|
+
The browser submits through `UiBridge.postCommand`:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
uiBridge.postCommand({
|
|
237
|
+
type: "questions.submit",
|
|
238
|
+
questionId,
|
|
239
|
+
sessionId,
|
|
240
|
+
values,
|
|
241
|
+
})
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Cancel:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
uiBridge.postCommand({
|
|
248
|
+
type: "questions.cancel",
|
|
249
|
+
questionId,
|
|
250
|
+
sessionId,
|
|
251
|
+
})
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Server bridge handlers must:
|
|
255
|
+
|
|
256
|
+
- validate `sessionId` and `questionId`
|
|
257
|
+
- validate submitted values against the form schema
|
|
258
|
+
- persist answer/cancel state
|
|
259
|
+
- resolve the matching `AskUserCoordinator` waiter
|
|
260
|
+
- emit/update Workspace state so the Questions page leaves pending mode
|
|
261
|
+
|
|
262
|
+
No direct browser form route should be treated as the conceptual API. If an HTTP
|
|
263
|
+
route exists underneath, it is only transport for the UI bridge.
|
|
264
|
+
|
|
265
|
+
## Questions page behavior
|
|
266
|
+
|
|
267
|
+
- Register/open a page or panel titled `Questions`.
|
|
268
|
+
- Auto-open or focus when a new pending question appears.
|
|
269
|
+
- Show title, context, form fields, submit button, and cancel affordance.
|
|
270
|
+
- Disable submit while validation fails or command is in flight.
|
|
271
|
+
- After submit, show a short answered state and return focus behavior to the
|
|
272
|
+
workspace shell if appropriate.
|
|
273
|
+
- If no pending question exists, show an empty state: "No questions right now."
|
|
274
|
+
|
|
275
|
+
## Primitive UI package
|
|
276
|
+
|
|
277
|
+
Expose primitives from Workspace front code so apps can customize the page while
|
|
278
|
+
keeping standard schema behavior:
|
|
279
|
+
|
|
280
|
+
- `<QuestionForm schema values onValuesChange onSubmit onCancel />`
|
|
281
|
+
- `<QuestionField field value onChange error />`
|
|
282
|
+
- `<QuestionSubmitButton />`
|
|
283
|
+
- `<QuestionCancelButton />`
|
|
284
|
+
- `useQuestionForm(schema, initialValues?)`
|
|
285
|
+
- `validateQuestionValues(schema, values)`
|
|
286
|
+
|
|
287
|
+
Primitives should be headless-enough to style, but the default Questions page
|
|
288
|
+
should provide a polished complete UI.
|
|
289
|
+
|
|
290
|
+
## `pi-ask-user` evaluation
|
|
291
|
+
|
|
292
|
+
`pi-ask-user` provides a native Pi `ask_user` tool with TUI overlay/inline UI,
|
|
293
|
+
selection lists, freeform input, multi-select, timeout, and a useful bundled
|
|
294
|
+
skill. It is good prior art and may be useful as a standalone CLI fallback.
|
|
295
|
+
|
|
296
|
+
Do not make it the primary Workspace implementation because it does not provide:
|
|
297
|
+
|
|
298
|
+
- Workspace page/panel integration
|
|
299
|
+
- generated rich React form rendering
|
|
300
|
+
- Workspace persistence semantics
|
|
301
|
+
- answer dispatch through `UiBridge.postCommand`
|
|
302
|
+
- app-shell primitive components
|
|
303
|
+
|
|
304
|
+
Recommended stance:
|
|
305
|
+
|
|
306
|
+
- Use `pi-ask-user` as reference for prompt/tool ergonomics.
|
|
307
|
+
- Consider optional CLI/TUI fallback for standalone `@boring/agent` mode.
|
|
308
|
+
- Keep Workspace Questions plugin implementation first-party.
|
|
309
|
+
|
|
310
|
+
## Acceptance criteria for implementation bead
|
|
311
|
+
|
|
312
|
+
- Shared schema/types exist and are browser-safe.
|
|
313
|
+
- Questions plugin registers a `Questions` page/panel.
|
|
314
|
+
- Agent/session can create one pending question and block for the answer.
|
|
315
|
+
- Pending question persists across browser reload.
|
|
316
|
+
- Default page renders text, textarea, select, multiselect, checkbox, and radio.
|
|
317
|
+
- Submit uses `UiBridge.postCommand`, not direct conceptual route dispatch.
|
|
318
|
+
- Submitted values resolve the blocking tool call.
|
|
319
|
+
- Cancel resolves the tool call with `status: "cancelled"`.
|
|
320
|
+
- Primitive form components/hooks are exported for app-shell reuse.
|
|
321
|
+
- Unit tests cover schema validation and one-pending-question enforcement.
|
|
322
|
+
- Integration test or harness test covers ask -> render -> submit -> tool result.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-workspace",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Workspace UI, plugin, and bridge package for composing chat, files, catalogs, editors, and app-specific panes.",
|
|
@@ -125,8 +125,8 @@
|
|
|
125
125
|
"tailwind-merge": "^2.0.0",
|
|
126
126
|
"zod": "^3.23.0",
|
|
127
127
|
"zustand": "^5.0.0",
|
|
128
|
-
"@hachej/boring-agent": "0.1.
|
|
129
|
-
"@hachej/boring-ui-kit": "0.1.
|
|
128
|
+
"@hachej/boring-agent": "0.1.12",
|
|
129
|
+
"@hachej/boring-ui-kit": "0.1.12"
|
|
130
130
|
},
|
|
131
131
|
"devDependencies": {
|
|
132
132
|
"@tailwindcss/postcss": "^4.0.0",
|