zooid 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +41 -3
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-O6E4CDTV.js → chunk-OKGNOROJ.js} +19 -6
- package/dist/{chunk-O6E4CDTV.js.map → chunk-OKGNOROJ.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/web/assets/{index-1pU3tgkr.js → index-BqOX0Pv4.js} +67 -67
- package/dist/web/assets/{index-CXOPATwH.js → index-Dc8BJYf_.js} +1 -1
- package/dist/web/assets/{reaction-picker-emoji-DrlHZ5qt.js → reaction-picker-emoji-xD2HkULN.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +8 -8
- package/src/commands/dev.ts +5 -1
- package/src/services/tuwunel.ts +68 -1
package/dist/web/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
7
7
|
<title>Zoon</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-BqOX0Pv4.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-C-ZtBp7U.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zooid",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "Open-source tool for defining and running AI agents alongside you and your team. Any model, any CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,13 +37,13 @@
|
|
|
37
37
|
"marked": "^18.0.4",
|
|
38
38
|
"sanitize-html": "^2.17.4",
|
|
39
39
|
"yaml": "^2.5.0",
|
|
40
|
-
"@zooid/
|
|
41
|
-
"@zooid/
|
|
42
|
-
"@zooid/
|
|
43
|
-
"@zooid/core": "^0.7.
|
|
44
|
-
"@zooid/
|
|
45
|
-
"@zooid/runtime-local": "^0.7.
|
|
46
|
-
"@zooid/transport-matrix": "^0.7.
|
|
40
|
+
"@zooid/context-mcp": "^0.7.4",
|
|
41
|
+
"@zooid/acp-client": "^0.7.4",
|
|
42
|
+
"@zooid/transport-http": "^0.7.4",
|
|
43
|
+
"@zooid/core": "^0.7.4",
|
|
44
|
+
"@zooid/runtime-docker": "^0.7.4",
|
|
45
|
+
"@zooid/runtime-local": "^0.7.4",
|
|
46
|
+
"@zooid/transport-matrix": "^0.7.4"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@agentclientprotocol/sdk": "^0.21.0",
|
package/src/commands/dev.ts
CHANGED
|
@@ -161,7 +161,11 @@ export async function runDev(flags: DevFlags): Promise<DevHandle> {
|
|
|
161
161
|
title: 'Wait for Tuwunel /_matrix/client/versions',
|
|
162
162
|
task: async () => {
|
|
163
163
|
if (!ctx.svc) throw new Error('service not started')
|
|
164
|
-
|
|
164
|
+
// 180s covers Docker Desktop's first-run bind-mount setup on macOS
|
|
165
|
+
// (which can sit in `created` for 30–90s before transitioning to
|
|
166
|
+
// `running`) plus Tuwunel's RocksDB init. Repeated runs against an
|
|
167
|
+
// existing DB are far quicker.
|
|
168
|
+
await ctx.svc.waitHealthy({ url: homeserver, timeoutMs: 180_000 })
|
|
165
169
|
},
|
|
166
170
|
},
|
|
167
171
|
{
|
package/src/services/tuwunel.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { spawn, type ChildProcess } from 'node:child_process'
|
|
2
|
+
import { execFile } from 'node:child_process'
|
|
3
|
+
import { promisify } from 'node:util'
|
|
2
4
|
import type { Paths } from '../bootstrap/paths.js'
|
|
3
5
|
|
|
6
|
+
const execFileAsync = promisify(execFile)
|
|
7
|
+
|
|
4
8
|
export interface TuwunelOpts {
|
|
5
9
|
name: string
|
|
6
10
|
image?: string
|
|
@@ -61,7 +65,32 @@ export class TuwunelService {
|
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
async waitHealthy(opts: { url: string; timeoutMs: number }): Promise<void> {
|
|
68
|
+
// Two-phase wait: first the container has to transition from `created` to
|
|
69
|
+
// `running` (Docker Desktop on macOS can take 30–90s on first-run bind-mount
|
|
70
|
+
// setup), then Tuwunel has to open its DB and start serving HTTP. Polling
|
|
71
|
+
// only HTTP loses both signals: a long create looks identical to a long
|
|
72
|
+
// boot, and a crashed container looks identical to a slow one.
|
|
64
73
|
const deadline = Date.now() + opts.timeoutMs
|
|
74
|
+
|
|
75
|
+
// Phase 1: wait for the container to be running. Surface the actual
|
|
76
|
+
// docker error if it transitions to `exited` instead — that's the
|
|
77
|
+
// case where today we just hang for 60s and say "did not become healthy"
|
|
78
|
+
// with no clue what really broke.
|
|
79
|
+
while (Date.now() < deadline) {
|
|
80
|
+
const state = await this.inspectState().catch(() => null)
|
|
81
|
+
if (state?.status === 'running') break
|
|
82
|
+
if (state?.status === 'exited') {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Tuwunel container exited before serving HTTP ` +
|
|
85
|
+
`(exit=${state.exitCode}${state.error ? `, error=${state.error}` : ''}). ` +
|
|
86
|
+
`Check the engine logs (\`${this.opts.engine} logs ${this.opts.name}\`).`,
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Phase 2: container is running (or we ran out of time). Poll the HTTP
|
|
93
|
+
// endpoint until either it answers or the deadline passes.
|
|
65
94
|
while (Date.now() < deadline) {
|
|
66
95
|
try {
|
|
67
96
|
const r = await fetch(`${opts.url}/_matrix/client/versions`)
|
|
@@ -71,10 +100,48 @@ export class TuwunelService {
|
|
|
71
100
|
}
|
|
72
101
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
73
102
|
}
|
|
103
|
+
|
|
104
|
+
// Last-ditch diagnostic before throwing the generic timeout — surface the
|
|
105
|
+
// current container state so the user knows whether to blame slow start,
|
|
106
|
+
// slow boot, or a misconfiguration.
|
|
107
|
+
const finalState = await this.inspectState().catch(() => null)
|
|
108
|
+
const detail = finalState
|
|
109
|
+
? ` (container status=${finalState.status}${
|
|
110
|
+
finalState.exitCode !== undefined ? `, exit=${finalState.exitCode}` : ''
|
|
111
|
+
})`
|
|
112
|
+
: ''
|
|
74
113
|
throw new Error(
|
|
75
|
-
`Tuwunel did not become healthy at ${opts.url} in ${opts.timeoutMs}ms`,
|
|
114
|
+
`Tuwunel did not become healthy at ${opts.url} in ${opts.timeoutMs}ms${detail}`,
|
|
76
115
|
)
|
|
77
116
|
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Read the engine's view of the container. Returns null if the container
|
|
120
|
+
* isn't known to the engine (e.g. before spawn completed or after `--rm`).
|
|
121
|
+
*/
|
|
122
|
+
private async inspectState(): Promise<{
|
|
123
|
+
status: string
|
|
124
|
+
exitCode?: number
|
|
125
|
+
error?: string
|
|
126
|
+
} | null> {
|
|
127
|
+
try {
|
|
128
|
+
const { stdout } = await execFileAsync(this.opts.engine, [
|
|
129
|
+
'inspect',
|
|
130
|
+
this.opts.name,
|
|
131
|
+
'--format',
|
|
132
|
+
'{{.State.Status}}|{{.State.ExitCode}}|{{.State.Error}}',
|
|
133
|
+
])
|
|
134
|
+
const [status, exitCodeRaw, error] = stdout.trim().split('|')
|
|
135
|
+
const exitCode = exitCodeRaw && exitCodeRaw !== '<no value>' ? Number(exitCodeRaw) : undefined
|
|
136
|
+
return {
|
|
137
|
+
status: status ?? 'unknown',
|
|
138
|
+
...(Number.isFinite(exitCode) ? { exitCode: exitCode as number } : {}),
|
|
139
|
+
...(error ? { error } : {}),
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
144
|
+
}
|
|
78
145
|
}
|
|
79
146
|
|
|
80
147
|
function execEngine(engine: string, args: string[]): Promise<void> {
|