acrylblends 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/README.md +9 -0
  2. package/dist/index.d.ts +47 -2
  3. package/dist/index.js +2017 -32
  4. package/dist/index.js.map +1 -1
  5. package/docs/framework/agents/index.md +1 -1
  6. package/docs/framework/getting-started/desktop.md +23 -4
  7. package/docs/framework/guides/create-acrylblends.md +5 -3
  8. package/docs/framework/workshop/README.md +139 -153
  9. package/docs/framework/workshop/facilitator.md +69 -101
  10. package/legacy/README.md +9 -0
  11. package/legacy/agent/157ce3db1112/src/app.ts +98 -0
  12. package/legacy/agent/157ce3db1112/src/routes.ts +63 -0
  13. package/legacy/agent/24c681a00fca/src/app.ts +160 -0
  14. package/legacy/agent/24c681a00fca/src/routes.ts +83 -0
  15. package/legacy/agent/2e790da977b2/src/app.ts +98 -0
  16. package/legacy/agent/2e790da977b2/src/routes.ts +63 -0
  17. package/legacy/agent/6307045545dc/src/app.ts +168 -0
  18. package/legacy/agent/6307045545dc/src/routes.ts +83 -0
  19. package/legacy/agent/6a5fb9c15c53/src/app.ts +158 -0
  20. package/legacy/agent/6a5fb9c15c53/src/routes.ts +83 -0
  21. package/legacy/agent/71fb54a5a942/src/app.ts +111 -0
  22. package/legacy/agent/71fb54a5a942/src/routes.ts +63 -0
  23. package/legacy/agent/74ece148269c/src/app.ts +168 -0
  24. package/legacy/agent/74ece148269c/src/routes.ts +93 -0
  25. package/legacy/agent/855359304707/src/app.ts +168 -0
  26. package/legacy/agent/855359304707/src/routes.ts +126 -0
  27. package/legacy/agent/b5ef3c786ae4/src/app.ts +127 -0
  28. package/legacy/agent/b5ef3c786ae4/src/routes.ts +76 -0
  29. package/legacy/agent/c2f30958043b/src/app.ts +98 -0
  30. package/legacy/agent/c2f30958043b/src/routes.ts +53 -0
  31. package/legacy/blank/5a91c558af22/src/routes.ts +18 -0
  32. package/legacy/blank/9c650cf52bf3/src/routes.ts +14 -0
  33. package/legacy/blank/aea589283445/src/routes.ts +16 -0
  34. package/legacy/blank/b2cbb759c889/src/routes.ts +16 -0
  35. package/legacy/blank/d6d371c9a65e/src/routes.ts +18 -0
  36. package/legacy/chat/7076cc821e2c/src/app.ts +72 -0
  37. package/legacy/chat/7076cc821e2c/src/routes.ts +13 -0
  38. package/legacy/chat/7dbe6d5170df/src/app.ts +107 -0
  39. package/legacy/chat/7dbe6d5170df/src/routes.ts +19 -0
  40. package/legacy/chat/a9a9ab44c58e/src/app.ts +107 -0
  41. package/legacy/chat/a9a9ab44c58e/src/routes.ts +15 -0
  42. package/legacy/chat/f20ad9a51b06/src/app.ts +106 -0
  43. package/legacy/chat/f20ad9a51b06/src/routes.ts +15 -0
  44. package/legacy/layouts.json +524 -0
  45. package/legacy/nodejs/1d477f37aaf4/src/app.ts +92 -0
  46. package/legacy/nodejs/1d477f37aaf4/src/routes.ts +40 -0
  47. package/legacy/nodejs/b1bd9462496c/src/app.ts +61 -0
  48. package/legacy/nodejs/b1bd9462496c/src/routes.ts +37 -0
  49. package/legacy/nodejs/d5bab75076ef/src/app.ts +95 -0
  50. package/legacy/nodejs/d5bab75076ef/src/routes.ts +40 -0
  51. package/legacy/notes/4edc36949c15/src/app.ts +107 -0
  52. package/legacy/notes/4edc36949c15/src/routes.ts +14 -0
  53. package/legacy/notes/6cab2d09747c/src/app.ts +72 -0
  54. package/legacy/notes/6cab2d09747c/src/routes.ts +12 -0
  55. package/legacy/notes/7ca2dfed623a/src/app.ts +106 -0
  56. package/legacy/notes/7ca2dfed623a/src/routes.ts +14 -0
  57. package/legacy/todo/02e613b3625c/src/app.ts +71 -0
  58. package/legacy/todo/02e613b3625c/src/todo.ts +123 -0
  59. package/legacy/todo/45694a8d9e15/src/app.ts +71 -0
  60. package/legacy/todo/45694a8d9e15/src/todo.ts +96 -0
  61. package/legacy/todo/5923d3a6099f/src/app.ts +71 -0
  62. package/legacy/todo/5923d3a6099f/src/todo.ts +125 -0
  63. package/legacy/todo/849a18d66dc1/src/app.ts +91 -0
  64. package/legacy/todo/849a18d66dc1/src/todo.ts +125 -0
  65. package/legacy/todo/8e64211312f3/src/app.ts +71 -0
  66. package/legacy/todo/8e64211312f3/src/todo.ts +125 -0
  67. package/legacy/todo/b79078a87565/src/app.ts +71 -0
  68. package/legacy/todo/b79078a87565/src/todo.ts +125 -0
  69. package/lib/{audit-BFpULBCj.d.ts → audit-Bok_UNCn.d.ts} +26 -3
  70. package/lib/{client-modules-drj4mjhj.js → client-modules-Bkdr83jO.js} +1 -1
  71. package/lib/cordis/door.d.ts +87 -9
  72. package/lib/cordis/door.js +324 -60
  73. package/lib/cordis/inspect.js +1 -1
  74. package/lib/cordis/node-server.d.ts +101 -3
  75. package/lib/cordis/node-server.js +3 -3
  76. package/lib/cordis/router-http.d.ts +7 -1
  77. package/lib/cordis/router-http.js +44 -3
  78. package/lib/cordis/router.d.ts +1 -1
  79. package/lib/cordis/router.js +1 -1
  80. package/lib/cordis/state.js +1 -1
  81. package/lib/door/host.d.ts +2 -2
  82. package/lib/door/host.js +1 -1
  83. package/lib/door/page.d.ts +10 -1
  84. package/lib/door/page.js +46 -7
  85. package/lib/door/protocol.d.ts +1 -1
  86. package/lib/drivers.d.ts +10 -1
  87. package/lib/{index-DMOtA-NJ.d.ts → index-DGq7H0lb.d.ts} +11 -1
  88. package/lib/node-server.d.ts +7 -1
  89. package/lib/node-server.js +2 -2
  90. package/lib/{protocol-a2aqrp2X.d.ts → protocol-BeIYCgTo.d.ts} +4 -0
  91. package/lib/{snapshot-text-BwM6T2nG.js → snapshot-text-CXTnjxGx.js} +87 -7
  92. package/lib/src-CtkIX_Gg.js +427 -0
  93. package/lib/{src-BTBm4OPD.js → src-iBJ2ysV9.js} +77 -14
  94. package/lib/{test-websocket-BLeEvybj.js → test-websocket-CQOSvBth.js} +17 -7
  95. package/lib/{test-websocket-DyeS50Tr.d.ts → test-websocket-GUTAkFQT.d.ts} +3 -1
  96. package/package.json +2 -1
  97. package/plugins/acryl-door/plugin.json +3 -0
  98. package/plugins/acryl-door/src/cli/acryl.ts +63 -6
  99. package/plugins/acryl-door/src/host/channel.ts +86 -9
  100. package/plugins/acryl-door/src/host/chat-tools.ts +8 -6
  101. package/plugins/acryl-door/src/host/door-file.ts +7 -2
  102. package/plugins/acryl-door/src/host/door.ts +128 -24
  103. package/plugins/acryl-door/src/host/operator-routes.ts +21 -1
  104. package/plugins/acryl-door/src/host/page-routes.ts +3 -1
  105. package/plugins/acryl-door/src/host/page-socket.ts +18 -8
  106. package/plugins/acryl-door/src/host/verbs.ts +14 -1
  107. package/plugins/acryl-door/src/index.ts +168 -30
  108. package/plugins/acryl-door/src/page/base.ts +32 -0
  109. package/plugins/acryl-door/src/page/channel.ts +3 -1
  110. package/plugins/acryl-door/src/page/driver/accessibility.ts +11 -0
  111. package/plugins/acryl-door/src/page/driver/actions.ts +4 -0
  112. package/plugins/acryl-door/src/page/driver/driver.ts +32 -4
  113. package/plugins/acryl-door/src/page/driver/snapshot.ts +5 -2
  114. package/plugins/acryl-door/src/page/index.ts +41 -2
  115. package/plugins/acryl-door/src/page/outline.ts +56 -0
  116. package/plugins/acryl-door/src/page/protocol.ts +4 -0
  117. package/plugins/acryl-door/src/page/snapshot-text.ts +2 -1
  118. package/plugins/acryl-node-server/src/adapter/listener.ts +4 -0
  119. package/plugins/acryl-node-server/src/adapter/raw.ts +18 -0
  120. package/plugins/acryl-node-server/src/adapter/websocket.ts +15 -9
  121. package/plugins/acryl-node-server/src/client-modules.ts +7 -1
  122. package/plugins/acryl-node-server/src/index.ts +304 -16
  123. package/plugins/acryl-node-server/src/upgrade-hub.ts +2 -2
  124. package/plugins/acryl-page/plugin.json +12 -0
  125. package/plugins/acryl-page/src/index.ts +163 -0
  126. package/plugins/acryl-router/src/service.ts +77 -14
  127. package/plugins/acryl-router-http/src/index.ts +15 -1
  128. package/plugins/acryl-router-http/src/serve.ts +31 -0
  129. package/plugins/acryl-web-server/plugin.json +14 -0
  130. package/plugins/acryl-web-server/src/index.ts +164 -0
  131. package/plugins/agent/agent.ts +38 -7
  132. package/plugins/agent/index.ts +1 -1
  133. package/plugins/agent/model.ts +10 -2
  134. package/plugins/agent/tools.ts +9 -3
  135. package/plugins/agent-panel/client/describe.ts +57 -0
  136. package/plugins/agent-panel/client/driver-pick.ts +22 -0
  137. package/plugins/agent-panel/client/frame.ts +51 -0
  138. package/plugins/agent-panel/client/main.ts +238 -26
  139. package/plugins/agent-panel/drivers.ts +7 -2
  140. package/plugins/agent-panel/headless.ts +5 -2
  141. package/plugins/agent-panel/index.ts +146 -57
  142. package/plugins/agent-panel/mount.ts +27 -0
  143. package/plugins/agent-panel/visitors.ts +231 -0
  144. package/plugins/builder/index.ts +13 -35
  145. package/plugins/builder/package.ts +186 -0
  146. package/plugins/builder/plugin.json +3 -1
  147. package/plugins/desktop/frame.ts +231 -0
  148. package/plugins/desktop/launch.ts +44 -9
  149. package/plugins/desktop/main.ts +100 -32
  150. package/plugins/desktop/window.ts +21 -4
  151. package/plugins/driver-pty/client/terminal.ts +4 -1
  152. package/plugins/driver-pty/index.ts +38 -11
  153. package/plugins/driver-pty/spawn.ts +38 -0
  154. package/plugins/driver-pty/terminal.ts +2 -0
  155. package/plugins/extensions/index.ts +47 -18
  156. package/plugins/extensions/plugin-store.ts +27 -0
  157. package/plugins/extensions/plugin.json +1 -1
  158. package/plugins/extensions/resolve.ts +13 -0
  159. package/plugins/extensions/stage.ts +15 -5
  160. package/plugins/feedback/client.d.ts +22 -0
  161. package/plugins/feedback/client.js +471 -0
  162. package/plugins/feedback/index.ts +143 -0
  163. package/plugins/feedback/plugin.json +11 -0
  164. package/plugins/feedback/store.ts +307 -0
  165. package/plugins/home/index.ts +122 -0
  166. package/plugins/home/plugin.json +11 -0
  167. package/plugins/instance/index.ts +6 -0
  168. package/plugins/manifest/app-plugins.ts +86 -0
  169. package/plugins/manifest/index.ts +12 -2
  170. package/plugins/manifest/plugin.json +1 -1
  171. package/plugins/manifest/snapshot.ts +275 -0
  172. package/plugins/openai/index.ts +64 -5
  173. package/plugins/openai/prompt.ts +23 -0
  174. package/plugins/plugin-share/README.md +51 -0
  175. package/plugins/plugin-share/index.ts +467 -0
  176. package/plugins/plugin-share/plugin.json +15 -0
  177. package/plugins/plugin-share/publish.ts +65 -0
  178. package/plugins/plugin-share/pull-check.ts +129 -0
  179. package/plugins/plugin-share/pulled-records.ts +70 -0
  180. package/shared/AGENTS.core.md +36 -16
  181. package/shared/skills/acryl-add-ui/SKILL.md +19 -0
  182. package/shared/skills/acryl-app/SKILL.md +13 -0
  183. package/shared/skills/acryl-build-plugin/SKILL.md +22 -0
  184. package/shared/skills/acryl-change-plugin/SKILL.md +14 -0
  185. package/shared/skills/acryl-diagnose-plugin/SKILL.md +13 -0
  186. package/shared/skills/acryl-fix-plugin/SKILL.md +13 -0
  187. package/shared/skills/acryl-remove-plugin/SKILL.md +12 -0
  188. package/shared/skills/acryl-share-plugin/SKILL.md +16 -0
  189. package/shared/skills/cordis-plugin/SKILL.md +26 -0
  190. package/templates/agent/.gitattributes +2 -0
  191. package/templates/agent/AGENTS.md +1 -1
  192. package/templates/agent/blend.yaml +6 -0
  193. package/templates/agent/gitignore +1 -0
  194. package/templates/agent/package.json +1 -0
  195. package/templates/agent/src/app.ts +154 -33
  196. package/templates/agent/src/frame.ts +17 -0
  197. package/templates/agent/src/main.ts +5 -3
  198. package/templates/agent/src/routes.ts +38 -19
  199. package/templates/agent/template.json +3 -0
  200. package/templates/agent/test/app.test.ts +45 -3
  201. package/templates/blank/.gitattributes +2 -0
  202. package/templates/blank/AGENTS.md +3 -2
  203. package/templates/blank/blend.yaml +11 -0
  204. package/templates/blank/gitignore +1 -0
  205. package/templates/blank/package.json +1 -0
  206. package/templates/blank/src/app.ts +100 -29
  207. package/templates/blank/src/door.ts +7 -0
  208. package/templates/blank/src/first-page.ts +5 -0
  209. package/templates/blank/src/frame.ts +17 -0
  210. package/templates/blank/src/routes.ts +65 -8
  211. package/templates/blank/template.json +14 -1
  212. package/templates/blank/test/app.test.ts +103 -16
  213. package/templates/chat/.gitattributes +2 -0
  214. package/templates/chat/blend.yaml +2 -1
  215. package/templates/chat/gitignore +1 -0
  216. package/templates/chat/package.json +1 -0
  217. package/templates/chat/src/app.ts +72 -22
  218. package/templates/chat/src/chat-app.ts +11 -2
  219. package/templates/chat/src/client/chat.tsx +4 -2
  220. package/templates/chat/src/frame.ts +17 -0
  221. package/templates/chat/src/page.tsx +6 -4
  222. package/templates/chat/src/routes.ts +14 -4
  223. package/templates/chat/src/runtime.ts +9 -3
  224. package/templates/chat/template.json +3 -1
  225. package/templates/chat/test/chat.test.ts +46 -1
  226. package/templates/chat/test/server.test.ts +17 -3
  227. package/templates/nodejs/.gitattributes +2 -0
  228. package/templates/nodejs/blend.yaml +2 -1
  229. package/templates/nodejs/gitignore +1 -0
  230. package/templates/nodejs/package.json +1 -0
  231. package/templates/nodejs/src/app.ts +76 -21
  232. package/templates/nodejs/src/frame.ts +17 -0
  233. package/templates/nodejs/src/page.tsx +3 -3
  234. package/templates/nodejs/src/routes.ts +32 -13
  235. package/templates/nodejs/template.json +3 -1
  236. package/templates/nodejs/test/app.test.ts +38 -3
  237. package/templates/nodejs/test/server.test.ts +17 -3
  238. package/templates/notes/.gitattributes +2 -0
  239. package/templates/notes/blend.yaml +2 -1
  240. package/templates/notes/gitignore +1 -0
  241. package/templates/notes/package.json +1 -0
  242. package/templates/notes/src/app.ts +72 -22
  243. package/templates/notes/src/frame.ts +17 -0
  244. package/templates/notes/src/notes-app.ts +24 -17
  245. package/templates/notes/src/routes.ts +11 -4
  246. package/templates/notes/src/runtime.ts +9 -3
  247. package/templates/notes/src/views.tsx +26 -17
  248. package/templates/notes/template.json +3 -1
  249. package/templates/notes/test/notes.test.ts +39 -1
  250. package/templates/notes/test/server.test.ts +17 -3
  251. package/templates/todo/.gitattributes +2 -0
  252. package/templates/todo/blend.yaml +3 -0
  253. package/templates/todo/gitignore +1 -0
  254. package/templates/todo/package.json +1 -0
  255. package/templates/todo/src/app.ts +81 -27
  256. package/templates/todo/src/door.ts +7 -0
  257. package/templates/todo/src/frame.ts +17 -0
  258. package/templates/todo/src/page.ts +13 -6
  259. package/templates/todo/src/todo.ts +18 -6
  260. package/templates/todo/template.json +11 -1
  261. package/templates/todo/test/todo.test.ts +67 -9
  262. package/lib/src-LX0E4ec4.js +0 -117
  263. package/shared/SKILL.md +0 -14
@@ -24,6 +24,8 @@ export type RunResult = (
24
24
  export interface RunOptions {
25
25
  /** Aborting it stops the run: the model call or tool in flight is dropped and the run ends `{ status: 'cancelled' }`. */
26
26
  readonly signal?: AbortSignal
27
+ /** The door's id of the page the run was asked from: every tool call carries it, so the page tools drive that page and no other (a production assistant serves many visitors). */
28
+ readonly page?: string
27
29
  }
28
30
 
29
31
  export interface Agent {
@@ -39,6 +41,16 @@ export interface Budget {
39
41
  export interface AgentConfig {
40
42
  /** Steps per run before it stops. */
41
43
  steps?: number
44
+ /**
45
+ * The only tools a run may see and call; absent: every tool in the registry. A production assistant gets the page tools alone (`door_look`, `door_act`), so whatever
46
+ * a visitor asks, the agent can use the app and never change it. A call to any other name fails as an unknown tool would.
47
+ */
48
+ tools?: readonly string[]
49
+ /**
50
+ * The tool whose result an answer must come right after. When the model answers after another tool call (it acted and did not look), the agent calls this tool
51
+ * itself, once, and asks the model again with what it shows. The production assistant sets `door_look`, so what it tells a visitor is what the page shows now.
52
+ */
53
+ answerAfter?: string
42
54
  }
43
55
 
44
56
  /**
@@ -91,6 +103,8 @@ export const agent = {
91
103
  inject: ['model', 'tools', 'memory'],
92
104
  apply(ctx: Context, config?: AgentConfig) {
93
105
  const limit = config?.steps ?? 6
106
+ const allowed = config?.tools === undefined ? undefined : new Set(config.tools)
107
+ const answerAfter = config?.answerAfter
94
108
  let runs = 0
95
109
  ctx.provide('agent', {
96
110
  async run(question, options) {
@@ -104,13 +118,15 @@ export const agent = {
104
118
  )
105
119
  // Assigned inside the run's fiber; `as` keeps TypeScript from narrowing it to this first value.
106
120
  let result = { status: 'budget_exceeded', steps: 0, trace } as RunResult
107
- const task = ctx.plugin({
121
+ // Each run's `budget` is its own: runs may overlap (the production Assistant serves visitors at once), and a service name is shared unless isolated.
122
+ const task = ctx.isolate('budget').plugin({
108
123
  name: `task-${runs}`,
109
124
  inject: ['model', 'tools', 'memory'],
110
125
  async apply(scope: Context) {
111
126
  scope.provide('budget', { steps: 0, limit } satisfies Budget)
112
127
  const budget = scope.budget
113
128
  let toolResult: unknown
129
+ let looked = false
114
130
  while (budget.steps < budget.limit) {
115
131
  if (signal.aborted) {
116
132
  result = { status: 'cancelled', steps: budget.steps, trace }
@@ -124,7 +140,7 @@ export const agent = {
124
140
  step: budget.steps,
125
141
  toolResult,
126
142
  trace: [...trace],
127
- tools: scope.tools.list(),
143
+ tools: scope.tools.list().filter((tool) => allowed?.has(tool.name) ?? true),
128
144
  signal,
129
145
  }),
130
146
  signal,
@@ -136,16 +152,31 @@ export const agent = {
136
152
  }
137
153
  budget.steps += 1
138
154
  if (step.tool === undefined) {
139
- scope.memory.remember(`${question} => ${step.text}`)
140
- result = { status: 'completed', output: step.text, steps: budget.steps, trace }
141
- return
142
- }
155
+ const last = trace.at(-1)
156
+ const unseen =
157
+ answerAfter !== undefined &&
158
+ last !== undefined &&
159
+ last.tool !== answerAfter &&
160
+ !looked
161
+ if (!unseen) {
162
+ scope.memory.remember(`${question} => ${step.text}`)
163
+ result = { status: 'completed', output: step.text, steps: budget.steps, trace }
164
+ return
165
+ }
166
+ step = { tool: answerAfter, input: {} }
167
+ looked = true
168
+ } else looked = false
143
169
  const call = record
144
170
  ? recorder?.start(`tool:${step.tool}`, step.input, { parent: record.id })
145
171
  : undefined
146
172
  try {
173
+ if (allowed !== undefined && !allowed.has(step.tool))
174
+ throw new Error(`no tool named "${step.tool}" is allowed in this run`)
147
175
  toolResult = await until(
148
- scope.tools.call(step.tool, step.input, { signal }),
176
+ scope.tools.call(step.tool, step.input, {
177
+ signal,
178
+ ...(options?.page === undefined ? {} : { page: options.page }),
179
+ }),
149
180
  signal,
150
181
  )
151
182
  trace.push({
@@ -17,4 +17,4 @@ export {
17
17
  type Step,
18
18
  scripted,
19
19
  } from './model.ts'
20
- export { type Tool, type Tools, tools } from './tools.ts'
20
+ export { type CallOptions, type Tool, type Tools, tools } from './tools.ts'
@@ -52,8 +52,16 @@ declare module '@deepseek-ai/cordis' {
52
52
  }
53
53
 
54
54
  const builtIn: Rule[] = [
55
- ({ toolResult, step }) =>
56
- step > 0 ? { text: `The answer is ${String(toolResult)}.` } : undefined,
55
+ ({ toolResult, step }) => {
56
+ if (step === 0) return undefined
57
+ // A failed call comes back as `{ error }`: say what failed, not "[object Object]".
58
+ const failed = typeof toolResult === 'object' && toolResult !== null && 'error' in toolResult
59
+ return {
60
+ text: failed
61
+ ? `That did not work: ${String(toolResult.error)}.`
62
+ : `The answer is ${String(toolResult)}.`,
63
+ }
64
+ },
57
65
  ({ question }) => {
58
66
  const sum = /^add\s+(-?\d+)\s+(-?\d+)$/.exec(question.trim())
59
67
  return sum ? { tool: 'add', input: [Number(sum[1]), Number(sum[2])] } : undefined
@@ -1,12 +1,18 @@
1
1
  import type { Context } from '@deepseek-ai/cordis'
2
2
 
3
+ /** What a run passes with each call: its signal, and the door's id of the page it was asked from (the page tools drive only that page). */
4
+ export interface CallOptions {
5
+ readonly signal?: AbortSignal
6
+ readonly page?: string
7
+ }
8
+
3
9
  /** One tool: what it does (for the model) and how to run it. */
4
10
  export interface Tool {
5
11
  readonly description: string
6
12
  /** The JSON Schema of the input (an object schema), sent to a real model so it knows which fields the tool takes. A tool without one takes any object. */
7
13
  readonly parameters?: Readonly<Record<string, unknown>>
8
- /** `options.signal` aborts when the run is cancelled; a tool that can stop its work listens to it. */
9
- run(input: unknown, options?: { signal?: AbortSignal }): unknown | Promise<unknown>
14
+ /** `options.signal` aborts when the run is cancelled; a tool that can stop its work listens to it. `options.page` is the page the run was asked from, when it was. */
15
+ run(input: unknown, options?: CallOptions): unknown | Promise<unknown>
10
16
  }
11
17
 
12
18
  /** The tool registry. A plugin registers its tools inside `ctx.effect`, so they leave with it. */
@@ -18,7 +24,7 @@ export interface Tools {
18
24
  description: string
19
25
  parameters?: Readonly<Record<string, unknown>>
20
26
  }>
21
- call(name: string, input: unknown, options?: { signal?: AbortSignal }): Promise<unknown>
27
+ call(name: string, input: unknown, options?: CallOptions): Promise<unknown>
22
28
  }
23
29
 
24
30
  declare module '@deepseek-ai/cordis' {
@@ -42,3 +42,60 @@ export function describeTool(data: Record<string, unknown>): string | undefined
42
42
  const failed = data.error ? ` failed: ${clip(String(data.error))}` : ''
43
43
  return `${tool}${input}${failed}`
44
44
  }
45
+
46
+ /** What an act touched, from its result: the door's chat tool answers `done: link "My tasks" (detail); look again before the next act`, its verb `{ target }`. */
47
+ function targetOf(result: unknown): { role?: string; name?: string } {
48
+ if (typeof result === 'string') {
49
+ const match = /^done: (\S+) "(.*)"(?: \(.*\))?; look again/u.exec(result)
50
+ return match === null ? {} : { role: match[1], name: match[2] }
51
+ }
52
+ if (typeof result !== 'object' || result === null) return {}
53
+ const target = (result as { target?: unknown }).target
54
+ if (typeof target !== 'object' || target === null) return {}
55
+ const { role, name } = target as { role?: unknown; name?: unknown }
56
+ return {
57
+ ...(typeof role === 'string' ? { role } : {}),
58
+ ...(typeof name === 'string' ? { name } : {}),
59
+ }
60
+ }
61
+
62
+ /**
63
+ * The Assistant's step on the page, as the person reads it under the answer: what it did, named the way the page names it. `ui.open("My tasks")`,
64
+ * `ui.fill("Due", "This week")`, `ui.click("Save")`, `ui.press("Enter")`. A look changes nothing, so it is left out unless it failed. The control's name comes from the
65
+ * act's result (the page says what it acted on); the ref is the fallback.
66
+ */
67
+ export function describePageStep(data: Record<string, unknown>): string | undefined {
68
+ const failed = data.error ? ' failed' : ''
69
+ if (data.tool === 'door_look') return failed === '' ? undefined : 'ui.look() failed'
70
+ if (data.tool !== 'door_act') return describeTool(data)
71
+ const input = (typeof data.input === 'object' && data.input !== null ? data.input : {}) as Record<
72
+ string,
73
+ unknown
74
+ >
75
+ const target = targetOf(data.result)
76
+ const quoted = (value: unknown): string => JSON.stringify(clip(String(value ?? '')))
77
+ const op = typeof input.op === 'string' ? input.op : 'act'
78
+ const named =
79
+ typeof target.name === 'string' && target.name !== ''
80
+ ? quoted(target.name)
81
+ : typeof input.ref === 'string'
82
+ ? input.ref
83
+ : undefined
84
+ const [verb, args]: [string, readonly (string | undefined)[]] =
85
+ op === 'navigate'
86
+ ? ['open', [quoted(input.path)]]
87
+ : op === 'click' && target.role === 'link'
88
+ ? ['open', [named]]
89
+ : op === 'type'
90
+ ? ['fill', [named, quoted(input.text)]]
91
+ : op === 'select'
92
+ ? ['fill', [named, quoted(input.option)]]
93
+ : op === 'press'
94
+ ? ['press', [quoted(input.key)]]
95
+ : op === 'scroll'
96
+ ? ['scroll', [input.direction === undefined ? undefined : quoted(input.direction)]]
97
+ : op === 'wait'
98
+ ? ['wait', [quoted(input.text ?? input.name)]]
99
+ : [op, [named]]
100
+ return `ui.${verb}(${args.filter((arg) => arg !== undefined).join(', ')})${failed}`
101
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Which driver pick a finished terminal open may apply to. `choose()` can be in flight when the person picks
3
+ * another driver (the panel also restores a remembered terminal at start), and only the latest pick may win: a
4
+ * stale terminal must not replace the form the person chose.
5
+ */
6
+ export interface PickGeneration {
7
+ /** Start a pick and return the token its result is settled with. */
8
+ begin(): number
9
+ /** True only while `generation` is still the latest pick. */
10
+ isCurrent(generation: number): boolean
11
+ }
12
+
13
+ export function createPickGeneration(): PickGeneration {
14
+ let generation = 0
15
+ return {
16
+ begin: () => {
17
+ generation += 1
18
+ return generation
19
+ },
20
+ isCurrent: (candidate) => candidate === generation,
21
+ }
22
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The ACRYL frame's view as the app page sees it (ide/acryl-blends-frame/src/client/frame-view.ts): the frame's canvas hides the app's own build-time
3
+ * controls in Build and shows what production shows in Preview. The page is same-origin with the frame, so it reads the handle the frame publishes on
4
+ * its window through `window.parent`; a page opened directly has no such parent and keeps its own behavior.
5
+ */
6
+
7
+ export const FRAME_VIEW_KEY: symbol = Symbol.for('acryl.frame.view')
8
+ export const FRAME_MODE_EVENT = 'acryl:frame-mode'
9
+
10
+ /** What the app page reads from `window.parent[FRAME_VIEW_KEY]`. */
11
+ export interface FrameViewHandle {
12
+ readonly mode: 'build' | 'preview'
13
+ }
14
+
15
+ /**
16
+ * The frame's view handle when this page runs in the frame's canvas.
17
+ * @param win - the page's window.
18
+ * @returns the handle, or undefined when the page is the top window or its parent is cross-origin.
19
+ */
20
+ export function frameViewOf(win: Window = window): FrameViewHandle | undefined {
21
+ try {
22
+ if (win.parent === win) return undefined
23
+ return (win.parent as unknown as Record<symbol, FrameViewHandle | undefined>)[FRAME_VIEW_KEY]
24
+ } catch {
25
+ return undefined
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Whether the panel is the production Assistant: the Host itself is the Assistant, or the app's production environment names it (ACRYL_ASSISTANT=on)
31
+ * and the page runs in the frame, whose Preview shows the app as production would.
32
+ * @param session - the panel's session from its Host.
33
+ * @param frame - the frame's view handle, if any.
34
+ */
35
+ export function panelVariant(
36
+ session: { readonly assistant?: boolean; readonly assistantInProduction?: boolean },
37
+ frame: FrameViewHandle | undefined,
38
+ ): boolean {
39
+ if (session.assistant === true) return true
40
+ return frame !== undefined && session.assistantInProduction === true
41
+ }
42
+
43
+ /**
44
+ * Whether the panel shows at all: on a page opened directly, yes; in the frame, only in Preview, and only as the Assistant. Build is the builder's
45
+ * screen, where the app's own panel has no place over the canvas.
46
+ * @param frame - the frame's view handle, if any.
47
+ * @param assistant - whether this panel is the production Assistant (see {@link panelVariant}).
48
+ */
49
+ export function panelVisible(frame: FrameViewHandle | undefined, assistant: boolean): boolean {
50
+ return frame === undefined || (frame.mode === 'preview' && assistant)
51
+ }