@llui/agent 0.0.29
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/LICENSE +21 -0
- package/README.md +210 -0
- package/dist/client/agentConfirm.d.ts +60 -0
- package/dist/client/agentConfirm.d.ts.map +1 -0
- package/dist/client/agentConfirm.js +66 -0
- package/dist/client/agentConfirm.js.map +1 -0
- package/dist/client/agentConnect.d.ts +125 -0
- package/dist/client/agentConnect.d.ts.map +1 -0
- package/dist/client/agentConnect.js +114 -0
- package/dist/client/agentConnect.js.map +1 -0
- package/dist/client/agentLog.d.ts +51 -0
- package/dist/client/agentLog.d.ts.map +1 -0
- package/dist/client/agentLog.js +53 -0
- package/dist/client/agentLog.js.map +1 -0
- package/dist/client/effect-handler.d.ts +15 -0
- package/dist/client/effect-handler.d.ts.map +1 -0
- package/dist/client/effect-handler.js +146 -0
- package/dist/client/effect-handler.js.map +1 -0
- package/dist/client/effects.d.ts +27 -0
- package/dist/client/effects.d.ts.map +1 -0
- package/dist/client/effects.js +2 -0
- package/dist/client/effects.js.map +1 -0
- package/dist/client/factory.d.ts +47 -0
- package/dist/client/factory.d.ts.map +1 -0
- package/dist/client/factory.js +105 -0
- package/dist/client/factory.js.map +1 -0
- package/dist/client/index.d.ts +7 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +5 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/rpc/describe-context.d.ts +10 -0
- package/dist/client/rpc/describe-context.d.ts.map +1 -0
- package/dist/client/rpc/describe-context.js +8 -0
- package/dist/client/rpc/describe-context.js.map +1 -0
- package/dist/client/rpc/describe-visible-content.d.ts +22 -0
- package/dist/client/rpc/describe-visible-content.d.ts.map +1 -0
- package/dist/client/rpc/describe-visible-content.js +66 -0
- package/dist/client/rpc/describe-visible-content.js.map +1 -0
- package/dist/client/rpc/get-state.d.ts +15 -0
- package/dist/client/rpc/get-state.d.ts.map +1 -0
- package/dist/client/rpc/get-state.js +37 -0
- package/dist/client/rpc/get-state.js.map +1 -0
- package/dist/client/rpc/list-actions.d.ts +27 -0
- package/dist/client/rpc/list-actions.d.ts.map +1 -0
- package/dist/client/rpc/list-actions.js +38 -0
- package/dist/client/rpc/list-actions.js.map +1 -0
- package/dist/client/rpc/query-dom.d.ts +20 -0
- package/dist/client/rpc/query-dom.d.ts.map +1 -0
- package/dist/client/rpc/query-dom.js +37 -0
- package/dist/client/rpc/query-dom.js.map +1 -0
- package/dist/client/rpc/send-message.d.ts +28 -0
- package/dist/client/rpc/send-message.d.ts.map +1 -0
- package/dist/client/rpc/send-message.js +40 -0
- package/dist/client/rpc/send-message.js.map +1 -0
- package/dist/client/uuid.d.ts +2 -0
- package/dist/client/uuid.d.ts.map +1 -0
- package/dist/client/uuid.js +24 -0
- package/dist/client/uuid.js.map +1 -0
- package/dist/client/ws-client.d.ts +44 -0
- package/dist/client/ws-client.d.ts.map +1 -0
- package/dist/client/ws-client.js +176 -0
- package/dist/client/ws-client.js.map +1 -0
- package/dist/protocol.d.ts +319 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +6 -0
- package/dist/protocol.js.map +1 -0
- package/dist/server/audit.d.ts +6 -0
- package/dist/server/audit.d.ts.map +1 -0
- package/dist/server/audit.js +6 -0
- package/dist/server/audit.js.map +1 -0
- package/dist/server/factory.d.ts +10 -0
- package/dist/server/factory.d.ts.map +1 -0
- package/dist/server/factory.js +69 -0
- package/dist/server/factory.js.map +1 -0
- package/dist/server/http/mint.d.ts +23 -0
- package/dist/server/http/mint.d.ts.map +1 -0
- package/dist/server/http/mint.js +63 -0
- package/dist/server/http/mint.js.map +1 -0
- package/dist/server/http/resume.d.ts +14 -0
- package/dist/server/http/resume.d.ts.map +1 -0
- package/dist/server/http/resume.js +89 -0
- package/dist/server/http/resume.js.map +1 -0
- package/dist/server/http/revoke.d.ts +11 -0
- package/dist/server/http/revoke.d.ts.map +1 -0
- package/dist/server/http/revoke.js +24 -0
- package/dist/server/http/revoke.js.map +1 -0
- package/dist/server/http/router.d.ts +13 -0
- package/dist/server/http/router.d.ts.map +1 -0
- package/dist/server/http/router.js +28 -0
- package/dist/server/http/router.js.map +1 -0
- package/dist/server/http/sessions.d.ts +8 -0
- package/dist/server/http/sessions.d.ts.map +1 -0
- package/dist/server/http/sessions.js +27 -0
- package/dist/server/http/sessions.js.map +1 -0
- package/dist/server/identity.d.ts +8 -0
- package/dist/server/identity.d.ts.map +1 -0
- package/dist/server/identity.js +41 -0
- package/dist/server/identity.js.map +1 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +6 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/lap/confirm-result.d.ts +14 -0
- package/dist/server/lap/confirm-result.d.ts.map +1 -0
- package/dist/server/lap/confirm-result.js +60 -0
- package/dist/server/lap/confirm-result.js.map +1 -0
- package/dist/server/lap/describe.d.ts +22 -0
- package/dist/server/lap/describe.d.ts.map +1 -0
- package/dist/server/lap/describe.js +67 -0
- package/dist/server/lap/describe.js.map +1 -0
- package/dist/server/lap/forward.d.ts +24 -0
- package/dist/server/lap/forward.d.ts.map +1 -0
- package/dist/server/lap/forward.js +68 -0
- package/dist/server/lap/forward.js.map +1 -0
- package/dist/server/lap/message.d.ts +14 -0
- package/dist/server/lap/message.d.ts.map +1 -0
- package/dist/server/lap/message.js +97 -0
- package/dist/server/lap/message.js.map +1 -0
- package/dist/server/lap/router.d.ts +4 -0
- package/dist/server/lap/router.d.ts.map +1 -0
- package/dist/server/lap/router.js +37 -0
- package/dist/server/lap/router.js.map +1 -0
- package/dist/server/lap/wait.d.ts +14 -0
- package/dist/server/lap/wait.d.ts.map +1 -0
- package/dist/server/lap/wait.js +35 -0
- package/dist/server/lap/wait.js.map +1 -0
- package/dist/server/options.d.ts +41 -0
- package/dist/server/options.d.ts.map +1 -0
- package/dist/server/options.js +2 -0
- package/dist/server/options.js.map +1 -0
- package/dist/server/rate-limit.d.ts +14 -0
- package/dist/server/rate-limit.d.ts.map +1 -0
- package/dist/server/rate-limit.js +43 -0
- package/dist/server/rate-limit.js.map +1 -0
- package/dist/server/token-store.d.ts +27 -0
- package/dist/server/token-store.d.ts.map +1 -0
- package/dist/server/token-store.js +55 -0
- package/dist/server/token-store.js.map +1 -0
- package/dist/server/token.d.ts +24 -0
- package/dist/server/token.d.ts.map +1 -0
- package/dist/server/token.js +77 -0
- package/dist/server/token.js.map +1 -0
- package/dist/server/ws/pairing-registry.d.ts +53 -0
- package/dist/server/ws/pairing-registry.d.ts.map +1 -0
- package/dist/server/ws/pairing-registry.js +205 -0
- package/dist/server/ws/pairing-registry.js.map +1 -0
- package/dist/server/ws/upgrade.d.ts +23 -0
- package/dist/server/ws/upgrade.d.ts.map +1 -0
- package/dist/server/ws/upgrade.js +81 -0
- package/dist/server/ws/upgrade.js.map +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Franco Ponticelli
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# @llui/agent
|
|
2
|
+
|
|
3
|
+
Server and browser-client libraries for the [LLui Agent Protocol (LAP)](../../docs/superpowers/specs/2026-04-19-llui-agent-design.md).
|
|
4
|
+
|
|
5
|
+
## What this buys you
|
|
6
|
+
|
|
7
|
+
Your app's users can install the `llui-agent` bridge into Claude Desktop once, paste a token you mint for them, and drive your LLui app from Claude. Same Msgs and State you're already using — Claude dispatches like a remote user.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @llui/agent @llui/effects ws
|
|
13
|
+
pnpm add -D @llui/vite-plugin # if not already present
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Enable agent-metadata emission in `vite.config.ts`:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import llui from '@llui/vite-plugin'
|
|
20
|
+
export default { plugins: [llui({ agent: true })] }
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Server
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { createLluiAgentServer } from '@llui/agent/server'
|
|
27
|
+
import express from 'express'
|
|
28
|
+
|
|
29
|
+
const agent = createLluiAgentServer({
|
|
30
|
+
signingKey: process.env.LLUI_AGENT_KEY!,
|
|
31
|
+
identityResolver: async (req) => req.cookies.user_id ?? null,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const app = express()
|
|
35
|
+
// The router is Web-standards; adapt it:
|
|
36
|
+
app.use('/agent', async (req, res) => {
|
|
37
|
+
const webReq = expressToWebRequest(req) // adapter
|
|
38
|
+
const webRes = await agent.router(webReq)
|
|
39
|
+
if (!webRes) {
|
|
40
|
+
res.status(404).end()
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
webRes.headers.forEach((v, k) => res.setHeader(k, v))
|
|
44
|
+
res.status(webRes.status).send(await webRes.text())
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const server = app.listen(8787)
|
|
48
|
+
server.on('upgrade', agent.wsUpgrade)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Client
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { mountApp } from '@llui/dom'
|
|
55
|
+
import { createAgentClient, agentConnect, agentConfirm, agentLog } from '@llui/agent/client'
|
|
56
|
+
import { handleEffects } from '@llui/effects'
|
|
57
|
+
import { App } from './App'
|
|
58
|
+
|
|
59
|
+
const root = document.getElementById('app')!
|
|
60
|
+
const handle = mountApp(root, App)
|
|
61
|
+
|
|
62
|
+
const client = createAgentClient({
|
|
63
|
+
handle,
|
|
64
|
+
def: App,
|
|
65
|
+
rootElement: root,
|
|
66
|
+
slices: {
|
|
67
|
+
getConnect: (s) => s.agent.connect,
|
|
68
|
+
getConfirm: (s) => s.agent.confirm,
|
|
69
|
+
wrapConnectMsg: (m) => ({ type: 'agent', sub: 'connect', msg: m }),
|
|
70
|
+
wrapConfirmMsg: (m) => ({ type: 'agent', sub: 'confirm', msg: m }),
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
client.start()
|
|
74
|
+
|
|
75
|
+
// Chain client.effectHandler into your onEffect:
|
|
76
|
+
const onEffect = handleEffects<MyEffect | AgentEffect>()
|
|
77
|
+
.when('http', ...)
|
|
78
|
+
.else(client.effectHandler)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## App-side annotations
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
type Msg =
|
|
85
|
+
/** @intent("Increment the counter") */
|
|
86
|
+
| { type: 'inc' }
|
|
87
|
+
/** @intent("Delete item") @requiresConfirm */
|
|
88
|
+
| { type: 'delete', id: string }
|
|
89
|
+
/** @intent("Place order") @humanOnly */
|
|
90
|
+
| { type: 'checkout' }
|
|
91
|
+
/** @intent("Navigate") @alwaysAffordable */
|
|
92
|
+
| { type: 'nav', to: 'reports' | 'settings' | 'home' }
|
|
93
|
+
|
|
94
|
+
export const App = component<State, Msg, Effect>({
|
|
95
|
+
name: 'App',
|
|
96
|
+
init: ...,
|
|
97
|
+
update: ...,
|
|
98
|
+
view: ...,
|
|
99
|
+
agentAffordances: (state) => [
|
|
100
|
+
{ type: 'nav', to: 'reports' },
|
|
101
|
+
...(state.user ? [{ type: 'signOut' }] : []),
|
|
102
|
+
],
|
|
103
|
+
agentDocs: {
|
|
104
|
+
purpose: 'Kanban for a 3-person design team.',
|
|
105
|
+
overview: 'Columns: To do / Doing / Done. Cards carry owner, due date, tags.',
|
|
106
|
+
cautions: ['Moving to Done locks edits — reopen first.'],
|
|
107
|
+
},
|
|
108
|
+
agentContext: (state) => ({
|
|
109
|
+
summary: `Viewing board "${state.boardName}", ${state.cards.length} cards visible.`,
|
|
110
|
+
hints: state.selectedCard
|
|
111
|
+
? ['Card focused; enter advances status.']
|
|
112
|
+
: ['Tab to list, arrow to select.'],
|
|
113
|
+
}),
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Annotations reference
|
|
118
|
+
|
|
119
|
+
| Tag | Semantics |
|
|
120
|
+
| ------------------- | ------------------------------------------------------------ |
|
|
121
|
+
| `@intent("...")` | Human-readable label for Claude + confirmation UI + log |
|
|
122
|
+
| `@alwaysAffordable` | Surfaces to Claude even when no binding is currently visible |
|
|
123
|
+
| `@requiresConfirm` | Claude must propose; user approves before dispatch |
|
|
124
|
+
| `@humanOnly` | Claude cannot dispatch; not in `list_actions` |
|
|
125
|
+
|
|
126
|
+
## App state shape (host integration)
|
|
127
|
+
|
|
128
|
+
Wire your root state and Msg to include agent sub-slices:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
type State = {
|
|
132
|
+
// ...your app state...
|
|
133
|
+
agent: {
|
|
134
|
+
connect: agentConnect.State
|
|
135
|
+
confirm: agentConfirm.State
|
|
136
|
+
log: agentLog.State
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
type Msg =
|
|
141
|
+
// ...your app msgs...
|
|
142
|
+
| { type: 'agent'; sub: 'connect'; msg: agentConnect.Msg }
|
|
143
|
+
| { type: 'agent'; sub: 'confirm'; msg: agentConfirm.Msg }
|
|
144
|
+
| { type: 'agent'; sub: 'log'; msg: agentLog.Msg }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Delegate in `update`:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
update: (state, msg) => {
|
|
151
|
+
if (msg.type === 'agent') {
|
|
152
|
+
if (msg.sub === 'connect') {
|
|
153
|
+
const [connect, effects] = agentConnect.update(state.agent.connect, msg.msg)
|
|
154
|
+
return [{ ...state, agent: { ...state.agent, connect } }, effects]
|
|
155
|
+
}
|
|
156
|
+
if (msg.sub === 'confirm') {
|
|
157
|
+
const [confirm, effects] = agentConfirm.update(state.agent.confirm, msg.msg)
|
|
158
|
+
return [{ ...state, agent: { ...state.agent, confirm } }, effects]
|
|
159
|
+
}
|
|
160
|
+
if (msg.sub === 'log') {
|
|
161
|
+
const [log, effects] = agentLog.update(state.agent.log, msg.msg)
|
|
162
|
+
return [{ ...state, agent: { ...state.agent, log } }, effects]
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// ...your app logic...
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## View wiring
|
|
170
|
+
|
|
171
|
+
Render `agentConnect`, `agentConfirm`, and `agentLog` anywhere in your view tree:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
view: ({ send, branch, show }) => {
|
|
175
|
+
const connectParts = agentConnect.connect(
|
|
176
|
+
(s) => s.agent.connect,
|
|
177
|
+
(m) => send({ type: 'agent', sub: 'connect', msg: m }),
|
|
178
|
+
{ mintUrl: '/agent/mint' },
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
const confirmParts = agentConfirm.connect(
|
|
182
|
+
(s) => s.agent.confirm,
|
|
183
|
+
(m) => send({ type: 'agent', sub: 'confirm', msg: m }),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return [
|
|
187
|
+
// Renders the "Connect with Claude" button + token copy box + session list:
|
|
188
|
+
div(connectParts.root, [
|
|
189
|
+
button(connectParts.mintTrigger, ['Connect with Claude']),
|
|
190
|
+
...show({
|
|
191
|
+
when: (s) => s.agent.connect.pendingToken !== null,
|
|
192
|
+
render: () => [
|
|
193
|
+
pre(connectParts.pendingTokenBox),
|
|
194
|
+
button(connectParts.copyConnectSnippetButton, ['Copy']),
|
|
195
|
+
],
|
|
196
|
+
}),
|
|
197
|
+
]),
|
|
198
|
+
// Renders pending confirmation cards:
|
|
199
|
+
div(confirmParts.root),
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Entry points
|
|
205
|
+
|
|
206
|
+
- `@llui/agent/protocol` — all LAP types, WS frame types, token types, audit types.
|
|
207
|
+
- `@llui/agent/server` — `createLluiAgentServer`, `InMemoryTokenStore`, `consoleAuditSink`, interfaces.
|
|
208
|
+
- `@llui/agent/client` — `createAgentClient`, `agentConnect`, `agentConfirm`, `agentLog`, `AgentEffect`.
|
|
209
|
+
|
|
210
|
+
See the [Agent Protocol doc](../../docs/designs/10%20Agent%20Protocol.md) for the full wire protocol and security model.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { AgentEffect } from './effects.js';
|
|
2
|
+
export type ConfirmEntry = {
|
|
3
|
+
id: string;
|
|
4
|
+
variant: string;
|
|
5
|
+
payload: unknown;
|
|
6
|
+
intent: string;
|
|
7
|
+
reason: string | null;
|
|
8
|
+
proposedAt: number;
|
|
9
|
+
status: 'pending' | 'approved' | 'rejected';
|
|
10
|
+
};
|
|
11
|
+
export type AgentConfirmState = {
|
|
12
|
+
pending: ConfirmEntry[];
|
|
13
|
+
};
|
|
14
|
+
export type AgentConfirmMsg = {
|
|
15
|
+
type: 'Propose';
|
|
16
|
+
entry: ConfirmEntry;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'Approve';
|
|
19
|
+
id: string;
|
|
20
|
+
} | {
|
|
21
|
+
type: 'Reject';
|
|
22
|
+
id: string;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'ExpireStale';
|
|
25
|
+
now: number;
|
|
26
|
+
maxAgeMs: number;
|
|
27
|
+
};
|
|
28
|
+
export declare function init(): [AgentConfirmState, AgentEffect[]];
|
|
29
|
+
export declare function update(state: AgentConfirmState, msg: AgentConfirmMsg): [AgentConfirmState, AgentEffect[]];
|
|
30
|
+
import { type Send } from '@llui/dom';
|
|
31
|
+
type ConnectBag = {
|
|
32
|
+
root: {
|
|
33
|
+
'data-scope': string;
|
|
34
|
+
};
|
|
35
|
+
entry: (id: string) => {
|
|
36
|
+
card: {
|
|
37
|
+
'data-part': string;
|
|
38
|
+
'data-status': string;
|
|
39
|
+
'data-id': string;
|
|
40
|
+
};
|
|
41
|
+
approveButton: {
|
|
42
|
+
onClick: () => void;
|
|
43
|
+
disabled: boolean;
|
|
44
|
+
};
|
|
45
|
+
rejectButton: {
|
|
46
|
+
onClick: () => void;
|
|
47
|
+
disabled: boolean;
|
|
48
|
+
};
|
|
49
|
+
intentText: string;
|
|
50
|
+
reasonText: string | null;
|
|
51
|
+
payloadText: string;
|
|
52
|
+
} | null;
|
|
53
|
+
empty: {
|
|
54
|
+
'data-part': string;
|
|
55
|
+
'data-visible': boolean;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
export declare function connect<S>(get: (s: S) => AgentConfirmState, send: Send<AgentConfirmMsg>): (state: S) => ConnectBag;
|
|
59
|
+
export {};
|
|
60
|
+
//# sourceMappingURL=agentConfirm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agentConfirm.d.ts","sourceRoot":"","sources":["../../src/client/agentConfirm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE/C,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,CAAA;CAC5C,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAAE,OAAO,EAAE,YAAY,EAAE,CAAA;CAAE,CAAA;AAE3D,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1D,wBAAgB,IAAI,IAAI,CAAC,iBAAiB,EAAE,WAAW,EAAE,CAAC,CAEzD;AAED,wBAAgB,MAAM,CACpB,KAAK,EAAE,iBAAiB,EACxB,GAAG,EAAE,eAAe,GACnB,CAAC,iBAAiB,EAAE,WAAW,EAAE,CAAC,CAgCpC;AAGD,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,WAAW,CAAA;AAErC,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9B,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK;QACrB,IAAI,EAAE;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,aAAa,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAA;QACvE,aAAa,EAAE;YAAE,OAAO,EAAE,MAAM,IAAI,CAAC;YAAC,QAAQ,EAAE,OAAO,CAAA;SAAE,CAAA;QACzD,YAAY,EAAE;YAAE,OAAO,EAAE,MAAM,IAAI,CAAC;YAAC,QAAQ,EAAE,OAAO,CAAA;SAAE,CAAA;QACxD,UAAU,EAAE,MAAM,CAAA;QAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;QACzB,WAAW,EAAE,MAAM,CAAA;KACpB,GAAG,IAAI,CAAA;IACR,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,CAAA;CACxD,CAAA;AAED,wBAAgB,OAAO,CAAC,CAAC,EACvB,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,iBAAiB,EAChC,IAAI,EAAE,IAAI,CAAC,eAAe,CAAC,GAC1B,CAAC,KAAK,EAAE,CAAC,KAAK,UAAU,CA0B1B"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export function init() {
|
|
2
|
+
return [{ pending: [] }, []];
|
|
3
|
+
}
|
|
4
|
+
export function update(state, msg) {
|
|
5
|
+
switch (msg.type) {
|
|
6
|
+
case 'Propose':
|
|
7
|
+
return [{ pending: [...state.pending, msg.entry] }, []];
|
|
8
|
+
case 'Approve': {
|
|
9
|
+
const entry = state.pending.find((e) => e.id === msg.id);
|
|
10
|
+
if (!entry || entry.status !== 'pending')
|
|
11
|
+
return [state, []];
|
|
12
|
+
return [
|
|
13
|
+
{ pending: state.pending.map((e) => (e.id === msg.id ? { ...e, status: 'approved' } : e)) },
|
|
14
|
+
[
|
|
15
|
+
{
|
|
16
|
+
type: 'AgentForwardMsg',
|
|
17
|
+
payload: { type: entry.variant, ...entry.payload },
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
case 'Reject':
|
|
23
|
+
return [
|
|
24
|
+
{ pending: state.pending.map((e) => (e.id === msg.id ? { ...e, status: 'rejected' } : e)) },
|
|
25
|
+
[],
|
|
26
|
+
];
|
|
27
|
+
case 'ExpireStale':
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
pending: state.pending.filter((e) => msg.now - e.proposedAt <= msg.maxAgeMs || e.status !== 'pending'),
|
|
31
|
+
},
|
|
32
|
+
[],
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Connect bag:
|
|
37
|
+
import {} from '@llui/dom';
|
|
38
|
+
export function connect(get, send) {
|
|
39
|
+
return (state) => {
|
|
40
|
+
const s = get(state);
|
|
41
|
+
return {
|
|
42
|
+
root: { 'data-scope': 'agent-confirm' },
|
|
43
|
+
entry: (id) => {
|
|
44
|
+
const e = s.pending.find((x) => x.id === id);
|
|
45
|
+
if (!e)
|
|
46
|
+
return null;
|
|
47
|
+
return {
|
|
48
|
+
card: { 'data-part': 'entry', 'data-status': e.status, 'data-id': e.id },
|
|
49
|
+
approveButton: {
|
|
50
|
+
onClick: () => send({ type: 'Approve', id }),
|
|
51
|
+
disabled: e.status !== 'pending',
|
|
52
|
+
},
|
|
53
|
+
rejectButton: {
|
|
54
|
+
onClick: () => send({ type: 'Reject', id }),
|
|
55
|
+
disabled: e.status !== 'pending',
|
|
56
|
+
},
|
|
57
|
+
intentText: e.intent,
|
|
58
|
+
reasonText: e.reason,
|
|
59
|
+
payloadText: JSON.stringify(e.payload, null, 2),
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
empty: { 'data-part': 'empty', 'data-visible': s.pending.length === 0 },
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=agentConfirm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agentConfirm.js","sourceRoot":"","sources":["../../src/client/agentConfirm.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentEffect } from './effects.js'\n\nexport type ConfirmEntry = {\n id: string\n variant: string\n payload: unknown\n intent: string\n reason: string | null\n proposedAt: number\n status: 'pending' | 'approved' | 'rejected'\n}\n\nexport type AgentConfirmState = { pending: ConfirmEntry[] }\n\nexport type AgentConfirmMsg =\n | { type: 'Propose'; entry: ConfirmEntry }\n | { type: 'Approve'; id: string }\n | { type: 'Reject'; id: string }\n | { type: 'ExpireStale'; now: number; maxAgeMs: number }\n\nexport function init(): [AgentConfirmState, AgentEffect[]] {\n return [{ pending: [] }, []]\n}\n\nexport function update(\n state: AgentConfirmState,\n msg: AgentConfirmMsg,\n): [AgentConfirmState, AgentEffect[]] {\n switch (msg.type) {\n case 'Propose':\n return [{ pending: [...state.pending, msg.entry] }, []]\n case 'Approve': {\n const entry = state.pending.find((e) => e.id === msg.id)\n if (!entry || entry.status !== 'pending') return [state, []]\n return [\n { pending: state.pending.map((e) => (e.id === msg.id ? { ...e, status: 'approved' } : e)) },\n [\n {\n type: 'AgentForwardMsg',\n payload: { type: entry.variant, ...(entry.payload as object) },\n },\n ],\n ]\n }\n case 'Reject':\n return [\n { pending: state.pending.map((e) => (e.id === msg.id ? { ...e, status: 'rejected' } : e)) },\n [],\n ]\n case 'ExpireStale':\n return [\n {\n pending: state.pending.filter(\n (e) => msg.now - e.proposedAt <= msg.maxAgeMs || e.status !== 'pending',\n ),\n },\n [],\n ]\n }\n}\n\n// Connect bag:\nimport { type Send } from '@llui/dom'\n\ntype ConnectBag = {\n root: { 'data-scope': string }\n entry: (id: string) => {\n card: { 'data-part': string; 'data-status': string; 'data-id': string }\n approveButton: { onClick: () => void; disabled: boolean }\n rejectButton: { onClick: () => void; disabled: boolean }\n intentText: string\n reasonText: string | null\n payloadText: string\n } | null\n empty: { 'data-part': string; 'data-visible': boolean }\n}\n\nexport function connect<S>(\n get: (s: S) => AgentConfirmState,\n send: Send<AgentConfirmMsg>,\n): (state: S) => ConnectBag {\n return (state) => {\n const s = get(state)\n return {\n root: { 'data-scope': 'agent-confirm' },\n entry: (id) => {\n const e = s.pending.find((x) => x.id === id)\n if (!e) return null\n return {\n card: { 'data-part': 'entry', 'data-status': e.status, 'data-id': e.id },\n approveButton: {\n onClick: () => send({ type: 'Approve', id }),\n disabled: e.status !== 'pending',\n },\n rejectButton: {\n onClick: () => send({ type: 'Reject', id }),\n disabled: e.status !== 'pending',\n },\n intentText: e.intent,\n reasonText: e.reason,\n payloadText: JSON.stringify(e.payload, null, 2),\n }\n },\n empty: { 'data-part': 'empty', 'data-visible': s.pending.length === 0 },\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Send } from '@llui/dom';
|
|
2
|
+
import type { AgentSession, AgentToken } from '../protocol.js';
|
|
3
|
+
import type { AgentEffect } from './effects.js';
|
|
4
|
+
export type AgentConnectStatus = 'idle' | 'minting' | 'pending-claude' | 'active' | 'error';
|
|
5
|
+
export type AgentConnectPendingToken = {
|
|
6
|
+
token: AgentToken;
|
|
7
|
+
tid: string;
|
|
8
|
+
lapUrl: string;
|
|
9
|
+
connectSnippet: string;
|
|
10
|
+
expiresAt: number;
|
|
11
|
+
};
|
|
12
|
+
export type AgentConnectState = {
|
|
13
|
+
status: AgentConnectStatus;
|
|
14
|
+
pendingToken: AgentConnectPendingToken | null;
|
|
15
|
+
sessions: AgentSession[];
|
|
16
|
+
resumable: AgentSession[];
|
|
17
|
+
error: {
|
|
18
|
+
code: string;
|
|
19
|
+
detail: string;
|
|
20
|
+
} | null;
|
|
21
|
+
};
|
|
22
|
+
export type AgentConnectMsg = {
|
|
23
|
+
type: 'Mint';
|
|
24
|
+
} | {
|
|
25
|
+
type: 'MintSucceeded';
|
|
26
|
+
token: AgentToken;
|
|
27
|
+
tid: string;
|
|
28
|
+
lapUrl: string;
|
|
29
|
+
wsUrl: string;
|
|
30
|
+
expiresAt: number;
|
|
31
|
+
} | {
|
|
32
|
+
type: 'MintFailed';
|
|
33
|
+
error: {
|
|
34
|
+
code: string;
|
|
35
|
+
detail: string;
|
|
36
|
+
};
|
|
37
|
+
} | {
|
|
38
|
+
type: 'WsOpened';
|
|
39
|
+
} | {
|
|
40
|
+
type: 'WsClosed';
|
|
41
|
+
} | {
|
|
42
|
+
type: 'ActivatedByClaude';
|
|
43
|
+
} | {
|
|
44
|
+
type: 'ResumeList';
|
|
45
|
+
tids: string[];
|
|
46
|
+
} | {
|
|
47
|
+
type: 'ResumeListLoaded';
|
|
48
|
+
sessions: AgentSession[];
|
|
49
|
+
} | {
|
|
50
|
+
type: 'Resume';
|
|
51
|
+
tid: string;
|
|
52
|
+
} | {
|
|
53
|
+
type: 'Revoke';
|
|
54
|
+
tid: string;
|
|
55
|
+
} | {
|
|
56
|
+
type: 'ClearError';
|
|
57
|
+
} | {
|
|
58
|
+
type: 'SessionsLoaded';
|
|
59
|
+
sessions: AgentSession[];
|
|
60
|
+
} | {
|
|
61
|
+
type: 'RefreshSessions';
|
|
62
|
+
};
|
|
63
|
+
export type AgentConnectInitOpts = {
|
|
64
|
+
mintUrl: string;
|
|
65
|
+
};
|
|
66
|
+
/** Component shape is [State, Effect[]] — consistent with @llui/components. */
|
|
67
|
+
export declare function init(_opts: AgentConnectInitOpts): [AgentConnectState, AgentEffect[]];
|
|
68
|
+
export declare function update(state: AgentConnectState, msg: AgentConnectMsg, opts: AgentConnectInitOpts): [AgentConnectState, AgentEffect[]];
|
|
69
|
+
export type AgentConnectConnectOptions = {
|
|
70
|
+
id?: string;
|
|
71
|
+
};
|
|
72
|
+
type ConnectBag = {
|
|
73
|
+
root: {
|
|
74
|
+
'data-scope': string;
|
|
75
|
+
'data-state': string;
|
|
76
|
+
};
|
|
77
|
+
mintTrigger: {
|
|
78
|
+
onClick: () => void;
|
|
79
|
+
disabled: boolean;
|
|
80
|
+
};
|
|
81
|
+
pendingTokenBox: {
|
|
82
|
+
'data-part': string;
|
|
83
|
+
'data-visible': boolean;
|
|
84
|
+
};
|
|
85
|
+
copyConnectSnippetButton: {
|
|
86
|
+
onClick: () => void;
|
|
87
|
+
disabled: boolean;
|
|
88
|
+
};
|
|
89
|
+
sessionsList: {
|
|
90
|
+
'data-part': string;
|
|
91
|
+
};
|
|
92
|
+
sessionItem: (tid: string) => {
|
|
93
|
+
'data-part': string;
|
|
94
|
+
'data-tid': string;
|
|
95
|
+
};
|
|
96
|
+
revokeButton: (tid: string) => {
|
|
97
|
+
onClick: () => void;
|
|
98
|
+
};
|
|
99
|
+
resumeBanner: {
|
|
100
|
+
'data-part': string;
|
|
101
|
+
'data-visible': boolean;
|
|
102
|
+
};
|
|
103
|
+
resumeItem: (tid: string) => {
|
|
104
|
+
'data-part': string;
|
|
105
|
+
'data-tid': string;
|
|
106
|
+
};
|
|
107
|
+
resumeButton: (tid: string) => {
|
|
108
|
+
onClick: () => void;
|
|
109
|
+
};
|
|
110
|
+
dismissButton: (tid: string) => {
|
|
111
|
+
onClick: () => void;
|
|
112
|
+
};
|
|
113
|
+
error: {
|
|
114
|
+
'data-part': string;
|
|
115
|
+
'data-visible': boolean;
|
|
116
|
+
onClick: () => void;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Builds prop bags for the view. See spec §9.1 and the @llui/components
|
|
121
|
+
* dialog.ts pattern.
|
|
122
|
+
*/
|
|
123
|
+
export declare function connect<S>(get: (s: S) => AgentConnectState, send: Send<AgentConnectMsg>, _opts?: AgentConnectConnectOptions): (state: S) => ConnectBag;
|
|
124
|
+
export {};
|
|
125
|
+
//# sourceMappingURL=agentConnect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agentConnect.d.ts","sourceRoot":"","sources":["../../src/client/agentConnect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACrC,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE/C,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,SAAS,GAAG,gBAAgB,GAAG,QAAQ,GAAG,OAAO,CAAA;AAE3F,MAAM,MAAM,wBAAwB,GAAG;IACrC,KAAK,EAAE,UAAU,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,kBAAkB,CAAA;IAC1B,YAAY,EAAE,wBAAwB,GAAG,IAAI,CAAA;IAC7C,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,SAAS,EAAE,YAAY,EAAE,CAAA;IACzB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAC/C,CAAA;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IACE,IAAI,EAAE,eAAe,CAAA;IACrB,KAAK,EAAE,UAAU,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;CAClB,GACD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAC/D;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,mBAAmB,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,iBAAiB,CAAA;CAAE,CAAA;AAE/B,MAAM,MAAM,oBAAoB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtD,+EAA+E;AAC/E,wBAAgB,IAAI,CAAC,KAAK,EAAE,oBAAoB,GAAG,CAAC,iBAAiB,EAAE,WAAW,EAAE,CAAC,CAWpF;AAED,wBAAgB,MAAM,CACpB,KAAK,EAAE,iBAAiB,EACxB,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,oBAAoB,GACzB,CAAC,iBAAiB,EAAE,WAAW,EAAE,CAAC,CAqDpC;AAID,MAAM,MAAM,0BAA0B,GAAG;IACvC,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAA;IACpD,WAAW,EAAE;QAAE,OAAO,EAAE,MAAM,IAAI,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAA;IACvD,eAAe,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,CAAA;IACjE,wBAAwB,EAAE;QAAE,OAAO,EAAE,MAAM,IAAI,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAA;IACpE,YAAY,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAA;IACrC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;IACzE,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK;QAAE,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,CAAA;IACtD,YAAY,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,CAAA;IAC9D,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;IACxE,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK;QAAE,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,CAAA;IACtD,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK;QAAE,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,CAAA;IACvD,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,CAAA;CAC7E,CAAA;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,CAAC,EACvB,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,iBAAiB,EAChC,IAAI,EAAE,IAAI,CAAC,eAAe,CAAC,EAC3B,KAAK,GAAE,0BAA+B,GACrC,CAAC,KAAK,EAAE,CAAC,KAAK,UAAU,CAyC1B"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/** Component shape is [State, Effect[]] — consistent with @llui/components. */
|
|
2
|
+
export function init(_opts) {
|
|
3
|
+
return [
|
|
4
|
+
{
|
|
5
|
+
status: 'idle',
|
|
6
|
+
pendingToken: null,
|
|
7
|
+
sessions: [],
|
|
8
|
+
resumable: [],
|
|
9
|
+
error: null,
|
|
10
|
+
},
|
|
11
|
+
[],
|
|
12
|
+
];
|
|
13
|
+
}
|
|
14
|
+
export function update(state, msg, opts) {
|
|
15
|
+
switch (msg.type) {
|
|
16
|
+
case 'Mint':
|
|
17
|
+
return [
|
|
18
|
+
{ ...state, status: 'minting' },
|
|
19
|
+
[{ type: 'AgentMintRequest', mintUrl: opts.mintUrl }],
|
|
20
|
+
];
|
|
21
|
+
case 'MintSucceeded': {
|
|
22
|
+
const pending = {
|
|
23
|
+
token: msg.token,
|
|
24
|
+
tid: msg.tid,
|
|
25
|
+
lapUrl: msg.lapUrl,
|
|
26
|
+
connectSnippet: `/llui-connect ${msg.lapUrl} ${msg.token}`,
|
|
27
|
+
expiresAt: msg.expiresAt,
|
|
28
|
+
};
|
|
29
|
+
return [
|
|
30
|
+
{ ...state, status: 'pending-claude', pendingToken: pending, error: null },
|
|
31
|
+
[{ type: 'AgentOpenWS', token: msg.token, wsUrl: msg.wsUrl }],
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
case 'MintFailed':
|
|
35
|
+
return [{ ...state, status: 'error', error: msg.error }, []];
|
|
36
|
+
case 'WsOpened':
|
|
37
|
+
// WS is open but Claude hasn't bound yet; stay at pending-claude.
|
|
38
|
+
return [state, []];
|
|
39
|
+
case 'WsClosed':
|
|
40
|
+
return [{ ...state, status: 'idle', pendingToken: null }, []];
|
|
41
|
+
case 'ActivatedByClaude':
|
|
42
|
+
return [{ ...state, status: 'active' }, []];
|
|
43
|
+
case 'ResumeList':
|
|
44
|
+
return [state, [{ type: 'AgentResumeCheck', tids: msg.tids }]];
|
|
45
|
+
case 'ResumeListLoaded':
|
|
46
|
+
return [{ ...state, resumable: msg.sessions }, []];
|
|
47
|
+
case 'Resume':
|
|
48
|
+
return [state, [{ type: 'AgentResumeClaim', tid: msg.tid }]];
|
|
49
|
+
case 'Revoke': {
|
|
50
|
+
// Optimistically remove from sessions + resumable.
|
|
51
|
+
return [
|
|
52
|
+
{
|
|
53
|
+
...state,
|
|
54
|
+
sessions: state.sessions.filter((s) => s.tid !== msg.tid),
|
|
55
|
+
resumable: state.resumable.filter((s) => s.tid !== msg.tid),
|
|
56
|
+
},
|
|
57
|
+
[{ type: 'AgentRevoke', tid: msg.tid }],
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
case 'ClearError':
|
|
61
|
+
return [{ ...state, error: null }, []];
|
|
62
|
+
case 'SessionsLoaded':
|
|
63
|
+
return [{ ...state, sessions: msg.sessions }, []];
|
|
64
|
+
case 'RefreshSessions':
|
|
65
|
+
return [state, [{ type: 'AgentSessionsList' }]];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Builds prop bags for the view. See spec §9.1 and the @llui/components
|
|
70
|
+
* dialog.ts pattern.
|
|
71
|
+
*/
|
|
72
|
+
export function connect(get, send, _opts = {}) {
|
|
73
|
+
return (state) => {
|
|
74
|
+
const s = get(state);
|
|
75
|
+
return {
|
|
76
|
+
root: { 'data-scope': 'agent-connect', 'data-state': s.status },
|
|
77
|
+
mintTrigger: {
|
|
78
|
+
onClick: () => send({ type: 'Mint' }),
|
|
79
|
+
disabled: s.status === 'minting' || s.status === 'pending-claude' || s.status === 'active',
|
|
80
|
+
},
|
|
81
|
+
pendingTokenBox: { 'data-part': 'pending-token', 'data-visible': s.pendingToken !== null },
|
|
82
|
+
copyConnectSnippetButton: {
|
|
83
|
+
onClick: () => {
|
|
84
|
+
if (s.pendingToken && typeof navigator !== 'undefined' && 'clipboard' in navigator) {
|
|
85
|
+
void navigator.clipboard.writeText(s.pendingToken.connectSnippet);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
disabled: s.pendingToken === null,
|
|
89
|
+
},
|
|
90
|
+
sessionsList: { 'data-part': 'sessions-list' },
|
|
91
|
+
sessionItem: (tid) => ({ 'data-part': 'session-item', 'data-tid': tid }),
|
|
92
|
+
revokeButton: (tid) => ({ onClick: () => send({ type: 'Revoke', tid }) }),
|
|
93
|
+
resumeBanner: { 'data-part': 'resume-banner', 'data-visible': s.resumable.length > 0 },
|
|
94
|
+
resumeItem: (tid) => ({ 'data-part': 'resume-item', 'data-tid': tid }),
|
|
95
|
+
resumeButton: (tid) => ({ onClick: () => send({ type: 'Resume', tid }) }),
|
|
96
|
+
dismissButton: (tid) => ({
|
|
97
|
+
// For dismiss, we currently just remove the resumable record locally.
|
|
98
|
+
// A "dismiss forever" flag could land in a follow-up; for v1, dismiss
|
|
99
|
+
// is a client-side-only state prune by reusing the Revoke Msg path
|
|
100
|
+
// with intent-split; for now Emit Revoke which both revokes server-side
|
|
101
|
+
// AND removes locally. Alternative: emit a new DismissResume msg —
|
|
102
|
+
// spec §9.1 lists dismissButton but doesn't spell out the emitted msg.
|
|
103
|
+
// V1 pragmatic choice: same as revoke (mark revoked on server).
|
|
104
|
+
onClick: () => send({ type: 'Revoke', tid }),
|
|
105
|
+
}),
|
|
106
|
+
error: {
|
|
107
|
+
'data-part': 'error',
|
|
108
|
+
'data-visible': s.error !== null,
|
|
109
|
+
onClick: () => send({ type: 'ClearError' }),
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=agentConnect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agentConnect.js","sourceRoot":"","sources":["../../src/client/agentConnect.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Send } from '@llui/dom'\nimport type { AgentSession, AgentToken } from '../protocol.js'\nimport type { AgentEffect } from './effects.js'\n\nexport type AgentConnectStatus = 'idle' | 'minting' | 'pending-claude' | 'active' | 'error'\n\nexport type AgentConnectPendingToken = {\n token: AgentToken\n tid: string\n lapUrl: string\n connectSnippet: string // \"/llui-connect <lapUrl> <token>\"\n expiresAt: number\n}\n\nexport type AgentConnectState = {\n status: AgentConnectStatus\n pendingToken: AgentConnectPendingToken | null\n sessions: AgentSession[]\n resumable: AgentSession[]\n error: { code: string; detail: string } | null\n}\n\nexport type AgentConnectMsg =\n | { type: 'Mint' }\n | {\n type: 'MintSucceeded'\n token: AgentToken\n tid: string\n lapUrl: string\n wsUrl: string\n expiresAt: number\n }\n | { type: 'MintFailed'; error: { code: string; detail: string } }\n | { type: 'WsOpened' }\n | { type: 'WsClosed' }\n | { type: 'ActivatedByClaude' }\n | { type: 'ResumeList'; tids: string[] }\n | { type: 'ResumeListLoaded'; sessions: AgentSession[] }\n | { type: 'Resume'; tid: string }\n | { type: 'Revoke'; tid: string }\n | { type: 'ClearError' }\n | { type: 'SessionsLoaded'; sessions: AgentSession[] }\n | { type: 'RefreshSessions' }\n\nexport type AgentConnectInitOpts = { mintUrl: string }\n\n/** Component shape is [State, Effect[]] — consistent with @llui/components. */\nexport function init(_opts: AgentConnectInitOpts): [AgentConnectState, AgentEffect[]] {\n return [\n {\n status: 'idle',\n pendingToken: null,\n sessions: [],\n resumable: [],\n error: null,\n },\n [],\n ]\n}\n\nexport function update(\n state: AgentConnectState,\n msg: AgentConnectMsg,\n opts: AgentConnectInitOpts,\n): [AgentConnectState, AgentEffect[]] {\n switch (msg.type) {\n case 'Mint':\n return [\n { ...state, status: 'minting' },\n [{ type: 'AgentMintRequest', mintUrl: opts.mintUrl }],\n ]\n case 'MintSucceeded': {\n const pending: AgentConnectPendingToken = {\n token: msg.token,\n tid: msg.tid,\n lapUrl: msg.lapUrl,\n connectSnippet: `/llui-connect ${msg.lapUrl} ${msg.token}`,\n expiresAt: msg.expiresAt,\n }\n return [\n { ...state, status: 'pending-claude', pendingToken: pending, error: null },\n [{ type: 'AgentOpenWS', token: msg.token, wsUrl: msg.wsUrl }],\n ]\n }\n case 'MintFailed':\n return [{ ...state, status: 'error', error: msg.error }, []]\n case 'WsOpened':\n // WS is open but Claude hasn't bound yet; stay at pending-claude.\n return [state, []]\n case 'WsClosed':\n return [{ ...state, status: 'idle', pendingToken: null }, []]\n case 'ActivatedByClaude':\n return [{ ...state, status: 'active' }, []]\n case 'ResumeList':\n return [state, [{ type: 'AgentResumeCheck', tids: msg.tids }]]\n case 'ResumeListLoaded':\n return [{ ...state, resumable: msg.sessions }, []]\n case 'Resume':\n return [state, [{ type: 'AgentResumeClaim', tid: msg.tid }]]\n case 'Revoke': {\n // Optimistically remove from sessions + resumable.\n return [\n {\n ...state,\n sessions: state.sessions.filter((s) => s.tid !== msg.tid),\n resumable: state.resumable.filter((s) => s.tid !== msg.tid),\n },\n [{ type: 'AgentRevoke', tid: msg.tid }],\n ]\n }\n case 'ClearError':\n return [{ ...state, error: null }, []]\n case 'SessionsLoaded':\n return [{ ...state, sessions: msg.sessions }, []]\n case 'RefreshSessions':\n return [state, [{ type: 'AgentSessionsList' }]]\n }\n}\n\n// ── Connect helper ────────────────────────────────────────────────────────────\n\nexport type AgentConnectConnectOptions = {\n id?: string // optional DOM id prefix\n}\n\ntype ConnectBag = {\n root: { 'data-scope': string; 'data-state': string }\n mintTrigger: { onClick: () => void; disabled: boolean }\n pendingTokenBox: { 'data-part': string; 'data-visible': boolean }\n copyConnectSnippetButton: { onClick: () => void; disabled: boolean }\n sessionsList: { 'data-part': string }\n sessionItem: (tid: string) => { 'data-part': string; 'data-tid': string }\n revokeButton: (tid: string) => { onClick: () => void }\n resumeBanner: { 'data-part': string; 'data-visible': boolean }\n resumeItem: (tid: string) => { 'data-part': string; 'data-tid': string }\n resumeButton: (tid: string) => { onClick: () => void }\n dismissButton: (tid: string) => { onClick: () => void }\n error: { 'data-part': string; 'data-visible': boolean; onClick: () => void }\n}\n\n/**\n * Builds prop bags for the view. See spec §9.1 and the @llui/components\n * dialog.ts pattern.\n */\nexport function connect<S>(\n get: (s: S) => AgentConnectState,\n send: Send<AgentConnectMsg>,\n _opts: AgentConnectConnectOptions = {},\n): (state: S) => ConnectBag {\n return (state) => {\n const s = get(state)\n return {\n root: { 'data-scope': 'agent-connect', 'data-state': s.status },\n mintTrigger: {\n onClick: () => send({ type: 'Mint' }),\n disabled: s.status === 'minting' || s.status === 'pending-claude' || s.status === 'active',\n },\n pendingTokenBox: { 'data-part': 'pending-token', 'data-visible': s.pendingToken !== null },\n copyConnectSnippetButton: {\n onClick: () => {\n if (s.pendingToken && typeof navigator !== 'undefined' && 'clipboard' in navigator) {\n void navigator.clipboard.writeText(s.pendingToken.connectSnippet)\n }\n },\n disabled: s.pendingToken === null,\n },\n sessionsList: { 'data-part': 'sessions-list' },\n sessionItem: (tid) => ({ 'data-part': 'session-item', 'data-tid': tid }),\n revokeButton: (tid) => ({ onClick: () => send({ type: 'Revoke', tid }) }),\n resumeBanner: { 'data-part': 'resume-banner', 'data-visible': s.resumable.length > 0 },\n resumeItem: (tid) => ({ 'data-part': 'resume-item', 'data-tid': tid }),\n resumeButton: (tid) => ({ onClick: () => send({ type: 'Resume', tid }) }),\n dismissButton: (tid) => ({\n // For dismiss, we currently just remove the resumable record locally.\n // A \"dismiss forever\" flag could land in a follow-up; for v1, dismiss\n // is a client-side-only state prune by reusing the Revoke Msg path\n // with intent-split; for now Emit Revoke which both revokes server-side\n // AND removes locally. Alternative: emit a new DismissResume msg —\n // spec §9.1 lists dismissButton but doesn't spell out the emitted msg.\n // V1 pragmatic choice: same as revoke (mark revoked on server).\n onClick: () => send({ type: 'Revoke', tid }),\n }),\n error: {\n 'data-part': 'error',\n 'data-visible': s.error !== null,\n onClick: () => send({ type: 'ClearError' }),\n },\n }\n }\n}\n"]}
|