acrylblends 1.0.10 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/index.d.ts +60 -2
- package/dist/index.js +1992 -33
- package/dist/index.js.map +1 -1
- package/docs/framework/agents/index.md +1 -1
- package/docs/framework/api/summary.md +1 -1
- package/docs/framework/getting-started/desktop.md +23 -4
- package/docs/framework/guides/create-acrylblends.md +1 -1
- package/docs/framework/guides/production.md +20 -0
- package/docs/framework/workshop/README.md +174 -285
- package/docs/framework/workshop/facilitator.md +81 -90
- package/legacy/README.md +9 -0
- package/legacy/agent/157ce3db1112/src/app.ts +98 -0
- package/legacy/agent/157ce3db1112/src/routes.ts +63 -0
- package/legacy/agent/24c681a00fca/src/app.ts +160 -0
- package/legacy/agent/24c681a00fca/src/routes.ts +83 -0
- package/legacy/agent/2e790da977b2/src/app.ts +98 -0
- package/legacy/agent/2e790da977b2/src/routes.ts +63 -0
- package/legacy/agent/6307045545dc/src/app.ts +168 -0
- package/legacy/agent/6307045545dc/src/routes.ts +83 -0
- package/legacy/agent/6a5fb9c15c53/src/app.ts +158 -0
- package/legacy/agent/6a5fb9c15c53/src/routes.ts +83 -0
- package/legacy/agent/71fb54a5a942/src/app.ts +111 -0
- package/legacy/agent/71fb54a5a942/src/routes.ts +63 -0
- package/legacy/agent/74ece148269c/src/app.ts +168 -0
- package/legacy/agent/74ece148269c/src/routes.ts +93 -0
- package/legacy/agent/855359304707/src/app.ts +168 -0
- package/legacy/agent/855359304707/src/routes.ts +126 -0
- package/legacy/agent/b5ef3c786ae4/src/app.ts +127 -0
- package/legacy/agent/b5ef3c786ae4/src/routes.ts +76 -0
- package/legacy/agent/c2f30958043b/src/app.ts +98 -0
- package/legacy/agent/c2f30958043b/src/routes.ts +53 -0
- package/legacy/blank/5a91c558af22/src/routes.ts +18 -0
- package/legacy/blank/9c650cf52bf3/src/routes.ts +14 -0
- package/legacy/blank/aea589283445/src/routes.ts +16 -0
- package/legacy/blank/b2cbb759c889/src/routes.ts +16 -0
- package/legacy/blank/d6d371c9a65e/src/routes.ts +18 -0
- package/legacy/chat/7076cc821e2c/src/app.ts +72 -0
- package/legacy/chat/7076cc821e2c/src/routes.ts +13 -0
- package/legacy/chat/7dbe6d5170df/src/app.ts +107 -0
- package/legacy/chat/7dbe6d5170df/src/routes.ts +19 -0
- package/legacy/chat/a9a9ab44c58e/src/app.ts +107 -0
- package/legacy/chat/a9a9ab44c58e/src/routes.ts +15 -0
- package/legacy/chat/f20ad9a51b06/src/app.ts +106 -0
- package/legacy/chat/f20ad9a51b06/src/routes.ts +15 -0
- package/legacy/layouts.json +524 -0
- package/legacy/nodejs/1d477f37aaf4/src/app.ts +92 -0
- package/legacy/nodejs/1d477f37aaf4/src/routes.ts +40 -0
- package/legacy/nodejs/b1bd9462496c/src/app.ts +61 -0
- package/legacy/nodejs/b1bd9462496c/src/routes.ts +37 -0
- package/legacy/nodejs/d5bab75076ef/src/app.ts +95 -0
- package/legacy/nodejs/d5bab75076ef/src/routes.ts +40 -0
- package/legacy/notes/4edc36949c15/src/app.ts +107 -0
- package/legacy/notes/4edc36949c15/src/routes.ts +14 -0
- package/legacy/notes/6cab2d09747c/src/app.ts +72 -0
- package/legacy/notes/6cab2d09747c/src/routes.ts +12 -0
- package/legacy/notes/7ca2dfed623a/src/app.ts +106 -0
- package/legacy/notes/7ca2dfed623a/src/routes.ts +14 -0
- package/legacy/todo/02e613b3625c/src/app.ts +71 -0
- package/legacy/todo/02e613b3625c/src/todo.ts +123 -0
- package/legacy/todo/45694a8d9e15/src/app.ts +71 -0
- package/legacy/todo/45694a8d9e15/src/todo.ts +96 -0
- package/legacy/todo/5923d3a6099f/src/app.ts +71 -0
- package/legacy/todo/5923d3a6099f/src/todo.ts +125 -0
- package/legacy/todo/849a18d66dc1/src/app.ts +91 -0
- package/legacy/todo/849a18d66dc1/src/todo.ts +125 -0
- package/legacy/todo/8e64211312f3/src/app.ts +71 -0
- package/legacy/todo/8e64211312f3/src/todo.ts +125 -0
- package/legacy/todo/b79078a87565/src/app.ts +71 -0
- package/legacy/todo/b79078a87565/src/todo.ts +125 -0
- package/lib/{audit-Dw-siYTp.d.ts → audit-Bok_UNCn.d.ts} +41 -6
- package/lib/{client-modules-drj4mjhj.js → client-modules-Bkdr83jO.js} +1 -1
- package/lib/cordis/door.d.ts +44 -7
- package/lib/cordis/door.js +214 -60
- package/lib/cordis/inspect.js +1 -1
- package/lib/cordis/node-server.d.ts +101 -3
- package/lib/cordis/node-server.js +3 -3
- package/lib/cordis/router-http.d.ts +7 -1
- package/lib/cordis/router-http.js +44 -3
- package/lib/cordis/router.d.ts +1 -1
- package/lib/cordis/router.js +1 -1
- package/lib/cordis/state.d.ts +3 -2
- package/lib/cordis/state.js +371 -7
- package/lib/door/host.d.ts +2 -2
- package/lib/door/host.js +1 -1
- package/lib/door/page.d.ts +20 -1
- package/lib/door/page.js +75 -14
- package/lib/door/protocol.d.ts +1 -1
- package/lib/drivers.d.ts +10 -1
- package/lib/{index-DMOtA-NJ.d.ts → index-DGq7H0lb.d.ts} +11 -1
- package/lib/node-server.d.ts +7 -1
- package/lib/node-server.js +2 -2
- package/lib/{protocol-a2aqrp2X.d.ts → protocol-BeIYCgTo.d.ts} +4 -0
- package/lib/{snapshot-text-BfEbEOjA.js → snapshot-text-CXTnjxGx.js} +121 -9
- package/lib/src-CtkIX_Gg.js +427 -0
- package/lib/{src-BTBm4OPD.js → src-iBJ2ysV9.js} +77 -14
- package/lib/{test-websocket-BLeEvybj.js → test-websocket-CQOSvBth.js} +17 -7
- package/lib/{test-websocket-DyeS50Tr.d.ts → test-websocket-GUTAkFQT.d.ts} +3 -1
- package/package.json +2 -1
- package/plugins/acryl-door/src/host/channel.ts +124 -9
- package/plugins/acryl-door/src/host/chat-tools.ts +8 -6
- package/plugins/acryl-door/src/host/door-file.ts +7 -2
- package/plugins/acryl-door/src/host/door.ts +58 -23
- package/plugins/acryl-door/src/host/page-routes.ts +3 -1
- package/plugins/acryl-door/src/host/page-socket.ts +18 -8
- package/plugins/acryl-door/src/host/verbs.ts +3 -0
- package/plugins/acryl-door/src/index.ts +166 -29
- package/plugins/acryl-door/src/page/base.ts +32 -0
- package/plugins/acryl-door/src/page/channel.ts +11 -1
- package/plugins/acryl-door/src/page/driver/accessibility.ts +11 -0
- package/plugins/acryl-door/src/page/driver/actions.ts +4 -0
- package/plugins/acryl-door/src/page/driver/driver.ts +49 -7
- package/plugins/acryl-door/src/page/driver/snapshot.ts +5 -2
- package/plugins/acryl-door/src/page/index.ts +46 -2
- package/plugins/acryl-door/src/page/indicator.ts +14 -6
- package/plugins/acryl-door/src/page/outline.ts +56 -0
- package/plugins/acryl-door/src/page/protocol.ts +4 -0
- package/plugins/acryl-door/src/page/snapshot-text.ts +2 -1
- package/plugins/acryl-node-server/src/adapter/listener.ts +4 -0
- package/plugins/acryl-node-server/src/adapter/raw.ts +18 -0
- package/plugins/acryl-node-server/src/adapter/websocket.ts +15 -9
- package/plugins/acryl-node-server/src/client-modules.ts +7 -1
- package/plugins/acryl-node-server/src/index.ts +304 -16
- package/plugins/acryl-node-server/src/upgrade-hub.ts +2 -2
- package/plugins/acryl-page/plugin.json +12 -0
- package/plugins/acryl-page/src/index.ts +163 -0
- package/plugins/acryl-router/src/service.ts +77 -14
- package/plugins/acryl-router-http/src/index.ts +15 -1
- package/plugins/acryl-router-http/src/serve.ts +31 -0
- package/plugins/acryl-state/plugin.json +4 -3
- package/plugins/acryl-state/src/client-source.ts +244 -0
- package/plugins/acryl-state/src/service.ts +33 -6
- package/plugins/acryl-state/src/socket.ts +96 -0
- package/plugins/acryl-web-server/plugin.json +14 -0
- package/plugins/acryl-web-server/src/index.ts +164 -0
- package/plugins/agent/agent.ts +38 -7
- package/plugins/agent/index.ts +1 -1
- package/plugins/agent/model.ts +10 -2
- package/plugins/agent/tools.ts +9 -3
- package/plugins/agent-panel/client/describe.ts +57 -0
- package/plugins/agent-panel/client/driver-pick.ts +22 -0
- package/plugins/agent-panel/client/frame.ts +51 -0
- package/plugins/agent-panel/client/main.ts +343 -81
- package/plugins/agent-panel/drivers.ts +7 -2
- package/plugins/agent-panel/headless.ts +5 -2
- package/plugins/agent-panel/index.ts +187 -61
- package/plugins/agent-panel/mount.ts +27 -0
- package/plugins/agent-panel/visitors.ts +231 -0
- package/plugins/desktop/frame.ts +231 -0
- package/plugins/desktop/launch.ts +44 -9
- package/plugins/desktop/main.ts +100 -32
- package/plugins/desktop/window.ts +21 -4
- package/plugins/driver-pty/client/terminal.ts +4 -1
- package/plugins/driver-pty/index.ts +38 -11
- package/plugins/driver-pty/spawn.ts +38 -0
- package/plugins/driver-pty/terminal.ts +2 -0
- package/plugins/extensions/index.ts +26 -1
- package/plugins/extensions/resolve.ts +13 -0
- package/plugins/feedback/client.d.ts +22 -0
- package/plugins/feedback/client.js +471 -0
- package/plugins/feedback/index.ts +143 -0
- package/plugins/feedback/plugin.json +11 -0
- package/plugins/feedback/store.ts +307 -0
- package/plugins/home/index.ts +122 -0
- package/plugins/home/plugin.json +11 -0
- package/plugins/instance/index.ts +12 -3
- package/plugins/manifest/app-plugins.ts +86 -0
- package/plugins/manifest/index.ts +12 -2
- package/plugins/manifest/snapshot.ts +275 -0
- package/plugins/openai/index.ts +64 -5
- package/plugins/openai/prompt.ts +23 -0
- package/shared/AGENTS.core.md +36 -16
- package/shared/skills/acryl-add-ui/SKILL.md +19 -0
- package/shared/skills/acryl-app/SKILL.md +13 -0
- package/shared/skills/acryl-build-plugin/SKILL.md +22 -0
- package/shared/skills/acryl-change-plugin/SKILL.md +14 -0
- package/shared/skills/acryl-diagnose-plugin/SKILL.md +13 -0
- package/shared/skills/acryl-fix-plugin/SKILL.md +13 -0
- package/shared/skills/acryl-remove-plugin/SKILL.md +12 -0
- package/shared/skills/acryl-share-plugin/SKILL.md +16 -0
- package/shared/skills/cordis-plugin/SKILL.md +26 -0
- package/templates/agent/AGENTS.md +4 -2
- package/templates/agent/blend.yaml +4 -0
- package/templates/agent/gitignore +1 -0
- package/templates/agent/package.json +1 -0
- package/templates/agent/src/app.ts +150 -33
- package/templates/agent/src/frame.ts +17 -0
- package/templates/agent/src/main.ts +5 -3
- package/templates/agent/src/routes.ts +73 -21
- package/templates/agent/template.json +2 -0
- package/templates/agent/test/app.test.ts +83 -5
- package/templates/blank/AGENTS.md +3 -2
- package/templates/blank/blend.yaml +11 -0
- package/templates/blank/gitignore +1 -0
- package/templates/blank/package.json +1 -0
- package/templates/blank/src/app.ts +100 -29
- package/templates/blank/src/door.ts +7 -0
- package/templates/blank/src/first-page.ts +5 -0
- package/templates/blank/src/frame.ts +17 -0
- package/templates/blank/src/routes.ts +65 -8
- package/templates/blank/template.json +14 -1
- package/templates/blank/test/app.test.ts +103 -16
- package/templates/chat/blend.yaml +2 -1
- package/templates/chat/gitignore +1 -0
- package/templates/chat/package.json +1 -0
- package/templates/chat/src/app.ts +72 -22
- package/templates/chat/src/chat-app.ts +11 -2
- package/templates/chat/src/client/chat.tsx +4 -2
- package/templates/chat/src/frame.ts +17 -0
- package/templates/chat/src/main.ts +1 -6
- package/templates/chat/src/page.tsx +6 -4
- package/templates/chat/src/routes.ts +14 -4
- package/templates/chat/src/runtime.ts +9 -3
- package/templates/chat/template.json +3 -1
- package/templates/chat/test/chat.test.ts +46 -1
- package/templates/chat/test/server.test.ts +17 -3
- package/templates/nodejs/blend.yaml +2 -1
- package/templates/nodejs/gitignore +1 -0
- package/templates/nodejs/package.json +1 -0
- package/templates/nodejs/src/app.ts +76 -21
- package/templates/nodejs/src/frame.ts +17 -0
- package/templates/nodejs/src/main.ts +1 -6
- package/templates/nodejs/src/page.tsx +3 -3
- package/templates/nodejs/src/routes.ts +32 -13
- package/templates/nodejs/template.json +3 -1
- package/templates/nodejs/test/app.test.ts +38 -3
- package/templates/nodejs/test/server.test.ts +17 -3
- package/templates/notes/blend.yaml +2 -1
- package/templates/notes/gitignore +1 -0
- package/templates/notes/package.json +1 -0
- package/templates/notes/src/app.ts +72 -22
- package/templates/notes/src/frame.ts +17 -0
- package/templates/notes/src/main.ts +1 -6
- package/templates/notes/src/notes-app.ts +24 -17
- package/templates/notes/src/routes.ts +11 -4
- package/templates/notes/src/runtime.ts +9 -3
- package/templates/notes/src/views.tsx +26 -17
- package/templates/notes/template.json +3 -1
- package/templates/notes/test/notes.test.ts +39 -1
- package/templates/notes/test/server.test.ts +17 -3
- package/templates/todo/blend.yaml +3 -0
- package/templates/todo/gitignore +1 -0
- package/templates/todo/package.json +1 -0
- package/templates/todo/src/app.ts +81 -27
- package/templates/todo/src/door.ts +7 -0
- package/templates/todo/src/frame.ts +17 -0
- package/templates/todo/src/page.ts +13 -6
- package/templates/todo/src/todo.ts +18 -6
- package/templates/todo/template.json +11 -1
- package/templates/todo/test/todo.test.ts +67 -9
- package/lib/src-LX0E4ec4.js +0 -117
- package/shared/SKILL.md +0 -14
package/lib/cordis/state.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { t as Acryl } from "../app-CQcGWjrk.js";
|
|
2
2
|
import { t as methodNotAllowed } from "../method-not-allowed-TO2_hnfl.js";
|
|
3
3
|
import { r as streamSSE } from "../streaming-BlDaph8U.js";
|
|
4
|
-
import { i as loopbackOnly } from "../loopback-only-BbasoF7R.js";
|
|
4
|
+
import { i as loopbackOnly, r as loopbackAuthority } from "../loopback-only-BbasoF7R.js";
|
|
5
5
|
import "../helpers-BEjcdbgw.js";
|
|
6
|
-
import "../src-
|
|
6
|
+
import "../src-iBJ2ysV9.js";
|
|
7
7
|
import "../middleware-BfG_fp8P.js";
|
|
8
8
|
import { Service } from "@deepseek-ai/cordis";
|
|
9
9
|
//#region ../../plugins/acryl-state/src/cell.ts
|
|
@@ -58,6 +58,345 @@ function createDerived(key, inputs, compute, report = () => void 0) {
|
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
//#endregion
|
|
61
|
+
//#region ../../plugins/acryl-state/src/client-source.ts
|
|
62
|
+
/**
|
|
63
|
+
* The browser module served at `/api/state/client.js`, kept as text so it travels with the plugin wherever the plugin goes (copied into an app as source, or bundled): there is
|
|
64
|
+
* no file to find at run time and no compiler needed to serve it. `String.raw` keeps every backslash as written, so what is below is the module exactly as the browser gets it.
|
|
65
|
+
* The code has no imports and uses no backtick or dollar-brace of its own.
|
|
66
|
+
*
|
|
67
|
+
* import { watch } from '/api/state/client.js'
|
|
68
|
+
* const stop = watch('tasks.count', (value) => { badge.textContent = String(value) })
|
|
69
|
+
*
|
|
70
|
+
* One WebSocket for the whole page carries every key; a WebSocket is not one of the browser's six connections per host, so a page that watches ten keys, in as many windows,
|
|
71
|
+
* leaves every one of them free for requests. Where the WebSocket cannot open (no `ws` package on the server, a proxy that does not upgrade), each key is followed by its
|
|
72
|
+
* event stream instead, with one console warning.
|
|
73
|
+
*/
|
|
74
|
+
const CLIENT_SOURCE = String.raw`// acryl-state: watch(key, onValue, onError?) follows a state key over one WebSocket for the page.
|
|
75
|
+
const HEARTBEAT_SILENCE_MS = 45000
|
|
76
|
+
const RETRY_MIN_MS = 250
|
|
77
|
+
const RETRY_MAX_MS = 10000
|
|
78
|
+
|
|
79
|
+
const here = import.meta.url
|
|
80
|
+
const watchers = new Map()
|
|
81
|
+
const latest = new Map()
|
|
82
|
+
const streams = new Map()
|
|
83
|
+
const complained = new Set()
|
|
84
|
+
let socket
|
|
85
|
+
let timer
|
|
86
|
+
let watchdog
|
|
87
|
+
let attempts = 0
|
|
88
|
+
let opened = false
|
|
89
|
+
let following = 'socket'
|
|
90
|
+
let suspended = false
|
|
91
|
+
let warnedStream = false
|
|
92
|
+
|
|
93
|
+
const socketUrl = () => {
|
|
94
|
+
const url = new URL('ws', here)
|
|
95
|
+
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
96
|
+
return url.href
|
|
97
|
+
}
|
|
98
|
+
const streamUrl = (key) => new URL(encodeURIComponent(key) + '/events?json=1', here).href
|
|
99
|
+
|
|
100
|
+
const deliver = (key, text) => {
|
|
101
|
+
if (latest.get(key) === text) return
|
|
102
|
+
latest.set(key, text)
|
|
103
|
+
let value
|
|
104
|
+
try {
|
|
105
|
+
value = JSON.parse(text)
|
|
106
|
+
} catch {
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
for (const watcher of [...(watchers.get(key) ?? [])]) {
|
|
110
|
+
try {
|
|
111
|
+
watcher.onValue(value)
|
|
112
|
+
} catch (cause) {
|
|
113
|
+
console.error('acryl-state: a watcher of ' + key + ' threw', cause)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const fail = (key, message) => {
|
|
119
|
+
const set = watchers.get(key)
|
|
120
|
+
if (set === undefined) return
|
|
121
|
+
let told = false
|
|
122
|
+
for (const watcher of [...set]) {
|
|
123
|
+
if (watcher.onError === undefined) continue
|
|
124
|
+
told = true
|
|
125
|
+
try {
|
|
126
|
+
watcher.onError(new Error(message))
|
|
127
|
+
} catch (cause) {
|
|
128
|
+
console.error('acryl-state: an error handler of ' + key + ' threw', cause)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!told && !complained.has(key)) {
|
|
132
|
+
complained.add(key)
|
|
133
|
+
console.warn('acryl-state: ' + key + ': ' + message)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const send = (frame) => {
|
|
138
|
+
if (socket !== undefined && socket.readyState === 1) socket.send(JSON.stringify(frame))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const arm = () => {
|
|
142
|
+
clearTimeout(watchdog)
|
|
143
|
+
watchdog = setTimeout(() => lost(socket), HEARTBEAT_SILENCE_MS)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const closeSocket = () => {
|
|
147
|
+
clearTimeout(watchdog)
|
|
148
|
+
const current = socket
|
|
149
|
+
socket = undefined
|
|
150
|
+
if (current === undefined) return
|
|
151
|
+
current.onopen = current.onmessage = current.onerror = current.onclose = null
|
|
152
|
+
try {
|
|
153
|
+
current.close(1000, 'page left')
|
|
154
|
+
} catch {
|
|
155
|
+
// Already closed.
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const closeStreams = () => {
|
|
160
|
+
for (const source of streams.values()) source.close()
|
|
161
|
+
streams.clear()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const openStream = (key) => {
|
|
165
|
+
if (streams.has(key) || typeof EventSource === 'undefined') return
|
|
166
|
+
const source = new EventSource(streamUrl(key))
|
|
167
|
+
streams.set(key, source)
|
|
168
|
+
source.addEventListener('value', (event) => deliver(key, event.data))
|
|
169
|
+
source.onerror = () => {
|
|
170
|
+
// A network error is retried by the browser itself; a refusal (the key does not exist) ends the stream for good.
|
|
171
|
+
if (source.readyState === 2) {
|
|
172
|
+
streams.delete(key)
|
|
173
|
+
fail(key, 'the event stream was refused (no such state?)')
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const fallBack = () => {
|
|
179
|
+
following = 'stream'
|
|
180
|
+
if (!warnedStream) {
|
|
181
|
+
warnedStream = true
|
|
182
|
+
console.warn('acryl-state: the WebSocket did not open, so each key is followed by its own event stream (each holds one of the browser\'s six connections to this host)')
|
|
183
|
+
}
|
|
184
|
+
for (const key of watchers.keys()) openStream(key)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// A socket that closes, errors or goes quiet: before it ever opened this is a server without WebSockets, so the streams take over; after, the server went away and the page tries again.
|
|
188
|
+
const lost = (which) => {
|
|
189
|
+
if (which !== socket) return
|
|
190
|
+
closeSocket()
|
|
191
|
+
if (watchers.size === 0 || suspended) return
|
|
192
|
+
if (!opened) {
|
|
193
|
+
fallBack()
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
const delay = Math.min(RETRY_MIN_MS * 2 ** attempts, RETRY_MAX_MS)
|
|
197
|
+
attempts += 1
|
|
198
|
+
clearTimeout(timer)
|
|
199
|
+
timer = setTimeout(connect, delay)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const connect = () => {
|
|
203
|
+
clearTimeout(timer)
|
|
204
|
+
if (socket !== undefined || watchers.size === 0 || suspended || following !== 'socket') return
|
|
205
|
+
let made
|
|
206
|
+
try {
|
|
207
|
+
made = new WebSocket(socketUrl())
|
|
208
|
+
} catch {
|
|
209
|
+
fallBack()
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
socket = made
|
|
213
|
+
made.onopen = () => {
|
|
214
|
+
opened = true
|
|
215
|
+
attempts = 0
|
|
216
|
+
arm()
|
|
217
|
+
for (const key of watchers.keys()) send({ t: 'sub', key })
|
|
218
|
+
}
|
|
219
|
+
made.onmessage = (event) => {
|
|
220
|
+
if (made !== socket) return
|
|
221
|
+
arm()
|
|
222
|
+
let frame
|
|
223
|
+
try {
|
|
224
|
+
frame = JSON.parse(event.data)
|
|
225
|
+
} catch {
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
if (frame === null || typeof frame !== 'object' || typeof frame.key !== 'string') return
|
|
229
|
+
if (typeof frame.error === 'string') fail(frame.key, frame.error)
|
|
230
|
+
else deliver(frame.key, JSON.stringify(frame.value))
|
|
231
|
+
}
|
|
232
|
+
made.onerror = () => lost(made)
|
|
233
|
+
made.onclose = () => lost(made)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const start = () => {
|
|
237
|
+
if (watchers.size === 0) return
|
|
238
|
+
if (following === 'stream') for (const key of watchers.keys()) openStream(key)
|
|
239
|
+
else connect()
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (typeof addEventListener === 'function') {
|
|
243
|
+
// A page in the back/forward cache keeps its connections open for as long as it is kept; let them go when the page is hidden for good, and open them again when it comes back.
|
|
244
|
+
addEventListener('pagehide', () => {
|
|
245
|
+
suspended = true
|
|
246
|
+
clearTimeout(timer)
|
|
247
|
+
closeSocket()
|
|
248
|
+
closeStreams()
|
|
249
|
+
})
|
|
250
|
+
addEventListener('pageshow', () => {
|
|
251
|
+
if (!suspended) return
|
|
252
|
+
suspended = false
|
|
253
|
+
start()
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Follow the state key: onValue gets the value now and after every change, as the JSON value the server holds (a string stays a string). onError, when given, is told of a key that
|
|
258
|
+
// does not exist; without it the page console gets one warning. Returns the function that stops watching.
|
|
259
|
+
export function watch(key, onValue, onError) {
|
|
260
|
+
if (typeof key !== 'string' || key === '') throw new TypeError('watch(key, onValue): the key is a state key like "tasks.count"')
|
|
261
|
+
if (typeof onValue !== 'function') throw new TypeError('watch(key, onValue): onValue is a function')
|
|
262
|
+
const watcher = { onValue, onError }
|
|
263
|
+
let set = watchers.get(key)
|
|
264
|
+
const first = set === undefined
|
|
265
|
+
if (first) {
|
|
266
|
+
set = new Set()
|
|
267
|
+
watchers.set(key, set)
|
|
268
|
+
}
|
|
269
|
+
set.add(watcher)
|
|
270
|
+
complained.delete(key)
|
|
271
|
+
if (!first && latest.has(key)) {
|
|
272
|
+
const text = latest.get(key)
|
|
273
|
+
queueMicrotask(() => {
|
|
274
|
+
if (!set.has(watcher)) return
|
|
275
|
+
try {
|
|
276
|
+
onValue(JSON.parse(text))
|
|
277
|
+
} catch (cause) {
|
|
278
|
+
console.error('acryl-state: a watcher of ' + key + ' threw', cause)
|
|
279
|
+
}
|
|
280
|
+
})
|
|
281
|
+
}
|
|
282
|
+
if (first && !suspended) {
|
|
283
|
+
if (following === 'stream') openStream(key)
|
|
284
|
+
else if (socket === undefined) connect()
|
|
285
|
+
else send({ t: 'sub', key })
|
|
286
|
+
}
|
|
287
|
+
let stopped = false
|
|
288
|
+
return () => {
|
|
289
|
+
if (stopped) return
|
|
290
|
+
stopped = true
|
|
291
|
+
set.delete(watcher)
|
|
292
|
+
if (set.size > 0) return
|
|
293
|
+
watchers.delete(key)
|
|
294
|
+
latest.delete(key)
|
|
295
|
+
complained.delete(key)
|
|
296
|
+
streams.get(key)?.close()
|
|
297
|
+
streams.delete(key)
|
|
298
|
+
send({ t: 'unsub', key })
|
|
299
|
+
if (watchers.size === 0) {
|
|
300
|
+
clearTimeout(timer)
|
|
301
|
+
closeSocket()
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
`;
|
|
306
|
+
/** A refusal (403) unless the upgrade comes from this machine to this app, and from its own page when it comes from a page at all. */
|
|
307
|
+
function refuseStranger(c) {
|
|
308
|
+
const authority = loopbackAuthority(c);
|
|
309
|
+
if (authority === void 0) return c.json({ error: "request authority rejected" }, 403);
|
|
310
|
+
const origin = c.req.header("origin");
|
|
311
|
+
const site = c.req.header("sec-fetch-site");
|
|
312
|
+
if (origin !== void 0 && origin !== authority.origin || site !== void 0 && site !== "same-origin") return c.json({ error: "only this app's own page may follow its state" }, 403);
|
|
313
|
+
}
|
|
314
|
+
/** The route handler that upgrades and follows keys of `cells` for as long as the socket is open. */
|
|
315
|
+
function stateSocket(cells, options) {
|
|
316
|
+
const heartbeatMs = options.heartbeatMs ?? 15e3;
|
|
317
|
+
return options.upgrade(() => {
|
|
318
|
+
const following = /* @__PURE__ */ new Map();
|
|
319
|
+
let beat;
|
|
320
|
+
return {
|
|
321
|
+
onOpen(_event, ws) {
|
|
322
|
+
beat = setInterval(() => {
|
|
323
|
+
try {
|
|
324
|
+
ws.send(JSON.stringify({ t: "ping" }));
|
|
325
|
+
} catch {}
|
|
326
|
+
}, heartbeatMs);
|
|
327
|
+
beat.unref();
|
|
328
|
+
},
|
|
329
|
+
onMessage(event, ws) {
|
|
330
|
+
if (typeof event.data !== "string") return;
|
|
331
|
+
let message;
|
|
332
|
+
try {
|
|
333
|
+
message = JSON.parse(event.data);
|
|
334
|
+
} catch {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (typeof message.key !== "string") return;
|
|
338
|
+
const key = message.key;
|
|
339
|
+
const tell = (frame) => {
|
|
340
|
+
try {
|
|
341
|
+
ws.send(JSON.stringify(frame));
|
|
342
|
+
} catch {}
|
|
343
|
+
};
|
|
344
|
+
const value = (current) => {
|
|
345
|
+
let text;
|
|
346
|
+
try {
|
|
347
|
+
text = JSON.stringify({
|
|
348
|
+
key,
|
|
349
|
+
value: current === void 0 ? null : current
|
|
350
|
+
});
|
|
351
|
+
} catch {
|
|
352
|
+
tell({
|
|
353
|
+
key,
|
|
354
|
+
error: "the value cannot be sent as JSON"
|
|
355
|
+
});
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
ws.send(text);
|
|
360
|
+
} catch {}
|
|
361
|
+
};
|
|
362
|
+
if (message.t === "unsub") {
|
|
363
|
+
following.get(key)?.();
|
|
364
|
+
following.delete(key);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (message.t !== "sub") return;
|
|
368
|
+
const cell = cells.find(key);
|
|
369
|
+
if (cell === void 0) {
|
|
370
|
+
tell({
|
|
371
|
+
key,
|
|
372
|
+
error: "no such state"
|
|
373
|
+
});
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const known = following.has(key);
|
|
377
|
+
if (!known && following.size >= 256) {
|
|
378
|
+
tell({
|
|
379
|
+
key,
|
|
380
|
+
error: "too many keys on one socket"
|
|
381
|
+
});
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (known) {
|
|
385
|
+
value(cell.get());
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
value(cell.get());
|
|
389
|
+
following.set(key, cell.subscribe(value));
|
|
390
|
+
},
|
|
391
|
+
onClose() {
|
|
392
|
+
if (beat !== void 0) clearInterval(beat);
|
|
393
|
+
for (const stop of following.values()) stop();
|
|
394
|
+
following.clear();
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}, { textOnly: true });
|
|
398
|
+
}
|
|
399
|
+
//#endregion
|
|
61
400
|
//#region ../../plugins/acryl-state/src/service.ts
|
|
62
401
|
/**
|
|
63
402
|
* The ACRYL state service (`ctx.state`): observable cells owned by plugins. A cell created from a plugin's context is removed
|
|
@@ -69,6 +408,8 @@ const KEEP_ALIVE_MS = 15e3;
|
|
|
69
408
|
/** Stable Cordis plugin name, equal to the package name and the Loader row id. */
|
|
70
409
|
const name = "acryl-state";
|
|
71
410
|
const KEY_PATTERN = /^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)*$/;
|
|
411
|
+
/** Names under `/api/state` that are routes, not keys: a cell cannot be called by one. */
|
|
412
|
+
const RESERVED_KEYS = /* @__PURE__ */ new Set(["ws", "client.js"]);
|
|
72
413
|
var State = class extends Service {
|
|
73
414
|
cells = /* @__PURE__ */ new Map();
|
|
74
415
|
constructor(ctx) {
|
|
@@ -76,6 +417,7 @@ var State = class extends Service {
|
|
|
76
417
|
}
|
|
77
418
|
claim(key) {
|
|
78
419
|
if (!KEY_PATTERN.test(key)) throw new TypeError(`acryl-state: key "${key}" must be dotted lowercase words, like "market.sources"`);
|
|
420
|
+
if (RESERVED_KEYS.has(key)) throw new TypeError(`acryl-state: "${key}" is the name of a route under /api/state, choose another key`);
|
|
79
421
|
if (this.cells.has(key)) throw new Error(`acryl-state: key "${key}" is already in use`);
|
|
80
422
|
}
|
|
81
423
|
report = (error) => {
|
|
@@ -111,9 +453,10 @@ var State = class extends Service {
|
|
|
111
453
|
}
|
|
112
454
|
/**
|
|
113
455
|
* The current value of `key` as a server-sent event stream: the value now, then one event per change, until the client cancels or the router
|
|
114
|
-
* is disposed (both abort the stream). A comment line goes out every 15 seconds so proxies and browsers keep an idle stream open.
|
|
456
|
+
* is disposed (both abort the stream). A comment line goes out every 15 seconds so proxies and browsers keep an idle stream open. A string value travels as it is; with `json`
|
|
457
|
+
* (the `?json=1` of the browser client) every value is JSON, so `"12"` and `12` can be told apart.
|
|
115
458
|
*/
|
|
116
|
-
stream(key, c) {
|
|
459
|
+
stream(key, c, json = false) {
|
|
117
460
|
const cell = this.find(key);
|
|
118
461
|
if (cell === void 0) return void 0;
|
|
119
462
|
return streamSSE(c, async (stream) => {
|
|
@@ -124,7 +467,7 @@ var State = class extends Service {
|
|
|
124
467
|
const send = (value) => {
|
|
125
468
|
queue = queue.then(() => stream.writeSSE({
|
|
126
469
|
event: "value",
|
|
127
|
-
data: typeof value === "string" ? value : JSON.stringify(value)
|
|
470
|
+
data: typeof value === "string" && !json ? value : JSON.stringify(value)
|
|
128
471
|
})).catch(() => {});
|
|
129
472
|
};
|
|
130
473
|
send(cell.get());
|
|
@@ -146,14 +489,35 @@ function apply(ctx) {
|
|
|
146
489
|
const state = new State(ctx);
|
|
147
490
|
ctx.inject(["router"], (inner) => {
|
|
148
491
|
const api = new Acryl();
|
|
149
|
-
|
|
492
|
+
let socket;
|
|
493
|
+
api.use(loopbackOnly({ inProcess: "allow" }), methodNotAllowed({ app: api })).get("/", (c) => c.json({ keys: state.keys() })).get("/client.js", (c) => c.body(CLIENT_SOURCE, 200, {
|
|
494
|
+
"content-type": "text/javascript; charset=utf-8",
|
|
495
|
+
"cache-control": "no-cache"
|
|
496
|
+
})).get("/ws", async (c, next) => {
|
|
497
|
+
const upgrade = socket;
|
|
498
|
+
if (upgrade === void 0) return c.json({ error: "this server does not upgrade to a WebSocket" }, 404);
|
|
499
|
+
const refusal = refuseStranger(c);
|
|
500
|
+
if (refusal !== void 0) return refusal;
|
|
501
|
+
return await upgrade(c, next);
|
|
502
|
+
}).get("/:key", (c) => {
|
|
150
503
|
const cell = state.find(c.req.param("key"));
|
|
151
504
|
return cell === void 0 ? c.json({ error: "no such state" }, 404) : c.json({
|
|
152
505
|
key: cell.key,
|
|
153
506
|
value: cell.get()
|
|
154
507
|
});
|
|
155
|
-
}).get("/:key/events", (c) => state.stream(c.req.param("key"), c) ?? c.json({ error: "no such state" }, 404));
|
|
508
|
+
}).get("/:key/events", (c) => state.stream(c.req.param("key"), c, c.req.query("json") === "1") ?? c.json({ error: "no such state" }, 404));
|
|
156
509
|
inner.router.route("/api/state", api);
|
|
510
|
+
inner.inject(["nodeServer"], (withServer) => {
|
|
511
|
+
try {
|
|
512
|
+
const upgrade = withServer.nodeServer.upgrades();
|
|
513
|
+
socket = stateSocket(state, { upgrade });
|
|
514
|
+
withServer.effect(() => () => {
|
|
515
|
+
socket = void 0;
|
|
516
|
+
}, "acryl-state: the WebSocket route");
|
|
517
|
+
} catch (cause) {
|
|
518
|
+
ctx.logger.warn(`acryl-state: state is followed by event streams only (${cause instanceof Error ? cause.message : String(cause)})`);
|
|
519
|
+
}
|
|
520
|
+
});
|
|
157
521
|
inner.inject(["routerHttp"], (http) => {
|
|
158
522
|
http.routerHttp.mount("/api/state");
|
|
159
523
|
});
|
package/lib/door/host.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as Acryl } from "../app-D8riatvv.js";
|
|
2
|
-
import { n as AuditEntry, y as UiSnapshot } from "../protocol-
|
|
3
|
-
import { i as UiChannel, n as auditPath, r as PageStream, t as AuditLog } from "../audit-
|
|
2
|
+
import { n as AuditEntry, y as UiSnapshot } from "../protocol-BeIYCgTo.js";
|
|
3
|
+
import { i as UiChannel, n as auditPath, r as PageStream, t as AuditLog } from "../audit-Bok_UNCn.js";
|
|
4
4
|
|
|
5
5
|
//#region ../../plugins/acryl-door/src/host/page-routes.d.ts
|
|
6
6
|
/** How often the Host writes a `ping`; the page gives up on a stream that is silent for three of these. */
|
package/lib/door/host.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { d as AuditLog, f as auditPath, n as HEARTBEAT_MS, p as UiChannel, r as pageChannelRoutes, t as renderSnapshot } from "../snapshot-text-
|
|
1
|
+
import { d as AuditLog, f as auditPath, n as HEARTBEAT_MS, p as UiChannel, r as pageChannelRoutes, t as renderSnapshot } from "../snapshot-text-CXTnjxGx.js";
|
|
2
2
|
export { AuditLog, HEARTBEAT_MS, UiChannel, auditPath, pageChannelRoutes, renderSnapshot };
|
package/lib/door/page.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { _ as UiRequest, v as UiResult } from "../protocol-
|
|
1
|
+
import { _ as UiRequest, v as UiResult } from "../protocol-BeIYCgTo.js";
|
|
2
2
|
|
|
3
3
|
//#region ../../plugins/acryl-door/src/page/driver/driver.d.ts
|
|
4
4
|
interface DriverActivity {
|
|
5
5
|
/** True from the moment a call starts until it ends. */
|
|
6
6
|
readonly busy: boolean;
|
|
7
|
+
/** True from the moment a run asked for from this page's panel starts until it ends, between the calls too. */
|
|
8
|
+
readonly running: boolean;
|
|
7
9
|
/** What the agent is doing, for the indicator ("click", "type", ...). */
|
|
8
10
|
readonly doing: string;
|
|
9
11
|
/** True after the user pressed the kill switch. */
|
|
@@ -14,6 +16,8 @@ interface UiDriverOptions {
|
|
|
14
16
|
/** Milliseconds since the user last used the page (mouse, keyboard); Infinity when never. */
|
|
15
17
|
readonly msSinceUserInput?: () => number;
|
|
16
18
|
readonly sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
19
|
+
/** Called with the element an act is about to use, for the page to show it (the door's outline on what the agent touches). */
|
|
20
|
+
readonly touched?: (element: Element) => void;
|
|
17
21
|
/** Leave the page: reload it, or go to a path of the app. Default: the document's own `location`, a moment after the answer is sent. */
|
|
18
22
|
readonly leave?: (to: {
|
|
19
23
|
readonly reload: true;
|
|
@@ -32,6 +36,7 @@ declare class UiDriver {
|
|
|
32
36
|
private busyCount;
|
|
33
37
|
private doing;
|
|
34
38
|
private current;
|
|
39
|
+
private runningFlag;
|
|
35
40
|
private readonly options;
|
|
36
41
|
constructor(options: UiDriverOptions);
|
|
37
42
|
/** The current activity; the same object until something changes, as a store snapshot must be. */
|
|
@@ -39,6 +44,11 @@ declare class UiDriver {
|
|
|
39
44
|
subscribe(listener: (activity: DriverActivity) => void): () => void;
|
|
40
45
|
/** The user pressed the kill switch: pending calls settle as `killed` and new ones are refused. */
|
|
41
46
|
kill(): void;
|
|
47
|
+
/**
|
|
48
|
+
* A run asked for from this page's panel starts (`true`) or ends (`false`). The indicator stays up for the whole run, not only while a call is in flight. A new run is a new
|
|
49
|
+
* question from the person, so it also allows the agent to drive again after an earlier Stop.
|
|
50
|
+
*/
|
|
51
|
+
setRunning(running: boolean): void;
|
|
42
52
|
/** The user allowed the agent to drive again. */
|
|
43
53
|
resume(): void;
|
|
44
54
|
/** The driver goes away: pending calls settle as `unloaded`; refs are gone. */
|
|
@@ -46,6 +56,11 @@ declare class UiDriver {
|
|
|
46
56
|
/** @param signal - the Host cancelled the call. @throws UiControlError. */
|
|
47
57
|
handle(request: UiRequest, signal?: AbortSignal): Promise<UiResult>;
|
|
48
58
|
private run;
|
|
59
|
+
/**
|
|
60
|
+
* Run an act that may start a navigation (a click on a link, Enter in a form) and say so: the browser asks `beforeunload` when a navigation starts, during the act or a
|
|
61
|
+
* moment after it. The answer is sent before the page goes; the Host then waits for the next page instead of reading this one again.
|
|
62
|
+
*/
|
|
63
|
+
private watchLeaving;
|
|
49
64
|
/**
|
|
50
65
|
* The page is about to be replaced, so the answer is given first and the move happens a moment later (the answer is a POST that the page's own unload would cut off).
|
|
51
66
|
* The Host does not wait for this answer to be the last word: it waits for the page to come back.
|
|
@@ -73,6 +88,8 @@ interface ChannelSource {
|
|
|
73
88
|
declare const SILENCE_MS = 45000;
|
|
74
89
|
interface PageChannelOptions {
|
|
75
90
|
readonly windowId: string;
|
|
91
|
+
/** The browser tab the page is in (kept across the tab's reloads and moves), so the Host follows a run's page into the next document of its tab. */
|
|
92
|
+
readonly tab?: string;
|
|
76
93
|
/** The desktop window this page is in (`AcrylWindow/<n>` in the user agent, set by the Electron shell), so the Host can drive it by the number `window list` prints. */
|
|
77
94
|
readonly shellWindow?: number;
|
|
78
95
|
readonly focused: () => boolean;
|
|
@@ -87,6 +104,8 @@ interface PageChannelOptions {
|
|
|
87
104
|
readonly post?: (url: string, body: string) => Promise<unknown>;
|
|
88
105
|
readonly silenceMs?: number;
|
|
89
106
|
readonly now?: () => number;
|
|
107
|
+
/** The Host says a run asked for from this page's panel started (`true`) or ended (`false`); false too when the stream is lost, until the Host says again. */
|
|
108
|
+
readonly onRun?: (running: boolean) => void;
|
|
90
109
|
}
|
|
91
110
|
declare class PageChannel {
|
|
92
111
|
private source;
|