acrylblends 0.11.0 → 1.0.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 +131 -53
- package/dist/index.d.ts +29 -2
- package/dist/index.js +450 -69
- package/dist/index.js.map +1 -1
- package/docs/framework/README.md +5 -3
- package/docs/framework/agents/index.md +1 -1
- package/docs/framework/api/acryl.md +5 -17
- package/docs/framework/api/context.md +12 -61
- package/docs/framework/api/presets.md +1 -1
- package/docs/framework/api/request.md +3 -3
- package/docs/framework/api/summary.md +1 -1
- package/docs/framework/concepts/web-standard.md +6 -16
- package/docs/framework/cordis/acryl-in-a-cordis-app.md +13 -8
- package/docs/framework/cordis/migration-from-0-4.md +1 -1
- package/docs/framework/cordis/migration-to-1-0.md +45 -0
- package/docs/framework/cordis/node-server.md +2 -2
- package/docs/framework/getting-started/basic.md +15 -10
- package/docs/framework/getting-started/desktop.md +1 -1
- package/docs/framework/getting-started/other-runtimes.md +10 -14
- package/docs/framework/guides/create-acrylblends.md +4 -2
- package/docs/framework/guides/data.md +1 -1
- package/docs/framework/guides/faq.md +1 -1
- package/docs/framework/guides/jsx-dom.md +1 -1
- package/docs/framework/guides/production.md +43 -0
- package/docs/framework/guides/rpc.md +3 -11
- package/docs/framework/helpers/adapter.md +13 -43
- package/docs/framework/helpers/conninfo.md +1 -36
- package/docs/framework/helpers/streaming.md +1 -1
- package/docs/framework/helpers/websocket.md +4 -11
- package/docs/framework/middleware/builtin/basic-auth.md +1 -1
- package/docs/framework/middleware/builtin/cache.md +2 -22
- package/docs/framework/middleware/builtin/compress.md +1 -1
- package/docs/framework/middleware/builtin/context-storage.md +10 -14
- package/docs/framework/middleware/builtin/cors.md +1 -2
- package/docs/framework/middleware/builtin/ip-restriction.md +1 -1
- package/docs/framework/middleware/builtin/jsx-renderer.md +1 -1
- package/docs/framework/middleware/builtin/logger.md +1 -1
- package/docs/framework/middleware/builtin/loopback-only.md +1 -1
- package/docs/framework/middleware/builtin/mount.md +1 -1
- package/docs/framework/middleware/builtin/request-id.md +13 -20
- package/docs/framework/middleware/builtin/timing.md +1 -2
- package/lib/cordis/door.d.ts +5 -0
- package/lib/cordis/door.js +8 -4
- package/lib/cordis/inspect.js +1 -1
- package/lib/cordis/router-http.js +1 -1
- package/lib/cordis/router.d.ts +1 -2
- package/lib/cordis/router.js +2 -3
- package/lib/cordis/state.js +1 -1
- package/lib/door/host.js +1 -1
- package/lib/drivers.d.ts +66 -0
- package/lib/drivers.js +16 -0
- package/lib/index.d.ts +1 -2
- package/lib/index.js +1 -2
- package/lib/{snapshot-text-yaWlezbd.js → snapshot-text-DmE0YfeZ.js} +2 -2
- package/lib/{src-DNzw0Qsb.js → src-2Z4mJg0x.js} +0 -1
- package/package.json +5 -121
- package/plugins/acryl-door/src/index.ts +8 -0
- package/plugins/acryl-router/src/index.ts +0 -1
- package/plugins/agent-panel/drivers.ts +1 -16
- package/plugins/agent-panel/index.ts +1 -1
- package/plugins/agent-panel/registry.ts +16 -0
- package/plugins/driver-acp/README.md +53 -0
- package/plugins/driver-acp/chat.ts +258 -0
- package/plugins/driver-acp/connection.ts +239 -0
- package/plugins/driver-acp/index.ts +65 -0
- package/plugins/driver-acp/plugin.json +12 -0
- package/plugins/driver-acp/provenance.json +20 -0
- package/plugins/driver-pty/index.ts +1 -0
- package/shared/library-history.json +464 -0
- package/docs/framework/getting-started/aws-lambda.md +0 -115
- package/docs/framework/getting-started/cloudflare-pages.md +0 -53
- package/docs/framework/getting-started/cloudflare-workers.md +0 -67
- package/docs/framework/getting-started/lambda-edge.md +0 -41
- package/docs/framework/getting-started/netlify.md +0 -25
- package/docs/framework/getting-started/service-worker.md +0 -33
- package/docs/framework/getting-started/vercel.md +0 -28
- package/lib/aws-lambda.d.ts +0 -324
- package/lib/aws-lambda.js +0 -357
- package/lib/cloudflare-pages.d.ts +0 -58
- package/lib/cloudflare-pages.js +0 -78
- package/lib/cloudflare-workers.d.ts +0 -21
- package/lib/cloudflare-workers.js +0 -80
- package/lib/deprecated-BP4fPt38.js +0 -10
- package/lib/deprecated-CTy4N7ng.d.ts +0 -12
- package/lib/lambda-edge.d.ts +0 -92
- package/lib/lambda-edge.js +0 -105
- package/lib/netlify.d.ts +0 -29
- package/lib/netlify.js +0 -30
- package/lib/service-worker.d.ts +0 -48
- package/lib/service-worker.js +0 -38
- package/lib/vercel.d.ts +0 -10
- package/lib/vercel.js +0 -9
- package/plugins/acryl-router/src/deprecated.ts +0 -15
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One ACP agent process and the JSON-RPC 2.0 connection to it: newline-delimited messages over its stdin and stdout (the ACP stdio transport). The driver needs a handful of
|
|
3
|
+
* calls, so this is the whole client: requests with their answers, notifications both ways, and the agent's own requests answered by a handler. `close()` ends the process
|
|
4
|
+
* the way an ACP agent expects: stdin closed first, then SIGTERM, then a hard kill, and it resolves only once the process is gone.
|
|
5
|
+
*/
|
|
6
|
+
import { type ChildProcessWithoutNullStreams, spawn, spawnSync } from 'node:child_process'
|
|
7
|
+
|
|
8
|
+
/** How to start an ACP agent: a program and its arguments, and what to add to the app's environment. */
|
|
9
|
+
export interface AcpAgent {
|
|
10
|
+
readonly command: string
|
|
11
|
+
readonly args: readonly string[]
|
|
12
|
+
readonly env?: Readonly<Record<string, string>>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** An error the agent answered with, or the reason the connection ended. */
|
|
16
|
+
export class AcpError extends Error {
|
|
17
|
+
/** The JSON-RPC error code, for an error the agent answered with. */
|
|
18
|
+
readonly code: number | undefined
|
|
19
|
+
|
|
20
|
+
constructor(message: string, code?: number) {
|
|
21
|
+
super(message)
|
|
22
|
+
this.name = 'AcpError'
|
|
23
|
+
this.code = code
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Answers a request the agent sends (`session/request_permission`); throwing answers with an error. */
|
|
28
|
+
export type AgentRequestHandler = (method: string, params: unknown) => unknown
|
|
29
|
+
|
|
30
|
+
/** How long each step of `close()` waits before the next, harder one. */
|
|
31
|
+
const GRACE_MS = 2000
|
|
32
|
+
/** The most of the agent's stderr kept, to say why it ended. */
|
|
33
|
+
const STDERR_TAIL = 4000
|
|
34
|
+
|
|
35
|
+
interface Pending {
|
|
36
|
+
resolve(value: unknown): void
|
|
37
|
+
reject(error: Error): void
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class AcpConnection {
|
|
41
|
+
readonly #child: ChildProcessWithoutNullStreams
|
|
42
|
+
readonly #pending = new Map<number, Pending>()
|
|
43
|
+
readonly #listeners = new Set<(method: string, params: unknown) => void>()
|
|
44
|
+
#nextId = 1
|
|
45
|
+
#buffer = ''
|
|
46
|
+
#stderr = ''
|
|
47
|
+
#ended: AcpError | undefined
|
|
48
|
+
/** Resolves with the reason once the process is gone. */
|
|
49
|
+
readonly ended: Promise<AcpError>
|
|
50
|
+
|
|
51
|
+
constructor(agent: AcpAgent, cwd: string, handle: AgentRequestHandler) {
|
|
52
|
+
this.#child = spawn(agent.command, [...agent.args], {
|
|
53
|
+
cwd,
|
|
54
|
+
env: { ...process.env, ...agent.env },
|
|
55
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
56
|
+
// An npm-installed agent is a `.cmd` shim on Windows, which only a shell can start.
|
|
57
|
+
shell: process.platform === 'win32',
|
|
58
|
+
// Its own process group on POSIX, so closing the connection also ends what the agent started (its tools, a shell): nothing is left behind when the plugin unloads.
|
|
59
|
+
detached: process.platform !== 'win32',
|
|
60
|
+
windowsHide: true,
|
|
61
|
+
})
|
|
62
|
+
this.ended = new Promise((resolve) => {
|
|
63
|
+
const end = (reason: AcpError): void => {
|
|
64
|
+
if (this.#ended) return
|
|
65
|
+
this.#ended = reason
|
|
66
|
+
for (const pending of this.#pending.values()) pending.reject(reason)
|
|
67
|
+
this.#pending.clear()
|
|
68
|
+
resolve(reason)
|
|
69
|
+
}
|
|
70
|
+
this.#child.on('error', (error) =>
|
|
71
|
+
end(new AcpError(`${agent.command} could not be started: ${error.message}`)),
|
|
72
|
+
)
|
|
73
|
+
this.#child.on('exit', (code, signal) => {
|
|
74
|
+
const tail = this.#stderr.trim().split('\n').slice(-3).join(' ').slice(-500)
|
|
75
|
+
end(
|
|
76
|
+
new AcpError(
|
|
77
|
+
`${agent.command} ended (${signal ?? `exit code ${String(code)}`})${tail ? `: ${tail}` : ''}`,
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
this.#child.stderr.setEncoding('utf8')
|
|
83
|
+
this.#child.stderr.on('data', (chunk: string) => {
|
|
84
|
+
this.#stderr = (this.#stderr + chunk).slice(-STDERR_TAIL)
|
|
85
|
+
})
|
|
86
|
+
this.#child.stdout.setEncoding('utf8')
|
|
87
|
+
this.#child.stdout.on('data', (chunk: string) => {
|
|
88
|
+
this.#buffer += chunk
|
|
89
|
+
for (let end = this.#buffer.indexOf('\n'); end !== -1; end = this.#buffer.indexOf('\n')) {
|
|
90
|
+
const line = this.#buffer.slice(0, end).trim()
|
|
91
|
+
this.#buffer = this.#buffer.slice(end + 1)
|
|
92
|
+
if (line) this.#receive(line, handle)
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
// A write after the agent went away fails here instead of throwing out of the process.
|
|
96
|
+
this.#child.stdin.on('error', () => {})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Sends a request and resolves with its result; rejects with the agent's error, or when the process ends first. */
|
|
100
|
+
request<T>(method: string, params: unknown): Promise<T> {
|
|
101
|
+
if (this.#ended) return Promise.reject(this.#ended)
|
|
102
|
+
const id = this.#nextId++
|
|
103
|
+
return new Promise<T>((resolve, reject) => {
|
|
104
|
+
this.#pending.set(id, { resolve: resolve as (value: unknown) => void, reject })
|
|
105
|
+
this.#send({ jsonrpc: '2.0', id, method, params })
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
notify(method: string, params: unknown): void {
|
|
110
|
+
if (!this.#ended) this.#send({ jsonrpc: '2.0', method, params })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Hears every notification the agent sends; returns the function that stops listening. */
|
|
114
|
+
onNotification(listener: (method: string, params: unknown) => void): () => void {
|
|
115
|
+
this.#listeners.add(listener)
|
|
116
|
+
return () => void this.#listeners.delete(listener)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Ends the process and what it started: stdin closed, then SIGTERM, then a hard kill, each to the whole process group on POSIX (the tree, with `taskkill /T`, on Windows).
|
|
121
|
+
* Resolves once they are gone. Safe to call again, and after the agent ended on its own (its leftovers are still cleared).
|
|
122
|
+
*/
|
|
123
|
+
async close(): Promise<void> {
|
|
124
|
+
if (!this.#ended) {
|
|
125
|
+
const gone = (ms: number) =>
|
|
126
|
+
Promise.race([
|
|
127
|
+
this.ended.then(() => true),
|
|
128
|
+
new Promise<boolean>((done) => setTimeout(() => done(false), ms).unref()),
|
|
129
|
+
])
|
|
130
|
+
this.#child.stdin.end()
|
|
131
|
+
if (!(await gone(GRACE_MS))) {
|
|
132
|
+
this.#signal('SIGTERM')
|
|
133
|
+
if (!(await gone(GRACE_MS))) this.#signal('SIGKILL')
|
|
134
|
+
await this.ended
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
await this.#reapGroup()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** A signal to the agent and, on POSIX, everything in its group. */
|
|
141
|
+
#signal(signal: 'SIGTERM' | 'SIGKILL'): void {
|
|
142
|
+
const pid = this.#child.pid
|
|
143
|
+
if (pid === undefined) return
|
|
144
|
+
if (process.platform === 'win32') {
|
|
145
|
+
if (signal === 'SIGKILL')
|
|
146
|
+
spawnSync('taskkill', ['/pid', String(pid), '/T', '/F'], { windowsHide: true })
|
|
147
|
+
else this.#child.kill(signal)
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
process.kill(-pid, signal)
|
|
152
|
+
} catch {
|
|
153
|
+
this.#child.kill(signal)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** On POSIX, processes the agent left in its group are ended too: SIGTERM, then SIGKILL after the grace. */
|
|
158
|
+
async #reapGroup(): Promise<void> {
|
|
159
|
+
const pid = this.#child.pid
|
|
160
|
+
if (process.platform === 'win32' || pid === undefined) return
|
|
161
|
+
const groupAlive = (): boolean => {
|
|
162
|
+
try {
|
|
163
|
+
process.kill(-pid, 0)
|
|
164
|
+
return true
|
|
165
|
+
} catch (error) {
|
|
166
|
+
return (error as NodeJS.ErrnoException).code === 'EPERM'
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
for (const signal of ['SIGTERM', 'SIGKILL'] as const) {
|
|
170
|
+
if (!groupAlive()) return
|
|
171
|
+
try {
|
|
172
|
+
process.kill(-pid, signal)
|
|
173
|
+
} catch {
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
for (let waited = 0; waited < GRACE_MS && groupAlive(); waited += 25)
|
|
177
|
+
await new Promise((done) => setTimeout(done, 25))
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
#send(message: unknown): void {
|
|
182
|
+
this.#child.stdin.write(`${JSON.stringify(message)}\n`)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#receive(line: string, handle: AgentRequestHandler): void {
|
|
186
|
+
let message: {
|
|
187
|
+
id?: number | string
|
|
188
|
+
method?: string
|
|
189
|
+
params?: unknown
|
|
190
|
+
result?: unknown
|
|
191
|
+
error?: { code?: number; message?: string }
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
message = JSON.parse(line)
|
|
195
|
+
} catch {
|
|
196
|
+
return // Not a protocol frame: an agent may print a stray line; it carries nothing to act on.
|
|
197
|
+
}
|
|
198
|
+
if (typeof message.method === 'string') {
|
|
199
|
+
if (message.id === undefined) {
|
|
200
|
+
for (const listener of this.#listeners) listener(message.method, message.params)
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
const id = message.id
|
|
204
|
+
Promise.resolve()
|
|
205
|
+
.then(() => handle(message.method as string, message.params))
|
|
206
|
+
.then(
|
|
207
|
+
(result) => this.#answer(id, result),
|
|
208
|
+
(error: unknown) =>
|
|
209
|
+
this.#send({
|
|
210
|
+
jsonrpc: '2.0',
|
|
211
|
+
id,
|
|
212
|
+
error:
|
|
213
|
+
error instanceof AcpError && error.code !== undefined
|
|
214
|
+
? { code: error.code, message: error.message }
|
|
215
|
+
: {
|
|
216
|
+
code: -32603,
|
|
217
|
+
message: error instanceof Error ? error.message : String(error),
|
|
218
|
+
},
|
|
219
|
+
}),
|
|
220
|
+
)
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
const pending = typeof message.id === 'number' ? this.#pending.get(message.id) : undefined
|
|
224
|
+
if (!pending) return
|
|
225
|
+
this.#pending.delete(message.id as number)
|
|
226
|
+
if (message.error)
|
|
227
|
+
pending.reject(
|
|
228
|
+
new AcpError(
|
|
229
|
+
message.error.message ?? 'the agent answered with an error',
|
|
230
|
+
message.error.code,
|
|
231
|
+
),
|
|
232
|
+
)
|
|
233
|
+
else pending.resolve(message.result)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
#answer(id: number | string, result: unknown): void {
|
|
237
|
+
if (!this.#ended) this.#send({ jsonrpc: '2.0', id, result: result ?? null })
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ACP driver (M8, spec 046 T028): any agent that speaks the Agent Client Protocol (ACP v1 over stdio) as a chat driver of the agent panel, started in the app folder.
|
|
3
|
+
* No agent gets code of its own here: an agent is a preset, a command line that starts its ACP server. `dsh` (DeepSeek Harness, `dsh --profile acp`) is the first preset;
|
|
4
|
+
* other ACP agents are added by config.
|
|
5
|
+
*
|
|
6
|
+
* - The drivers are `chat` entries of the panel's `drivers` service (`acp:dsh`, ...). A panel without this plugin lists none; this plugin without a panel is pending.
|
|
7
|
+
* - An agent whose command is not on the machine gets no driver, and the plugin says so in one line (as `driver-pty` does without node-pty).
|
|
8
|
+
* - Every agent process is acquired inside one `ctx.effect`; its disposer ends the process. Unloading or reloading the plugin leaves no agent running.
|
|
9
|
+
* - Development only, like the panel: with `NODE_ENV=production` (or `ACRYL_DOOR=off`) it starts nothing.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync } from 'node:fs'
|
|
12
|
+
import { isAbsolute } from 'node:path'
|
|
13
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
14
|
+
import { onPath } from '../agent-panel/headless.ts'
|
|
15
|
+
import type {} from '../agent-panel/registry.ts'
|
|
16
|
+
import { type AcpPermissions, acpDriver, type OpenAgent } from './chat.ts'
|
|
17
|
+
import { type AcpAgent, AcpConnection } from './connection.ts'
|
|
18
|
+
|
|
19
|
+
export const name = 'driver-acp'
|
|
20
|
+
export const inject = ['drivers', 'appInstance']
|
|
21
|
+
|
|
22
|
+
export type { AcpPermissions } from './chat.ts'
|
|
23
|
+
export { acpDriver } from './chat.ts'
|
|
24
|
+
export { type AcpAgent, AcpConnection, AcpError } from './connection.ts'
|
|
25
|
+
|
|
26
|
+
/** The ACP agents this plugin knows by name: the command line that starts each one's ACP server. */
|
|
27
|
+
export const PRESETS: Readonly<Record<string, AcpAgent>> = {
|
|
28
|
+
dsh: { command: 'dsh', args: ['--profile', 'acp'] },
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DriverAcpConfig {
|
|
32
|
+
/** The agents to offer, by name (the driver is `acp:<name>`). Default: the presets whose command is on the machine. */
|
|
33
|
+
agents?: Record<string, AcpAgent>
|
|
34
|
+
/** The answer to an agent's permission request: its "allow once" or its "reject once" option. Default `reject`. */
|
|
35
|
+
permissions?: AcpPermissions
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function apply(ctx: Context, config: DriverAcpConfig = {}): void {
|
|
39
|
+
if (process.env.NODE_ENV === 'production' || process.env.ACRYL_DOOR === 'off') return
|
|
40
|
+
const root = ctx.appInstance.root
|
|
41
|
+
const open: OpenAgent = (agent, handle) => {
|
|
42
|
+
let connection!: AcpConnection
|
|
43
|
+
const close = ctx.effect(() => {
|
|
44
|
+
connection = new AcpConnection(agent, root, handle)
|
|
45
|
+
return () => connection.close()
|
|
46
|
+
}) as () => Promise<void>
|
|
47
|
+
// An agent that ended on its own gives its effect back at once instead of at unload.
|
|
48
|
+
void connection.ended.then(() => close())
|
|
49
|
+
return { connection, close }
|
|
50
|
+
}
|
|
51
|
+
for (const [agentName, agent] of Object.entries(config.agents ?? PRESETS)) {
|
|
52
|
+
if (!(isAbsolute(agent.command) ? existsSync(agent.command) : onPath(agent.command))) {
|
|
53
|
+
console.warn(`driver-acp: no acp:${agentName} driver, "${agent.command}" is not on the PATH`)
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
const driver = acpDriver({
|
|
57
|
+
name: `acp:${agentName}`,
|
|
58
|
+
agent,
|
|
59
|
+
cwd: root,
|
|
60
|
+
permissions: config.permissions ?? 'reject',
|
|
61
|
+
open,
|
|
62
|
+
})
|
|
63
|
+
ctx.effect(() => ctx.drivers.register(driver))
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "driver-acp",
|
|
3
|
+
"description": "Any agent that speaks the Agent Client Protocol (ACP) as a chat in the agent panel, started in the app folder: DeepSeek Harness (dsh --profile acp) is the first preset, other ACP agents are added by config. Development only; needs the agent panel; opt-in with acrylblends add driver-acp.",
|
|
4
|
+
"requires": ["agent-panel", "instance"],
|
|
5
|
+
"inject": ["drivers", "appInstance"],
|
|
6
|
+
"provides": [],
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@deepseek-ai/cordis": "4.0.4"
|
|
9
|
+
},
|
|
10
|
+
"mount": "register it in src/app.ts ('driver-acp': driverAcp) and add a row `- id: driver-acp` / `name: driver-acp` to blend.yaml AFTER the agent-panel row; with dsh on the PATH the panel offers acp:dsh",
|
|
11
|
+
"gitignore": []
|
|
12
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"written": "from the specification, not ported: connection.ts and chat.ts are this repository's own code",
|
|
3
|
+
"protocol": {
|
|
4
|
+
"name": "Agent Client Protocol",
|
|
5
|
+
"version": 1,
|
|
6
|
+
"site": "https://agentclientprotocol.com",
|
|
7
|
+
"transport": "stdio, newline-delimited JSON-RPC 2.0"
|
|
8
|
+
},
|
|
9
|
+
"schema": {
|
|
10
|
+
"file": "test/acp-v1/schema.json",
|
|
11
|
+
"from": "npm @agentclientprotocol/sdk@2.0.2, schema/v1/schema.json (the stable v1 schema, not schema.unstable.json)",
|
|
12
|
+
"repository": "https://github.com/agentclientprotocol/typescript-sdk",
|
|
13
|
+
"license": "Apache-2.0, Copyright 2025 Zed Industries, Inc. and contributors (test/acp-v1/LICENSE)",
|
|
14
|
+
"sha256": "4bcd1d2051ff7ed379bd9b234fb9b09c5ab9245478698e9fab805bd9b52a33a6",
|
|
15
|
+
"modified": false,
|
|
16
|
+
"used by": "test/conformance.test.ts; the schema is a test fixture and is not copied into apps"
|
|
17
|
+
},
|
|
18
|
+
"checked against": "dsh (DeepSeek Harness) 0.2.1-rc.2, dsh --profile acp, 2026-10-28: scripts/verify-agent-panel.mjs --driver acp:dsh",
|
|
19
|
+
"update": "take schema/v1/schema.json from a newer @agentclientprotocol/sdk, update sha256 and the version here, and run test/conformance.test.ts"
|
|
20
|
+
}
|
|
@@ -16,6 +16,7 @@ import { streamSSE } from '../acryl-router/src/helpers/index.ts'
|
|
|
16
16
|
import { Acryl } from '../acryl-router/src/index.ts'
|
|
17
17
|
import type { TerminalDriver, TerminalSession } from '../agent-panel/drivers.ts'
|
|
18
18
|
import { onPath } from '../agent-panel/headless.ts'
|
|
19
|
+
import type {} from '../agent-panel/registry.ts'
|
|
19
20
|
import { loadNodePty, type PtySpawn } from './spawn.ts'
|
|
20
21
|
import { terminalDriver } from './terminal.ts'
|
|
21
22
|
|