@runuai/host 0.9.148 → 0.9.150
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/db/migrations/0024_task_env_mode.sql +9 -0
- package/db/migrations/0025_project_env_authority.sql +147 -0
- package/db/migrations/0026_project_env_replica.sql +150 -0
- package/db/migrations/meta/_journal.json +21 -0
- package/db/schema.ts +312 -2
- package/images/standard/Dockerfile +44 -6
- package/images/standard/container/uai-init +85 -2
- package/images/standard/container/uai-task-forwarder.mjs +303 -0
- package/lib/agent-cli.ts +18 -1
- package/lib/agent.ts +99 -34
- package/lib/agents/codex.ts +9 -1
- package/lib/agents/cursor.ts +15 -1
- package/lib/agents/grok.ts +8 -0
- package/lib/agents/kimi.ts +1 -0
- package/lib/agents/machine-durable.ts +2 -0
- package/lib/agents/opencode.ts +1 -0
- package/lib/agents/proc.ts +6 -1
- package/lib/agents/transport.ts +9 -5
- package/lib/browser-testing.ts +6 -0
- package/lib/codex-auth.ts +2 -0
- package/lib/credential-store.ts +141 -6
- package/lib/engine-accounts.ts +1 -0
- package/lib/env-file.ts +7 -0
- package/lib/env-schema.ts +841 -51
- package/lib/git-diff.ts +1 -0
- package/lib/git-identity.ts +2 -0
- package/lib/github-tokens.ts +1 -0
- package/lib/host-env.ts +98 -18
- package/lib/lease.ts +11 -3
- package/lib/machine-gateway-tunnel.ts +195 -17
- package/lib/machine-project-env-loader.ts +152 -0
- package/lib/managed-runtime.ts +270 -51
- package/lib/mcp-gateway.ts +777 -26
- package/lib/orchestrator.ts +408 -81
- package/lib/project-env-authority.ts +1319 -0
- package/lib/project-env-feature.ts +74 -0
- package/lib/project-env-loader.ts +237 -0
- package/lib/project-env-replica.ts +2094 -0
- package/lib/project-env-schema-capture.ts +342 -0
- package/lib/project-env-staging.ts +148 -0
- package/lib/project-env-task-policy.ts +176 -0
- package/lib/runtime-archive.ts +79 -16
- package/lib/runtime-authority.ts +44 -0
- package/lib/runtime-state.ts +175 -10
- package/lib/secrets-proxy/address.ts +72 -0
- package/lib/secrets-proxy/audit.ts +30 -0
- package/lib/secrets-proxy/ca-store.ts +200 -0
- package/lib/secrets-proxy/ca.ts +470 -0
- package/lib/secrets-proxy/container-steps.ts +138 -0
- package/lib/secrets-proxy/der.ts +273 -0
- package/lib/secrets-proxy/engine.ts +1633 -0
- package/lib/secrets-proxy/host.ts +93 -0
- package/lib/secrets-proxy/hostname.ts +60 -0
- package/lib/secrets-proxy/placeholders.ts +196 -0
- package/lib/secrets-proxy/policy.ts +141 -0
- package/lib/secrets-proxy/readiness.ts +148 -0
- package/lib/secrets-proxy/runtime.ts +582 -0
- package/lib/secrets-proxy/scrub.ts +559 -0
- package/lib/secrets-proxy/sidecar-view.ts +139 -0
- package/lib/secrets-proxy/task-scripts.ts +262 -0
- package/lib/secrets-proxy/task-trust.ts +574 -0
- package/lib/secrets-proxy/trust-watch.ts +80 -0
- package/lib/shared-files-s3.ts +3 -0
- package/lib/sidecar-guard.ts +58 -0
- package/lib/sidecar-resume.ts +51 -3
- package/lib/standard-image.ts +50 -12
- package/lib/task-env-forwarder.ts +228 -0
- package/lib/task-env-source.ts +193 -0
- package/lib/task-environment/apple-container.ts +45 -11
- package/lib/task-environment/docker.ts +51 -12
- package/lib/task-environment/index.ts +6 -2
- package/lib/task-environment/machine-task-up.ts +258 -25
- package/lib/task-environment/machine.ts +80 -22
- package/lib/task-environment/registry.ts +3 -2
- package/lib/task-environment/self-identity.ts +20 -0
- package/lib/task-environment/self.ts +92 -16
- package/lib/task-environment/types.ts +63 -0
- package/lib/task-gateway-port.ts +22 -0
- package/lib/task-gateway-protocol.ts +190 -0
- package/lib/task-gateway-recovery.ts +44 -0
- package/lib/task-gateway-state.ts +497 -0
- package/lib/transcript.ts +1 -0
- package/package.json +13 -1
- package/scripts/agent/env-hook.sh +626 -0
- package/scripts/agent/task-up.sh +476 -27
- package/scripts/agent/uai-env-exec.sh +26 -0
- package/src/cli.ts +26 -3
- package/src/index.ts +273 -19
- package/src/main.ts +939 -48
- package/src/project-env-latest.ts +130 -0
- package/src/project-env-policy.ts +281 -0
- package/src/project-env-wording.ts +47 -0
- package/src/protocol.ts +2219 -15
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Uai's task-local project environment loader (ADR-153).
|
|
3
|
+
#
|
|
4
|
+
# This file is sourced, never executed. Keep the hot path shell-native for
|
|
5
|
+
# bash and zsh: it runs before every task command. dash and other POSIX
|
|
6
|
+
# shells use curl because they have no TCP socket builtin.
|
|
7
|
+
|
|
8
|
+
# HTTP is deliberately a tiny, closed protocol. The trusted task forwarder
|
|
9
|
+
# emits CRLF, a Content-Length on every 200, and a strong ETag containing
|
|
10
|
+
# exactly 64 lowercase hex characters. These constants avoid a command
|
|
11
|
+
# substitution (and therefore a fork) in bash/zsh.
|
|
12
|
+
if [ -n "${BASH_VERSION-}" ] || [ -n "${ZSH_VERSION-}" ]; then
|
|
13
|
+
__uai_env_cr=$'\r'
|
|
14
|
+
else
|
|
15
|
+
__uai_env_cr=$(printf '\r')
|
|
16
|
+
fi
|
|
17
|
+
__uai_env_nl='
|
|
18
|
+
'
|
|
19
|
+
|
|
20
|
+
# Keep this in lockstep with TASK_GATEWAY_MAX_ENV_BYTES. The native socket
|
|
21
|
+
# readers still reject a short body before parsing or evaluating it.
|
|
22
|
+
__uai_env_max_body_bytes=524288
|
|
23
|
+
__uai_env_max_header_bytes=16384
|
|
24
|
+
__uai_env_backoff_seconds=10
|
|
25
|
+
|
|
26
|
+
__uai_env_now() {
|
|
27
|
+
if [ "${UAI_ENV_HOOK_TESTING-}" = "1" ] &&
|
|
28
|
+
[ -n "${UAI_ENV_TEST_NOW-}" ]; then
|
|
29
|
+
__uai_env_now_value=$UAI_ENV_TEST_NOW
|
|
30
|
+
return 0
|
|
31
|
+
fi
|
|
32
|
+
case ${SECONDS-} in
|
|
33
|
+
''|*[!0-9]*)
|
|
34
|
+
__uai_env_now_value=$(date +%s 2>/dev/null) || __uai_env_now_value=0
|
|
35
|
+
;;
|
|
36
|
+
*) __uai_env_now_value=$SECONDS ;;
|
|
37
|
+
esac
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
__uai_env_port() {
|
|
41
|
+
__uai_env_port_value=5877
|
|
42
|
+
if [ "${UAI_ENV_HOOK_TESTING-}" = "1" ]; then
|
|
43
|
+
case ${UAI_ENV_TEST_PORT-} in
|
|
44
|
+
''|*[!0-9]*) return 1 ;;
|
|
45
|
+
*)
|
|
46
|
+
if [ "$UAI_ENV_TEST_PORT" -lt 1 ] ||
|
|
47
|
+
[ "$UAI_ENV_TEST_PORT" -gt 65535 ]; then
|
|
48
|
+
return 1
|
|
49
|
+
fi
|
|
50
|
+
__uai_env_port_value=$UAI_ENV_TEST_PORT
|
|
51
|
+
;;
|
|
52
|
+
esac
|
|
53
|
+
fi
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
__uai_env_valid_revision() {
|
|
57
|
+
[ "${#1}" -eq 64 ] || return 1
|
|
58
|
+
case $1 in
|
|
59
|
+
*[!0-9a-f]*) return 1 ;;
|
|
60
|
+
esac
|
|
61
|
+
return 0
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
__uai_env_valid_name() {
|
|
65
|
+
case $1 in
|
|
66
|
+
''|[!A-Za-z_]*|*[!A-Za-z0-9_]*|__UAI_ENV_*) return 1 ;;
|
|
67
|
+
esac
|
|
68
|
+
return 0
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# A loader container starts with every project-owned name explicitly empty so
|
|
72
|
+
# Compose overrides any ENV baked into a derived image. A fresh shell has no
|
|
73
|
+
# __UAI_ENV_KEYS yet, so the first response also needs the complete launch-time
|
|
74
|
+
# name set in order to distinguish an absent key from an intentionally empty
|
|
75
|
+
# value. The runner mounts this list read-only; read and validate it once with
|
|
76
|
+
# shell builtins so Bash/Zsh's per-command hot path never forks.
|
|
77
|
+
__uai_env_load_managed_keys() {
|
|
78
|
+
local __uai_file=/run/uai/managed-env-keys
|
|
79
|
+
local __uai_line __uai_keys= __uai_seen=' '
|
|
80
|
+
|
|
81
|
+
[ "${__uai_env_managed_keys_loaded-0}" != "1" ] || return 0
|
|
82
|
+
if [ "${UAI_ENV_HOOK_TESTING-}" = "1" ] &&
|
|
83
|
+
[ -n "${UAI_ENV_TEST_MANAGED_KEYS-}" ]; then
|
|
84
|
+
__uai_file=$UAI_ENV_TEST_MANAGED_KEYS
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# Direct machine tasks have no image environment to clear and therefore do
|
|
88
|
+
# not need this container-only metadata file.
|
|
89
|
+
[ ! -L "$__uai_file" ] || return 1
|
|
90
|
+
if [ ! -e "$__uai_file" ]; then
|
|
91
|
+
__uai_env_managed_keys=
|
|
92
|
+
__uai_env_managed_keys_loaded=1
|
|
93
|
+
return 0
|
|
94
|
+
fi
|
|
95
|
+
[ -f "$__uai_file" ] || return 1
|
|
96
|
+
|
|
97
|
+
while IFS= read -r __uai_line || [ -n "$__uai_line" ]; do
|
|
98
|
+
__uai_env_valid_name "$__uai_line" || return 1
|
|
99
|
+
case $__uai_seen in
|
|
100
|
+
*" $__uai_line "*) return 1 ;;
|
|
101
|
+
esac
|
|
102
|
+
__uai_seen="${__uai_seen}${__uai_line} "
|
|
103
|
+
if [ -n "$__uai_keys" ]; then
|
|
104
|
+
__uai_keys="$__uai_keys $__uai_line"
|
|
105
|
+
else
|
|
106
|
+
__uai_keys=$__uai_line
|
|
107
|
+
fi
|
|
108
|
+
done < "$__uai_file"
|
|
109
|
+
|
|
110
|
+
__uai_env_managed_keys=$__uai_keys
|
|
111
|
+
__uai_env_managed_keys_loaded=1
|
|
112
|
+
return 0
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
__uai_env_trim_header_value() {
|
|
116
|
+
__uai_env_trimmed=$1
|
|
117
|
+
while [ "${__uai_env_trimmed# }" != "$__uai_env_trimmed" ]; do
|
|
118
|
+
__uai_env_trimmed=${__uai_env_trimmed# }
|
|
119
|
+
done
|
|
120
|
+
while [ "${__uai_env_trimmed% }" != "$__uai_env_trimmed" ]; do
|
|
121
|
+
__uai_env_trimmed=${__uai_env_trimmed% }
|
|
122
|
+
done
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# Parse a complete header block (status line first, no terminating blank line).
|
|
126
|
+
# On success it sets __uai_env_http_status, __uai_env_http_length and
|
|
127
|
+
# __uai_env_http_etag. The endpoint is intentionally stricter than a general
|
|
128
|
+
# HTTP client: transfer coding, duplicate framing, and non-canonical ETags are
|
|
129
|
+
# all rejected before a byte is evaluated as shell input.
|
|
130
|
+
__uai_env_parse_headers() {
|
|
131
|
+
local LC_ALL=C
|
|
132
|
+
local __uai_block=$1 __uai_line __uai_rest __uai_status __uai_name
|
|
133
|
+
local __uai_length= __uai_etag= __uai_seen_length=0 __uai_seen_etag=0
|
|
134
|
+
|
|
135
|
+
[ "${#__uai_block}" -le "$__uai_env_max_header_bytes" ] || return 1
|
|
136
|
+
__uai_status=${__uai_block%%"$__uai_env_nl"*}
|
|
137
|
+
__uai_status=${__uai_status%"$__uai_env_cr"}
|
|
138
|
+
if [ "$__uai_status" = "$__uai_block" ]; then
|
|
139
|
+
__uai_rest=
|
|
140
|
+
else
|
|
141
|
+
__uai_rest=${__uai_block#*"$__uai_env_nl"}
|
|
142
|
+
fi
|
|
143
|
+
case $__uai_status in
|
|
144
|
+
'HTTP/1.0 200 '*|'HTTP/1.1 200 '*) __uai_env_http_status=200 ;;
|
|
145
|
+
'HTTP/1.0 304 '*|'HTTP/1.1 304 '*) __uai_env_http_status=304 ;;
|
|
146
|
+
*) return 1 ;;
|
|
147
|
+
esac
|
|
148
|
+
|
|
149
|
+
while [ -n "$__uai_rest" ]; do
|
|
150
|
+
__uai_line=${__uai_rest%%"$__uai_env_nl"*}
|
|
151
|
+
if [ "$__uai_line" = "$__uai_rest" ]; then
|
|
152
|
+
__uai_rest=
|
|
153
|
+
else
|
|
154
|
+
__uai_rest=${__uai_rest#*"$__uai_env_nl"}
|
|
155
|
+
fi
|
|
156
|
+
__uai_line=${__uai_line%"$__uai_env_cr"}
|
|
157
|
+
[ -n "$__uai_line" ] || return 1
|
|
158
|
+
case $__uai_line in
|
|
159
|
+
[Cc][Oo][Nn][Tt][Ee][Nn][Tt]-[Ll][Ee][Nn][Gg][Tt][Hh]:*)
|
|
160
|
+
[ "$__uai_seen_length" -eq 0 ] || return 1
|
|
161
|
+
__uai_seen_length=1
|
|
162
|
+
__uai_env_trim_header_value "${__uai_line#*:}"
|
|
163
|
+
__uai_length=$__uai_env_trimmed
|
|
164
|
+
case $__uai_length in
|
|
165
|
+
0|[1-9]|[1-9][0-9]*) ;;
|
|
166
|
+
*) return 1 ;;
|
|
167
|
+
esac
|
|
168
|
+
;;
|
|
169
|
+
[Ee][Tt][Aa][Gg]:*)
|
|
170
|
+
[ "$__uai_seen_etag" -eq 0 ] || return 1
|
|
171
|
+
__uai_seen_etag=1
|
|
172
|
+
__uai_env_trim_header_value "${__uai_line#*:}"
|
|
173
|
+
case $__uai_env_trimmed in
|
|
174
|
+
\"*\")
|
|
175
|
+
__uai_etag=${__uai_env_trimmed#\"}
|
|
176
|
+
__uai_etag=${__uai_etag%\"}
|
|
177
|
+
;;
|
|
178
|
+
*) return 1 ;;
|
|
179
|
+
esac
|
|
180
|
+
__uai_env_valid_revision "$__uai_etag" || return 1
|
|
181
|
+
;;
|
|
182
|
+
[Tt][Rr][Aa][Nn][Ss][Ff][Ee][Rr]-[Ee][Nn][Cc][Oo][Dd][Ii][Nn][Gg]:*)
|
|
183
|
+
return 1
|
|
184
|
+
;;
|
|
185
|
+
*:*)
|
|
186
|
+
# Header values are opaque here. Validate only the field name: a
|
|
187
|
+
# normal Node response includes values such as
|
|
188
|
+
# `Date: Thu, 01 Oct 2026 00:23:00 GMT`.
|
|
189
|
+
__uai_name=${__uai_line%%:*}
|
|
190
|
+
case $__uai_name in
|
|
191
|
+
''|*[!A-Za-z0-9_-]*) return 1 ;;
|
|
192
|
+
esac
|
|
193
|
+
;;
|
|
194
|
+
*) return 1 ;;
|
|
195
|
+
esac
|
|
196
|
+
done
|
|
197
|
+
|
|
198
|
+
[ "$__uai_seen_etag" -eq 1 ] || return 1
|
|
199
|
+
if [ "$__uai_env_http_status" -eq 304 ]; then
|
|
200
|
+
# A 304 has no message body and Node's HTTP server correctly omits
|
|
201
|
+
# Content-Length. Accept an explicit zero, but no other framing.
|
|
202
|
+
if [ "$__uai_seen_length" -eq 1 ]; then
|
|
203
|
+
[ "$__uai_length" -eq 0 ] || return 1
|
|
204
|
+
else
|
|
205
|
+
__uai_length=0
|
|
206
|
+
fi
|
|
207
|
+
[ -n "${__UAI_ENV_ETAG-}" ] || return 1
|
|
208
|
+
[ "$__uai_etag" = "$__UAI_ENV_ETAG" ] || return 1
|
|
209
|
+
else
|
|
210
|
+
[ "$__uai_seen_length" -eq 1 ] || return 1
|
|
211
|
+
[ "$__uai_length" -le "$__uai_env_max_body_bytes" ] || return 1
|
|
212
|
+
[ "$__uai_length" -gt 0 ] || return 1
|
|
213
|
+
fi
|
|
214
|
+
__uai_env_http_length=$__uai_length
|
|
215
|
+
__uai_env_http_etag=$__uai_etag
|
|
216
|
+
return 0
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
__uai_env_read_headers() {
|
|
220
|
+
local LC_ALL=C
|
|
221
|
+
local __uai_kind=$1 __uai_fd=$2 __uai_line __uai_block= __uai_count=0
|
|
222
|
+
while :; do
|
|
223
|
+
if ! IFS= read -r -t 2 -u "$__uai_fd" __uai_line; then
|
|
224
|
+
return 1
|
|
225
|
+
fi
|
|
226
|
+
__uai_line=${__uai_line%"$__uai_env_cr"}
|
|
227
|
+
__uai_count=$((__uai_count + ${#__uai_line} + 1))
|
|
228
|
+
[ "$__uai_count" -le "$__uai_env_max_header_bytes" ] || return 1
|
|
229
|
+
if [ -z "$__uai_line" ]; then
|
|
230
|
+
break
|
|
231
|
+
fi
|
|
232
|
+
if [ -z "$__uai_block" ]; then
|
|
233
|
+
__uai_block=$__uai_line
|
|
234
|
+
else
|
|
235
|
+
__uai_block="${__uai_block}${__uai_env_nl}${__uai_line}"
|
|
236
|
+
fi
|
|
237
|
+
done
|
|
238
|
+
[ -n "$__uai_block" ] || return 1
|
|
239
|
+
__uai_env_parse_headers "$__uai_block" || return 1
|
|
240
|
+
|
|
241
|
+
__uai_env_http_body=
|
|
242
|
+
[ "$__uai_env_http_length" -gt 0 ] || return 0
|
|
243
|
+
if [ "$__uai_kind" = "zsh" ]; then
|
|
244
|
+
# zsh's `read -k N -t` applies the timeout only while waiting for the
|
|
245
|
+
# first byte; a peer can send one byte and then stall the shell forever.
|
|
246
|
+
# sysread returns whatever is currently available, so loop under one
|
|
247
|
+
# wall-clock deadline for the complete body.
|
|
248
|
+
zmodload zsh/system 2>/dev/null || return 1
|
|
249
|
+
zmodload zsh/datetime 2>/dev/null || return 1
|
|
250
|
+
local -F __uai_deadline __uai_remaining
|
|
251
|
+
local -i __uai_needed
|
|
252
|
+
local __uai_chunk
|
|
253
|
+
__uai_deadline=$(( EPOCHREALTIME + 2.0 ))
|
|
254
|
+
while [ "${#__uai_env_http_body}" -lt "$__uai_env_http_length" ]; do
|
|
255
|
+
__uai_remaining=$(( __uai_deadline - EPOCHREALTIME ))
|
|
256
|
+
(( __uai_remaining > 0.0 )) || return 1
|
|
257
|
+
__uai_needed=$(( __uai_env_http_length - ${#__uai_env_http_body} ))
|
|
258
|
+
if ! sysread -t "$__uai_remaining" -i "$__uai_fd" \
|
|
259
|
+
-s "$__uai_needed" __uai_chunk; then
|
|
260
|
+
return 1
|
|
261
|
+
fi
|
|
262
|
+
[ -n "$__uai_chunk" ] || return 1
|
|
263
|
+
__uai_env_http_body="${__uai_env_http_body}${__uai_chunk}"
|
|
264
|
+
done
|
|
265
|
+
else
|
|
266
|
+
IFS= read -r -N "$__uai_env_http_length" -u "$__uai_fd" -t 2 \
|
|
267
|
+
__uai_env_http_body || return 1
|
|
268
|
+
fi
|
|
269
|
+
[ "${#__uai_env_http_body}" -eq "$__uai_env_http_length" ] || return 1
|
|
270
|
+
return 0
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
__uai_env_request_text() {
|
|
274
|
+
__uai_env_request="GET /env HTTP/1.0${__uai_env_cr}${__uai_env_nl}Host: 127.0.0.1${__uai_env_cr}${__uai_env_nl}Connection: close${__uai_env_cr}${__uai_env_nl}"
|
|
275
|
+
if [ -n "${__UAI_ENV_ETAG-}" ]; then
|
|
276
|
+
__uai_env_valid_revision "$__UAI_ENV_ETAG" || return 1
|
|
277
|
+
__uai_env_request="${__uai_env_request}If-None-Match: \"${__UAI_ENV_ETAG}\"${__uai_env_cr}${__uai_env_nl}"
|
|
278
|
+
fi
|
|
279
|
+
__uai_env_request="${__uai_env_request}${__uai_env_cr}${__uai_env_nl}"
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
__uai_env_http_bash() {
|
|
283
|
+
local __uai_fd __uai_rc=1
|
|
284
|
+
__uai_env_port || return 1
|
|
285
|
+
__uai_env_request_text || return 1
|
|
286
|
+
if ! eval 'exec {__uai_fd}<>"/dev/tcp/127.0.0.1/'"$__uai_env_port_value"'"' \
|
|
287
|
+
2>/dev/null; then
|
|
288
|
+
return 1
|
|
289
|
+
fi
|
|
290
|
+
if printf '%s' "$__uai_env_request" >&"$__uai_fd" 2>/dev/null &&
|
|
291
|
+
__uai_env_read_headers bash "$__uai_fd"; then
|
|
292
|
+
__uai_rc=0
|
|
293
|
+
fi
|
|
294
|
+
eval 'exec {__uai_fd}>&-' 2>/dev/null || :
|
|
295
|
+
return "$__uai_rc"
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
__uai_env_http_zsh() {
|
|
299
|
+
local __uai_fd __uai_rc=1
|
|
300
|
+
__uai_env_port || return 1
|
|
301
|
+
__uai_env_request_text || return 1
|
|
302
|
+
zmodload zsh/net/tcp 2>/dev/null || return 1
|
|
303
|
+
ztcp 127.0.0.1 "$__uai_env_port_value" 2>/dev/null || return 1
|
|
304
|
+
__uai_fd=$REPLY
|
|
305
|
+
if print -rn -u "$__uai_fd" -- "$__uai_env_request" 2>/dev/null &&
|
|
306
|
+
__uai_env_read_headers zsh "$__uai_fd"; then
|
|
307
|
+
__uai_rc=0
|
|
308
|
+
fi
|
|
309
|
+
ztcp -c "$__uai_fd" 2>/dev/null || :
|
|
310
|
+
return "$__uai_rc"
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
__uai_env_parse_curl_response() {
|
|
314
|
+
local LC_ALL=C
|
|
315
|
+
local __uai_raw=$1 __uai_separator __uai_headers
|
|
316
|
+
__uai_separator="${__uai_env_cr}${__uai_env_nl}${__uai_env_cr}${__uai_env_nl}"
|
|
317
|
+
case $__uai_raw in
|
|
318
|
+
*"$__uai_separator"*) ;;
|
|
319
|
+
*) return 1 ;;
|
|
320
|
+
esac
|
|
321
|
+
__uai_headers=${__uai_raw%%"$__uai_separator"*}
|
|
322
|
+
__uai_env_http_body=${__uai_raw#*"$__uai_separator"}
|
|
323
|
+
__uai_env_parse_headers "$__uai_headers" || return 1
|
|
324
|
+
[ "${#__uai_env_http_body}" -eq "$__uai_env_http_length" ] || return 1
|
|
325
|
+
return 0
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
__uai_env_http_curl() {
|
|
329
|
+
local __uai_raw __uai_marker='__UAI_ENV_CURL_CAPTURE_END__'
|
|
330
|
+
__uai_env_port || return 1
|
|
331
|
+
__uai_env_request_text || return 1
|
|
332
|
+
if [ -n "${__UAI_ENV_ETAG-}" ]; then
|
|
333
|
+
__uai_raw=$(
|
|
334
|
+
/usr/bin/curl --silent --http1.0 --include --raw --noproxy '*' \
|
|
335
|
+
--connect-timeout 1 --max-time 2 \
|
|
336
|
+
--header 'Connection: close' \
|
|
337
|
+
--header "If-None-Match: \"${__UAI_ENV_ETAG}\"" \
|
|
338
|
+
"http://127.0.0.1:${__uai_env_port_value}/env" 2>/dev/null &&
|
|
339
|
+
printf '%s' "$__uai_marker"
|
|
340
|
+
) || return 1
|
|
341
|
+
else
|
|
342
|
+
__uai_raw=$(
|
|
343
|
+
/usr/bin/curl --silent --http1.0 --include --raw --noproxy '*' \
|
|
344
|
+
--connect-timeout 1 --max-time 2 \
|
|
345
|
+
--header 'Connection: close' \
|
|
346
|
+
"http://127.0.0.1:${__uai_env_port_value}/env" 2>/dev/null &&
|
|
347
|
+
printf '%s' "$__uai_marker"
|
|
348
|
+
) || return 1
|
|
349
|
+
fi
|
|
350
|
+
case $__uai_raw in
|
|
351
|
+
*"$__uai_marker") __uai_raw=${__uai_raw%"$__uai_marker"} ;;
|
|
352
|
+
*) return 1 ;;
|
|
353
|
+
esac
|
|
354
|
+
__uai_env_parse_curl_response "$__uai_raw"
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
__uai_env_http() {
|
|
358
|
+
if [ -n "${ZSH_VERSION-}" ]; then
|
|
359
|
+
__uai_env_http_zsh
|
|
360
|
+
elif [ -n "${BASH_VERSION-}" ]; then
|
|
361
|
+
__uai_env_http_bash
|
|
362
|
+
else
|
|
363
|
+
__uai_env_http_curl
|
|
364
|
+
fi
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
__uai_env_parse_body() {
|
|
368
|
+
local LC_ALL=C
|
|
369
|
+
local __uai_body=$1 __uai_without_last_nl __uai_last __uai_head
|
|
370
|
+
local __uai_first __uai_keys __uai_script __uai_key __uai_seen=' '
|
|
371
|
+
|
|
372
|
+
case $__uai_body in
|
|
373
|
+
*"$__uai_env_nl") ;;
|
|
374
|
+
*) return 1 ;;
|
|
375
|
+
esac
|
|
376
|
+
__uai_without_last_nl=${__uai_body%"$__uai_env_nl"}
|
|
377
|
+
__uai_last=${__uai_without_last_nl##*"$__uai_env_nl"}
|
|
378
|
+
[ "$__uai_last" != "$__uai_without_last_nl" ] || return 1
|
|
379
|
+
[ "$__uai_last" = "# uai-env-v1 end=${__uai_env_http_etag}" ] || return 1
|
|
380
|
+
__uai_head=${__uai_without_last_nl%"$__uai_env_nl$__uai_last"}
|
|
381
|
+
__uai_first=${__uai_head%%"$__uai_env_nl"*}
|
|
382
|
+
case $__uai_first in
|
|
383
|
+
'# uai-env-v1 keys='*) __uai_keys=${__uai_first#'# uai-env-v1 keys='} ;;
|
|
384
|
+
*) return 1 ;;
|
|
385
|
+
esac
|
|
386
|
+
case $__uai_keys in
|
|
387
|
+
*[!A-Za-z0-9_' ']*) return 1 ;;
|
|
388
|
+
esac
|
|
389
|
+
if [ "$__uai_head" = "$__uai_first" ]; then
|
|
390
|
+
__uai_script=
|
|
391
|
+
else
|
|
392
|
+
__uai_script=${__uai_head#*"$__uai_env_nl"}
|
|
393
|
+
fi
|
|
394
|
+
|
|
395
|
+
if [ -n "${ZSH_VERSION-}" ]; then
|
|
396
|
+
setopt localoptions shwordsplit
|
|
397
|
+
fi
|
|
398
|
+
for __uai_key in $__uai_keys; do
|
|
399
|
+
__uai_env_valid_name "$__uai_key" || return 1
|
|
400
|
+
case $__uai_seen in
|
|
401
|
+
*" $__uai_key "*) return 1 ;;
|
|
402
|
+
esac
|
|
403
|
+
__uai_seen="${__uai_seen}${__uai_key} "
|
|
404
|
+
done
|
|
405
|
+
__uai_env_validate_script "$__uai_script" "$__uai_keys" || return 1
|
|
406
|
+
__uai_env_body_keys=$__uai_keys
|
|
407
|
+
__uai_env_body_script=$__uai_script
|
|
408
|
+
return 0
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
# Accept only the renderer's exact grammar. Values may contain newlines and
|
|
412
|
+
# any byte except NUL; an apostrophe is represented by the canonical POSIX
|
|
413
|
+
# sequence '\'' (close quote, escaped quote, reopen quote). This scan is all
|
|
414
|
+
# shell builtins, so a changed response still does not fork before eval.
|
|
415
|
+
__uai_env_validate_script() {
|
|
416
|
+
local LC_ALL=C
|
|
417
|
+
local __uai_rest=$1 __uai_keys=$2 __uai_key __uai_prefix
|
|
418
|
+
local __uai_chunk __uai_tail __uai_consumed
|
|
419
|
+
if [ -n "${ZSH_VERSION-}" ]; then
|
|
420
|
+
setopt localoptions shwordsplit
|
|
421
|
+
fi
|
|
422
|
+
for __uai_key in $__uai_keys; do
|
|
423
|
+
__uai_prefix="export ${__uai_key}='"
|
|
424
|
+
case $__uai_rest in
|
|
425
|
+
"$__uai_prefix"*) __uai_rest=${__uai_rest#"$__uai_prefix"} ;;
|
|
426
|
+
*) return 1 ;;
|
|
427
|
+
esac
|
|
428
|
+
while :; do
|
|
429
|
+
# Scan toward the next quote in bounded chunks. Removing one byte at a
|
|
430
|
+
# time made a valid 100 KB refresh take tens of seconds because every
|
|
431
|
+
# removal copied the entire remaining suffix.
|
|
432
|
+
if [ -n "${BASH_VERSION-}${ZSH_VERSION-}" ]; then
|
|
433
|
+
# Bash and zsh's glob removal becomes superlinear on a long suffix.
|
|
434
|
+
# Bound each glob to 1 KiB, then advance by substring offset.
|
|
435
|
+
__uai_chunk=${__uai_rest:0:1024}
|
|
436
|
+
case $__uai_chunk in
|
|
437
|
+
*"'"*)
|
|
438
|
+
__uai_tail=${__uai_chunk#*"'"}
|
|
439
|
+
__uai_consumed=$(( ${#__uai_chunk} - ${#__uai_tail} ))
|
|
440
|
+
__uai_rest=${__uai_rest:$__uai_consumed}
|
|
441
|
+
;;
|
|
442
|
+
*)
|
|
443
|
+
[ "${#__uai_chunk}" -eq 1024 ] || return 1
|
|
444
|
+
__uai_rest=${__uai_rest:1024}
|
|
445
|
+
continue
|
|
446
|
+
;;
|
|
447
|
+
esac
|
|
448
|
+
else
|
|
449
|
+
case $__uai_rest in
|
|
450
|
+
*"'"*) __uai_rest=${__uai_rest#*"'"} ;;
|
|
451
|
+
*) return 1 ;;
|
|
452
|
+
esac
|
|
453
|
+
fi
|
|
454
|
+
case $__uai_rest in
|
|
455
|
+
"\\''"*) __uai_rest=${__uai_rest#"\\''"} ;;
|
|
456
|
+
'') break ;;
|
|
457
|
+
"$__uai_env_nl"*)
|
|
458
|
+
__uai_rest=${__uai_rest#"$__uai_env_nl"}
|
|
459
|
+
break
|
|
460
|
+
;;
|
|
461
|
+
*) return 1 ;;
|
|
462
|
+
esac
|
|
463
|
+
done
|
|
464
|
+
done
|
|
465
|
+
[ -z "$__uai_rest" ]
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
__uai_env_apply() {
|
|
469
|
+
local __uai_old_keys __uai_key
|
|
470
|
+
if [ -n "${ZSH_VERSION-}" ]; then
|
|
471
|
+
setopt localoptions shwordsplit
|
|
472
|
+
fi
|
|
473
|
+
__uai_env_load_managed_keys || return 1
|
|
474
|
+
__uai_old_keys="${__UAI_ENV_KEYS-} ${__uai_env_managed_keys-}"
|
|
475
|
+
if ! eval "$__uai_env_body_script"; then
|
|
476
|
+
return 1
|
|
477
|
+
fi
|
|
478
|
+
for __uai_key in $__uai_old_keys; do
|
|
479
|
+
__uai_env_valid_name "$__uai_key" || continue
|
|
480
|
+
case " $__uai_env_body_keys " in
|
|
481
|
+
*" $__uai_key "*) ;;
|
|
482
|
+
*) unset "$__uai_key" ;;
|
|
483
|
+
esac
|
|
484
|
+
done
|
|
485
|
+
__UAI_ENV_KEYS=$__uai_env_body_keys
|
|
486
|
+
__UAI_ENV_ETAG=$__uai_env_http_etag
|
|
487
|
+
export __UAI_ENV_KEYS __UAI_ENV_ETAG
|
|
488
|
+
return 0
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
__uai_env_sync_inner() {
|
|
492
|
+
__uai_env_http || return 1
|
|
493
|
+
if [ "$__uai_env_http_status" -eq 304 ]; then
|
|
494
|
+
return 0
|
|
495
|
+
fi
|
|
496
|
+
__uai_env_parse_body "$__uai_env_http_body" || return 1
|
|
497
|
+
__uai_env_apply
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
__uai_env_sync() {
|
|
501
|
+
local __uai_now
|
|
502
|
+
if [ -n "${ZSH_VERSION-}" ]; then
|
|
503
|
+
emulate -L zsh
|
|
504
|
+
fi
|
|
505
|
+
[ "${UAI_ENV_HOOK-0}" = "1" ] || return 0
|
|
506
|
+
[ "${__uai_env_sync_active-0}" != "1" ] || return 0
|
|
507
|
+
|
|
508
|
+
__uai_env_now
|
|
509
|
+
__uai_now=$__uai_env_now_value
|
|
510
|
+
__uai_env_retry_after=${__uai_env_retry_after-0}
|
|
511
|
+
case ${__uai_env_retry_after-0} in
|
|
512
|
+
''|*[!0-9]*) __uai_env_retry_after=0 ;;
|
|
513
|
+
esac
|
|
514
|
+
if [ "$__uai_now" -lt "$__uai_env_retry_after" ]; then
|
|
515
|
+
return 0
|
|
516
|
+
fi
|
|
517
|
+
|
|
518
|
+
__uai_env_sync_active=1
|
|
519
|
+
if __uai_env_sync_inner; then
|
|
520
|
+
__uai_env_retry_after=0
|
|
521
|
+
__uai_env_warned=0
|
|
522
|
+
else
|
|
523
|
+
__uai_env_retry_after=$((__uai_now + __uai_env_backoff_seconds))
|
|
524
|
+
if [ -z "${__UAI_ENV_ETAG-}" ] && [ "${__uai_env_warned-0}" != "1" ]; then
|
|
525
|
+
printf '%s\n' \
|
|
526
|
+
'uai: project environment is temporarily unavailable; retrying' >&2
|
|
527
|
+
__uai_env_warned=1
|
|
528
|
+
fi
|
|
529
|
+
fi
|
|
530
|
+
__uai_env_sync_active=0
|
|
531
|
+
return 0
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
# Interactive zsh calls this through add-zsh-hook. Keeping the callback in
|
|
535
|
+
# the asset makes the task-up stub a one-line, idempotent registration.
|
|
536
|
+
__uai_env_zsh_preexec() {
|
|
537
|
+
emulate -L zsh
|
|
538
|
+
# An interrupt unwinds the prior preexec before __uai_env_sync reaches its
|
|
539
|
+
# final assignment. At the next submitted command there cannot still be a
|
|
540
|
+
# live preexec, so clear that stale guard before trying again.
|
|
541
|
+
__uai_env_sync_active=0
|
|
542
|
+
__uai_env_sync
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
__uai_env_install_zsh_preexec() {
|
|
546
|
+
[ -n "${ZSH_VERSION-}" ] || return 0
|
|
547
|
+
emulate -L zsh
|
|
548
|
+
case $- in *i*) ;; *) return 0 ;; esac
|
|
549
|
+
[ "${__uai_env_zsh_preexec_installed-0}" != "1" ] || return 0
|
|
550
|
+
autoload -Uz add-zsh-hook || return 0
|
|
551
|
+
add-zsh-hook -d preexec __uai_env_zsh_preexec 2>/dev/null || :
|
|
552
|
+
add-zsh-hook preexec __uai_env_zsh_preexec
|
|
553
|
+
__uai_env_zsh_preexec_installed=1
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
# Interactive bash has no native preexec hook. A DEBUG trap marks the first
|
|
557
|
+
# simple command in a submitted command line; PROMPT_COMMAND clears the mark
|
|
558
|
+
# only when the next prompt is drawn. Existing DEBUG/PROMPT_COMMAND handlers
|
|
559
|
+
# remain in the chain, including VS Code's shell integration.
|
|
560
|
+
__uai_env_bash_preexec() {
|
|
561
|
+
__uai_env_sync
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
__uai_env_bash_precmd() {
|
|
565
|
+
# PROMPT_COMMAND runs after an interrupted preexec too. Clear both guards
|
|
566
|
+
# so the next submitted command retries instead of remaining disabled.
|
|
567
|
+
__uai_env_sync_active=0
|
|
568
|
+
__uai_env_bash_command_active=0
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
__uai_env_bash_debug_dispatch() {
|
|
572
|
+
local __uai_status=${1-0}
|
|
573
|
+
__uai_env_bash_restored_underscore=${2-}
|
|
574
|
+
if [ "${__uai_env_bash_command_active-0}" = "0" ]; then
|
|
575
|
+
__uai_env_bash_command_active=1
|
|
576
|
+
__uai_env_bash_preexec
|
|
577
|
+
fi
|
|
578
|
+
if [ -n "${__uai_env_bash_previous_debug-}" ]; then
|
|
579
|
+
builtin eval "$__uai_env_bash_previous_debug"
|
|
580
|
+
fi
|
|
581
|
+
return "$__uai_status"
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
__uai_env_bash_get_debug_trap() {
|
|
585
|
+
local -a __uai_terms
|
|
586
|
+
builtin eval "__uai_terms=( $(trap -p DEBUG) )"
|
|
587
|
+
builtin printf '%s' "${__uai_terms[2]-}"
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
__uai_env_install_bash_preexec() {
|
|
591
|
+
local __uai_prompt_decl
|
|
592
|
+
[ -n "${BASH_VERSION-}" ] || return 0
|
|
593
|
+
case $- in *i*) ;; *) return 0 ;; esac
|
|
594
|
+
[ "${__uai_env_bash_preexec_installed-0}" != "1" ] || return 0
|
|
595
|
+
|
|
596
|
+
__uai_env_bash_previous_debug=$(__uai_env_bash_get_debug_trap)
|
|
597
|
+
__uai_env_bash_command_active=1
|
|
598
|
+
# A DEBUG handler's own commands normally replace Bash's `$_`. Capture it
|
|
599
|
+
# as an argument before the dispatcher runs, then make it the trap's final
|
|
600
|
+
# argument so the submitted command observes the same previous argument.
|
|
601
|
+
trap '__uai_env_bash_debug_dispatch "$?" "$_"; : "$__uai_env_bash_restored_underscore"' DEBUG
|
|
602
|
+
|
|
603
|
+
__uai_prompt_decl=$(declare -p PROMPT_COMMAND 2>/dev/null || :)
|
|
604
|
+
case $__uai_prompt_decl in
|
|
605
|
+
'declare -a '*) builtin eval 'PROMPT_COMMAND+=(__uai_env_bash_precmd)' ;;
|
|
606
|
+
*)
|
|
607
|
+
if [ -n "${PROMPT_COMMAND-}" ]; then
|
|
608
|
+
PROMPT_COMMAND="${PROMPT_COMMAND};__uai_env_bash_precmd"
|
|
609
|
+
else
|
|
610
|
+
PROMPT_COMMAND=__uai_env_bash_precmd
|
|
611
|
+
fi
|
|
612
|
+
;;
|
|
613
|
+
esac
|
|
614
|
+
__uai_env_bash_preexec_installed=1
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
# BASH_ENV points straight at this asset. Bash also reads BASH_ENV for shell
|
|
618
|
+
# scripts; those inherit the caller's already-refreshed environment and must
|
|
619
|
+
# not pay another request. Only `bash -c` has BASH_EXECUTION_STRING.
|
|
620
|
+
if [ "${UAI_ENV_HOOK-0}" = "1" ] &&
|
|
621
|
+
[ -n "${BASH_VERSION-}" ] &&
|
|
622
|
+
[ -n "${BASH_EXECUTION_STRING-}" ] &&
|
|
623
|
+
[ -n "${BASH_ENV-}" ] &&
|
|
624
|
+
[ "$BASH_ENV" = "${BASH_SOURCE[0]-}" ]; then
|
|
625
|
+
__uai_env_sync
|
|
626
|
+
fi
|