typeclaw 0.1.1 → 0.1.3
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 +16 -12
- package/auth.schema.json +238 -7
- package/package.json +1 -1
- package/secrets.schema.json +238 -7
- package/src/agent/auth.ts +19 -38
- package/src/agent/doctor.ts +173 -0
- package/src/agent/subagents.ts +24 -2
- package/src/agent/tools/channel-fetch-attachment.ts +6 -0
- package/src/agent/tools/channel-history.ts +10 -1
- package/src/agent/tools/channel-log.ts +32 -0
- package/src/agent/tools/channel-reply.ts +18 -1
- package/src/agent/tools/channel-send.ts +13 -1
- package/src/bundled-plugins/backup/README.md +81 -0
- package/src/bundled-plugins/backup/index.ts +209 -0
- package/src/bundled-plugins/backup/runner.ts +231 -0
- package/src/bundled-plugins/backup/subagents.ts +200 -0
- package/src/bundled-plugins/memory/index.ts +42 -1
- package/src/bundled-plugins/tool-result-cap/README.md +67 -0
- package/src/bundled-plugins/tool-result-cap/cap-result.ts +56 -0
- package/src/bundled-plugins/tool-result-cap/index.ts +51 -0
- package/src/channels/adapters/kakaotalk.ts +25 -16
- package/src/channels/manager.ts +47 -38
- package/src/channels/router.ts +29 -0
- package/src/cli/channel.ts +3 -3
- package/src/cli/compose.ts +92 -1
- package/src/cli/doctor.ts +100 -0
- package/src/cli/index.ts +4 -0
- package/src/cli/init.ts +2 -1
- package/src/cli/ui.ts +11 -0
- package/src/compose/doctor.ts +141 -0
- package/src/compose/index.ts +8 -0
- package/src/compose/logs.ts +32 -19
- package/src/config/config.ts +31 -0
- package/src/container/log-colors.ts +75 -0
- package/src/container/log-timestamps.ts +84 -0
- package/src/container/logs.ts +71 -5
- package/src/container/start.ts +113 -9
- package/src/cron/consumer.ts +29 -7
- package/src/doctor/checks.ts +426 -0
- package/src/doctor/commit.ts +71 -0
- package/src/doctor/index.ts +287 -0
- package/src/doctor/plugin-bridge.ts +147 -0
- package/src/doctor/report.ts +142 -0
- package/src/doctor/types.ts +87 -0
- package/src/hostd/daemon.ts +28 -3
- package/src/hostd/protocol.ts +7 -0
- package/src/init/auto-upgrade.ts +368 -0
- package/src/init/cli-version.ts +81 -0
- package/src/init/dockerfile.ts +234 -25
- package/src/init/index.ts +141 -87
- package/src/init/kakaotalk-auth.ts +9 -3
- package/src/init/run-bun-install.ts +34 -0
- package/src/plugin/hooks.ts +32 -0
- package/src/plugin/index.ts +7 -0
- package/src/plugin/manager.ts +2 -0
- package/src/plugin/registry.ts +32 -3
- package/src/plugin/types.ts +65 -0
- package/src/run/bundled-plugins.ts +15 -0
- package/src/run/index.ts +19 -5
- package/src/secrets/defaults.ts +67 -0
- package/src/secrets/hydrate.ts +99 -0
- package/src/secrets/index.ts +6 -12
- package/src/secrets/kakao-store.ts +129 -0
- package/src/secrets/migrate-kakaotalk.ts +82 -0
- package/src/secrets/migrate.ts +5 -4
- package/src/secrets/resolve.ts +57 -0
- package/src/secrets/schema.ts +162 -42
- package/src/secrets/storage.ts +253 -47
- package/src/server/index.ts +103 -5
- package/src/shared/index.ts +3 -0
- package/src/shared/protocol.ts +22 -0
- package/src/skills/typeclaw-config/SKILL.md +48 -9
- package/typeclaw.schema.json +84 -0
- package/src/secrets/env.ts +0 -43
package/src/init/dockerfile.ts
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
import type { DockerfileConfig, DockerfileFeatureToggle } from '@/config/config'
|
|
2
2
|
|
|
3
|
+
import { GHCR_BASE_IMAGE_REPO } from './cli-version'
|
|
4
|
+
|
|
3
5
|
export const DOCKERFILE = 'Dockerfile'
|
|
4
6
|
|
|
7
|
+
export type BuildDockerfileOptions = {
|
|
8
|
+
// Null or omitted = emit the full inline heavy stack (dev mode, tests).
|
|
9
|
+
baseImageVersion?: string | null
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
// Apt packages that EVERY image must have — git for the agent runtime,
|
|
6
13
|
// curl/ca-certificates/gnupg for HTTPS and key fetches that downstream layers
|
|
7
|
-
// (e.g. the gh keyring) depend on.
|
|
8
|
-
//
|
|
9
|
-
|
|
14
|
+
// (e.g. the gh keyring) depend on. `iptables` and `util-linux` back the
|
|
15
|
+
// network egress entrypoint shim, installed unconditionally so that flipping
|
|
16
|
+
// `typeclaw.json#network.blockInternal` is a runtime toggle (re-run
|
|
17
|
+
// `typeclaw restart`) and not an image rebuild.
|
|
18
|
+
//
|
|
19
|
+
// On Debian trixie the single `iptables` package ships both the IPv4 nft
|
|
20
|
+
// frontend (`iptables-nft`, available as `iptables` through
|
|
21
|
+
// update-alternatives) and the IPv6 nft frontend (`ip6tables-nft`, available
|
|
22
|
+
// as `ip6tables`). The standalone `iptables-nft`/`ip6tables-nft` package
|
|
23
|
+
// names do NOT exist on trixie — `apt install iptables-nft` fails with
|
|
24
|
+
// "Unable to locate package". The shim invokes `iptables` and `ip6tables`
|
|
25
|
+
// which alternatives resolves to the nft variants.
|
|
26
|
+
//
|
|
27
|
+
// `util-linux` carries `setpriv`, which the shim uses to drop CAP_NET_ADMIN
|
|
28
|
+
// from the bounding set before exec'ing the agent. Listed first in the
|
|
29
|
+
// apt-get install line so the package set is self-documenting at a glance.
|
|
30
|
+
const BASELINE_APT_PACKAGES = ['git', 'ca-certificates', 'curl', 'gnupg', 'iptables', 'util-linux'] as const
|
|
10
31
|
|
|
11
32
|
// curl-impersonate is the only currently-working way to query DuckDuckGo from
|
|
12
33
|
// a non-browser client on residential IPs in 2026. DDG fingerprints incoming
|
|
@@ -35,6 +56,134 @@ export const CURL_IMPERSONATE_SHA256_ARM64 = '6766bc67fd3e8e2313875f32b36b5a3fab
|
|
|
35
56
|
// the impersonation to whatever `curl_chrome` resolves to.
|
|
36
57
|
export const CURL_IMPERSONATE_PROFILE = 'chrome136'
|
|
37
58
|
|
|
59
|
+
export const TYPECLAW_ENTRYPOINT_PATH = '/usr/local/bin/typeclaw-entrypoint'
|
|
60
|
+
|
|
61
|
+
// IPv4 networks the container is forbidden to egress to when
|
|
62
|
+
// `network.blockInternal` is true. Loopback (127/8) is NOT here — loopback
|
|
63
|
+
// traffic uses the `lo` interface, which the shim's first ACCEPT rule
|
|
64
|
+
// short-circuits. The agent inside the container needs loopback to dogfood
|
|
65
|
+
// its own `bun run dev` server. RFC1918 (10/8, 172.16/12, 192.168/16) covers
|
|
66
|
+
// router admin panels and home/office LANs. 169.254/16 covers cloud
|
|
67
|
+
// metadata (169.254.169.254 IMDS, 169.254.170.2 ECS task role) and Windows
|
|
68
|
+
// APIPA. 100.64/10 is CGNAT. 224/4 multicast and 240/4 reserved are belt-
|
|
69
|
+
// and-suspenders against creative exfil targets. host.docker.internal (in
|
|
70
|
+
// 172.16/12 on Docker Desktop/Linux) is re-allowed by the shim at runtime
|
|
71
|
+
// via getent so the agent's `restart` tool can still reach hostd.
|
|
72
|
+
export const NETWORK_BLOCK_IPV4_NETS = [
|
|
73
|
+
'10.0.0.0/8',
|
|
74
|
+
'172.16.0.0/12',
|
|
75
|
+
'192.168.0.0/16',
|
|
76
|
+
'169.254.0.0/16',
|
|
77
|
+
'100.64.0.0/10',
|
|
78
|
+
'224.0.0.0/4',
|
|
79
|
+
'240.0.0.0/4',
|
|
80
|
+
] as const
|
|
81
|
+
|
|
82
|
+
// IPv6 mirrors of the IPv4 block list. fc00::/7 is unique-local (the IPv6
|
|
83
|
+
// equivalent of RFC1918), fe80::/10 is link-local (incl. SLAAC + IPv6 cloud
|
|
84
|
+
// metadata in fd00:ec2::/64 which fits inside fc00::/7), ff00::/8 is
|
|
85
|
+
// multicast, ::ffff:0:0/96 is IPv4-mapped IPv6 (an attacker could otherwise
|
|
86
|
+
// reach 192.168.x.x via [::ffff:192.168.0.1]).
|
|
87
|
+
export const NETWORK_BLOCK_IPV6_NETS = ['fc00::/7', 'fe80::/10', 'ff00::/8', '::ffff:0:0/96'] as const
|
|
88
|
+
|
|
89
|
+
// Renders the shell script that runs as PID 1 inside the container. Two
|
|
90
|
+
// modes, picked at boot time from `$TYPECLAW_NETWORK_BLOCK_INTERNAL`:
|
|
91
|
+
//
|
|
92
|
+
// off (env unset or != "1"): no rules installed, no setpriv. Just exec
|
|
93
|
+
// `bun run typeclaw "$@"`. Identical observable behavior to a container
|
|
94
|
+
// without this feature. This is the opt-out path for users who set
|
|
95
|
+
// `network.blockInternal: false` in their `typeclaw.json`.
|
|
96
|
+
//
|
|
97
|
+
// on (default, env = "1" via `network.blockInternal: true`): walks IPv4 +
|
|
98
|
+
// IPv6 block lists and installs
|
|
99
|
+
// REJECT rules in the OUTPUT chain. Loopback (`-o lo`) is ACCEPT'd first
|
|
100
|
+
// so dev-server dogfooding still works. The hostd HTTP control port on
|
|
101
|
+
// `host.docker.internal` is re-allowed at runtime — narrowly, single
|
|
102
|
+
// TCP destport, only when hostd is configured — so the agent's `restart`
|
|
103
|
+
// tool can still reach the daemon. The shim then drops CAP_NET_ADMIN
|
|
104
|
+
// from the bounding set AND from the inheritable + ambient sets via
|
|
105
|
+
// setpriv before exec'ing the agent. Bounding set is the hard ceiling
|
|
106
|
+
// enforced by execve; inheritable + ambient are cleared defensively to
|
|
107
|
+
// match setpriv(1)'s explicit warning about not dropping the bounding
|
|
108
|
+
// set alone.
|
|
109
|
+
//
|
|
110
|
+
// Carve-out is intentionally narrow: ACCEPT only `tcp --dport <hostd-port>`
|
|
111
|
+
// to the host gateway, never the gateway IP wholesale. Without the dport
|
|
112
|
+
// scope, a compromised agent could reach any host service via
|
|
113
|
+
// `host.docker.internal:22` (SSH), `:53` (DNS), `:5432` (postgres), etc.
|
|
114
|
+
// The gateway IP itself sits inside `172.16.0.0/12`, which the IPv4 reject
|
|
115
|
+
// rules below DROP — the narrow ACCEPT here is the only path through.
|
|
116
|
+
// When hostd is not configured (`TYPECLAW_HOSTD_URL` unset or unparseable),
|
|
117
|
+
// nothing is ACCEPT'd: the agent loses self-restart capability but the
|
|
118
|
+
// rest of the egress filter still works.
|
|
119
|
+
//
|
|
120
|
+
// IPv4-only carve-out uses `getent ahostsv4` to force the resolver into
|
|
121
|
+
// the A-record path. Without this, `getent hosts` would return whichever
|
|
122
|
+
// family the resolver prefers, and on systems that prefer AAAA we'd feed
|
|
123
|
+
// a v6 address to `iptables` and crash under `set -e`. host.docker.internal
|
|
124
|
+
// resolves to a bridge gateway that is IPv4-only on every Docker runtime
|
|
125
|
+
// we support (Docker Desktop, OrbStack, Docker on Linux with the
|
|
126
|
+
// `--add-host host.docker.internal:host-gateway` flag typeclaw injects).
|
|
127
|
+
//
|
|
128
|
+
// REJECT (not DROP) so the agent fails fast with an ICMP unreachable
|
|
129
|
+
// instead of hanging on a 30-second connect timeout — much friendlier
|
|
130
|
+
// debug UX and identical security posture.
|
|
131
|
+
//
|
|
132
|
+
// `set -eu` propagates rule-install failures up to PID 1 exit, which kills
|
|
133
|
+
// the container. Failing closed is the right thing: an unenforced
|
|
134
|
+
// blockInternal=true is worse than blockInternal=false.
|
|
135
|
+
export function buildEntrypointShim(): string {
|
|
136
|
+
const ipv4Rules = NETWORK_BLOCK_IPV4_NETS.map(
|
|
137
|
+
(net) => `iptables -A OUTPUT -d ${net} -j REJECT --reject-with icmp-port-unreachable`,
|
|
138
|
+
)
|
|
139
|
+
const ipv6Rules = NETWORK_BLOCK_IPV6_NETS.map(
|
|
140
|
+
(net) => `ip6tables -A OUTPUT -d ${net} -j REJECT --reject-with icmp6-port-unreachable`,
|
|
141
|
+
)
|
|
142
|
+
return `#!/bin/sh
|
|
143
|
+
# AUTOGENERATED by typeclaw — do not edit.
|
|
144
|
+
# Source: src/init/dockerfile.ts \`buildEntrypointShim()\`.
|
|
145
|
+
set -eu
|
|
146
|
+
|
|
147
|
+
if [ "\${TYPECLAW_NETWORK_BLOCK_INTERNAL:-0}" != "1" ]; then
|
|
148
|
+
exec bun run typeclaw "$@"
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
iptables -A OUTPUT -o lo -j ACCEPT
|
|
152
|
+
|
|
153
|
+
# Hostd HTTP control carve-out: narrow ACCEPT, scoped to one TCP port on
|
|
154
|
+
# the host gateway. Skipped silently when hostd is not configured.
|
|
155
|
+
hostd_port=""
|
|
156
|
+
if [ -n "\${TYPECLAW_HOSTD_URL:-}" ]; then
|
|
157
|
+
hostd_port="$(printf '%s' "$TYPECLAW_HOSTD_URL" | sed -n 's#^https\\{0,1\\}://[^/:]\\{1,\\}:\\([0-9]\\{1,5\\}\\).*#\\1#p')"
|
|
158
|
+
fi
|
|
159
|
+
if [ -n "\${hostd_port:-}" ]; then
|
|
160
|
+
host_gw_ip="$(getent ahostsv4 host.docker.internal 2>/dev/null | awk '{print $1; exit}')"
|
|
161
|
+
if [ -n "\${host_gw_ip:-}" ]; then
|
|
162
|
+
iptables -A OUTPUT -p tcp -d "$host_gw_ip" --dport "$hostd_port" -j ACCEPT
|
|
163
|
+
fi
|
|
164
|
+
fi
|
|
165
|
+
${ipv4Rules.join('\n')}
|
|
166
|
+
|
|
167
|
+
ip6tables -A OUTPUT -o lo -j ACCEPT
|
|
168
|
+
${ipv6Rules.join('\n')}
|
|
169
|
+
|
|
170
|
+
exec setpriv --bounding-set -net_admin --inh-caps -net_admin --ambient-caps -net_admin -- bun run typeclaw "$@"
|
|
171
|
+
`
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Layer 6: install the network-egress entrypoint shim. Content is base64-
|
|
175
|
+
// encoded inline so the Dockerfile is fully self-contained — no second file
|
|
176
|
+
// in the build context, no COPY, no chicken-and-egg between init and start.
|
|
177
|
+
// Layer placement is intentionally late: shim source changes invalidate
|
|
178
|
+
// only this small layer (~1KB image impact), keeping Chrome and apt cached.
|
|
179
|
+
function renderEntrypointShimLayer(): string {
|
|
180
|
+
const encoded = Buffer.from(buildEntrypointShim(), 'utf8').toString('base64')
|
|
181
|
+
return `# Layer 6 (small, changes with the egress shim): install /usr/local/bin/typeclaw-entrypoint.
|
|
182
|
+
# The shim is a no-op unless \`network.blockInternal\` is true at runtime.
|
|
183
|
+
RUN echo "${encoded}" | base64 -d > ${TYPECLAW_ENTRYPOINT_PATH} \\
|
|
184
|
+
&& chmod +x ${TYPECLAW_ENTRYPOINT_PATH}`
|
|
185
|
+
}
|
|
186
|
+
|
|
38
187
|
// Shared-library runtime deps Chrome for Testing needs to launch on amd64
|
|
39
188
|
// Debian trixie (base of `oven/bun:1-slim`). `agent-browser install
|
|
40
189
|
// --with-deps` (v0.27.0) is supposed to install these but silently no-ops:
|
|
@@ -90,10 +239,19 @@ const APT_FEATURES: Record<'ffmpeg' | 'gh' | 'tmux' | 'python', AptFeature> = {
|
|
|
90
239
|
},
|
|
91
240
|
}
|
|
92
241
|
|
|
93
|
-
export function buildDockerfile(
|
|
94
|
-
|
|
242
|
+
export function buildDockerfile(
|
|
243
|
+
config: DockerfileConfig = defaultConfig(),
|
|
244
|
+
options: BuildDockerfileOptions = {},
|
|
245
|
+
): string {
|
|
246
|
+
const toggleAptArgs = collectToggleAptArgs(config)
|
|
95
247
|
const ghKeyringLayer = renderGhKeyringLayer(config.gh)
|
|
96
248
|
const customLines = renderCustomDockerfileLines(config.append)
|
|
249
|
+
const baseImageVersion = options.baseImageVersion ?? null
|
|
250
|
+
|
|
251
|
+
const fromAndHeavyLayers =
|
|
252
|
+
baseImageVersion !== null
|
|
253
|
+
? renderVersionedHead(baseImageVersion, ghKeyringLayer, toggleAptArgs)
|
|
254
|
+
: renderInlineHead(ghKeyringLayer, toggleAptArgs)
|
|
97
255
|
|
|
98
256
|
return `${BUILDKIT_HEADER}
|
|
99
257
|
# AUTOGENERATED by typeclaw — do not edit.
|
|
@@ -101,7 +259,60 @@ export function buildDockerfile(config: DockerfileConfig = defaultConfig()): str
|
|
|
101
259
|
# in the typeclaw repo. Local edits will be overwritten (and committed away if
|
|
102
260
|
# the working tree is dirty). To change the template, edit dockerfile.ts there.
|
|
103
261
|
|
|
104
|
-
${
|
|
262
|
+
${fromAndHeavyLayers}
|
|
263
|
+
# The agent folder (including node_modules) is bind-mounted at runtime by
|
|
264
|
+
# \`typeclaw start\`, so we do not COPY or install here. This keeps the image
|
|
265
|
+
# tiny and lets edits on the host take effect without rebuilds.
|
|
266
|
+
|
|
267
|
+
ENV NODE_ENV=production
|
|
268
|
+
|
|
269
|
+
# Keep agent-messenger's fallback config dir inside workspace/ for any future
|
|
270
|
+
# SDK fallback paths. TypeClaw's KakaoTalk adapter does not write there:
|
|
271
|
+
# credentials live in secrets.json#channels.kakaotalk and container writes go
|
|
272
|
+
# through hostd's secrets-patch RPC.
|
|
273
|
+
ENV AGENT_MESSENGER_CONFIG_DIR=/agent/workspace/.agent-messenger
|
|
274
|
+
|
|
275
|
+
${customLines}ENTRYPOINT ["${TYPECLAW_ENTRYPOINT_PATH}"]
|
|
276
|
+
CMD ["run"]
|
|
277
|
+
`
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// FROMs the prebuilt typeclaw-base image at the pinned version. Heavy
|
|
281
|
+
// layers (apt baseline, Chrome runtime libs, curl-impersonate, agent-browser,
|
|
282
|
+
// Chrome for Testing) are already in that image, so the per-agent head only
|
|
283
|
+
// re-runs the toggle apt install and (optionally) the gh keyring bootstrap.
|
|
284
|
+
//
|
|
285
|
+
// The entrypoint shim is ALSO re-emitted here, even though the base image
|
|
286
|
+
// already carries it. Two reasons: (1) older base images published before
|
|
287
|
+
// the shim landed (or before a shim source edit) don't have the up-to-date
|
|
288
|
+
// binary at TYPECLAW_ENTRYPOINT_PATH, and the per-agent ENTRYPOINT line
|
|
289
|
+
// would crash on startup with `stat: no such file or directory`. Re-emitting
|
|
290
|
+
// is ~1KB of image and keeps the contract local: whatever per-agent
|
|
291
|
+
// Dockerfile we emit guarantees the shim path exists, regardless of which
|
|
292
|
+
// base-image vintage we FROM. (2) Edits to `buildEntrypointShim()` ship via
|
|
293
|
+
// npm + `typeclaw start --build` immediately, instead of being blocked on a
|
|
294
|
+
// fresh base-image release. The base image's copy is harmlessly overwritten
|
|
295
|
+
// by this RUN — same path, same chmod.
|
|
296
|
+
function renderVersionedHead(baseImageVersion: string, ghKeyringLayer: string, toggleAptArgs: string[]): string {
|
|
297
|
+
const toggleAptLayer = toggleAptArgs.length === 0 ? '' : `${renderToggleAptInstallLayer(toggleAptArgs)}\n\n`
|
|
298
|
+
return `FROM ${GHCR_BASE_IMAGE_REPO}:${baseImageVersion}
|
|
299
|
+
|
|
300
|
+
WORKDIR /agent
|
|
301
|
+
|
|
302
|
+
ARG TARGETARCH
|
|
303
|
+
|
|
304
|
+
${ghKeyringLayer}${toggleAptLayer}${renderEntrypointShimLayer()}
|
|
305
|
+
|
|
306
|
+
`
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// FROMs oven/bun:1-slim and rebuilds the full heavy stack inline. Used by
|
|
310
|
+
// dev-mode runs (typeclaw installed via file: / link: spec) where the
|
|
311
|
+
// matching :version GHCR tag does not yet exist, and by the test suite to
|
|
312
|
+
// keep coverage of the full-stack layers independent of GHCR availability.
|
|
313
|
+
function renderInlineHead(ghKeyringLayer: string, toggleAptArgs: string[]): string {
|
|
314
|
+
const baselineAndToggleArgs = [...BASELINE_APT_PACKAGES, ...toggleAptArgs]
|
|
315
|
+
return `${FROM_AND_WORKDIR}
|
|
105
316
|
|
|
106
317
|
# Layers are ordered most-stable first to maximize Docker layer cache hits on
|
|
107
318
|
# rebuilds. Anything that pulls from npm (volatile) sits below anything that
|
|
@@ -125,7 +336,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \\
|
|
|
125
336
|
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \\
|
|
126
337
|
apt-get update \\
|
|
127
338
|
&& apt-get install -y --no-install-recommends \\
|
|
128
|
-
${
|
|
339
|
+
${baselineAndToggleArgs.join(' ')} \\
|
|
129
340
|
&& if [ "$TARGETARCH" = "arm64" ]; then \\
|
|
130
341
|
apt-get install -y --no-install-recommends chromium; \\
|
|
131
342
|
else \\
|
|
@@ -141,26 +352,22 @@ ${LAYER_4_AGENT_BROWSER_INSTALL}
|
|
|
141
352
|
|
|
142
353
|
${LAYER_5_CHROME_FOR_TESTING}
|
|
143
354
|
|
|
144
|
-
|
|
145
|
-
# \`typeclaw start\`, so we do not COPY or install here. This keeps the image
|
|
146
|
-
# tiny and lets edits on the host take effect without rebuilds.
|
|
147
|
-
|
|
148
|
-
ENV NODE_ENV=production
|
|
355
|
+
${renderEntrypointShimLayer()}
|
|
149
356
|
|
|
150
|
-
# Pin agent-messenger's config dir into the agent's workspace/ so KakaoTalk
|
|
151
|
-
# (and any future agent-messenger-backed channel) reads/writes credentials
|
|
152
|
-
# inside the bind-mounted agent folder. Without this, the SDK would default
|
|
153
|
-
# to /root/.config/agent-messenger inside the container, which doesn't
|
|
154
|
-
# survive container restarts and isn't visible from the host. The agent
|
|
155
|
-
# folder's bind-mount maps /agent → host's agent dir, so the credentials
|
|
156
|
-
# end up at <agentDir>/workspace/.agent-messenger/ on the host.
|
|
157
|
-
ENV AGENT_MESSENGER_CONFIG_DIR=/agent/workspace/.agent-messenger
|
|
158
|
-
|
|
159
|
-
${customLines}ENTRYPOINT ["bun", "run", "typeclaw"]
|
|
160
|
-
CMD ["run"]
|
|
161
357
|
`
|
|
162
358
|
}
|
|
163
359
|
|
|
360
|
+
function renderToggleAptInstallLayer(toggleAptArgs: string[]): string {
|
|
361
|
+
return `# Layer 1 (toggle apt install): packages requested via typeclaw.json
|
|
362
|
+
# #dockerfile toggles. Baseline + Chrome runtime libs are already in the
|
|
363
|
+
# base image; this layer only adds gh/tmux/python/ffmpeg if enabled.
|
|
364
|
+
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \\
|
|
365
|
+
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \\
|
|
366
|
+
apt-get update \\
|
|
367
|
+
&& apt-get install -y --no-install-recommends \\
|
|
368
|
+
${toggleAptArgs.join(' ')}`
|
|
369
|
+
}
|
|
370
|
+
|
|
164
371
|
// Recipe for the prebuilt typeclaw-base image published to
|
|
165
372
|
// ghcr.io/typeclaw/typeclaw-base by .github/workflows/base-image.yml. Built
|
|
166
373
|
// from the same constants and layer templates as buildDockerfile() so the
|
|
@@ -205,6 +412,8 @@ ${LAYER_3_AGENT_BROWSER_ARM64_CONFIG}
|
|
|
205
412
|
${LAYER_4_AGENT_BROWSER_INSTALL}
|
|
206
413
|
|
|
207
414
|
${LAYER_5_CHROME_FOR_TESTING}
|
|
415
|
+
|
|
416
|
+
${renderEntrypointShimLayer()}
|
|
208
417
|
`
|
|
209
418
|
}
|
|
210
419
|
|
|
@@ -280,8 +489,8 @@ function defaultConfig(): DockerfileConfig {
|
|
|
280
489
|
return { ffmpeg: false, gh: true, python: true, tmux: true, append: [] }
|
|
281
490
|
}
|
|
282
491
|
|
|
283
|
-
function
|
|
284
|
-
const args: string[] = [
|
|
492
|
+
function collectToggleAptArgs(config: DockerfileConfig): string[] {
|
|
493
|
+
const args: string[] = []
|
|
285
494
|
for (const key of ['ffmpeg', 'gh', 'python', 'tmux'] as const) {
|
|
286
495
|
args.push(...APT_FEATURES[key].toAptArgs(config[key]))
|
|
287
496
|
}
|