@runuai/host 0.9.148 → 0.9.149

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 (93) hide show
  1. package/db/migrations/0024_task_env_mode.sql +9 -0
  2. package/db/migrations/0025_project_env_authority.sql +147 -0
  3. package/db/migrations/0026_project_env_replica.sql +150 -0
  4. package/db/migrations/meta/_journal.json +21 -0
  5. package/db/schema.ts +312 -2
  6. package/images/standard/Dockerfile +44 -6
  7. package/images/standard/container/uai-init +85 -2
  8. package/images/standard/container/uai-task-forwarder.mjs +303 -0
  9. package/lib/agent-cli.ts +18 -1
  10. package/lib/agent.ts +99 -34
  11. package/lib/agents/codex.ts +9 -1
  12. package/lib/agents/cursor.ts +15 -1
  13. package/lib/agents/grok.ts +8 -0
  14. package/lib/agents/kimi.ts +1 -0
  15. package/lib/agents/machine-durable.ts +2 -0
  16. package/lib/agents/opencode.ts +1 -0
  17. package/lib/agents/proc.ts +6 -1
  18. package/lib/agents/transport.ts +9 -5
  19. package/lib/browser-testing.ts +6 -0
  20. package/lib/codex-auth.ts +2 -0
  21. package/lib/credential-store.ts +141 -6
  22. package/lib/engine-accounts.ts +1 -0
  23. package/lib/env-file.ts +7 -0
  24. package/lib/env-schema.ts +841 -51
  25. package/lib/git-diff.ts +1 -0
  26. package/lib/git-identity.ts +2 -0
  27. package/lib/github-tokens.ts +1 -0
  28. package/lib/host-env.ts +98 -18
  29. package/lib/lease.ts +11 -3
  30. package/lib/machine-gateway-tunnel.ts +195 -17
  31. package/lib/machine-project-env-loader.ts +152 -0
  32. package/lib/managed-runtime.ts +270 -51
  33. package/lib/mcp-gateway.ts +777 -26
  34. package/lib/orchestrator.ts +391 -69
  35. package/lib/project-env-authority.ts +1319 -0
  36. package/lib/project-env-feature.ts +74 -0
  37. package/lib/project-env-loader.ts +237 -0
  38. package/lib/project-env-replica.ts +2094 -0
  39. package/lib/project-env-schema-capture.ts +342 -0
  40. package/lib/project-env-staging.ts +148 -0
  41. package/lib/project-env-task-policy.ts +176 -0
  42. package/lib/runtime-archive.ts +79 -16
  43. package/lib/runtime-authority.ts +44 -0
  44. package/lib/runtime-state.ts +175 -10
  45. package/lib/secrets-proxy/address.ts +72 -0
  46. package/lib/secrets-proxy/audit.ts +30 -0
  47. package/lib/secrets-proxy/ca-store.ts +200 -0
  48. package/lib/secrets-proxy/ca.ts +470 -0
  49. package/lib/secrets-proxy/container-steps.ts +138 -0
  50. package/lib/secrets-proxy/der.ts +273 -0
  51. package/lib/secrets-proxy/engine.ts +1633 -0
  52. package/lib/secrets-proxy/host.ts +93 -0
  53. package/lib/secrets-proxy/hostname.ts +60 -0
  54. package/lib/secrets-proxy/placeholders.ts +196 -0
  55. package/lib/secrets-proxy/policy.ts +141 -0
  56. package/lib/secrets-proxy/readiness.ts +148 -0
  57. package/lib/secrets-proxy/runtime.ts +582 -0
  58. package/lib/secrets-proxy/scrub.ts +559 -0
  59. package/lib/secrets-proxy/sidecar-view.ts +139 -0
  60. package/lib/secrets-proxy/task-scripts.ts +262 -0
  61. package/lib/secrets-proxy/task-trust.ts +574 -0
  62. package/lib/secrets-proxy/trust-watch.ts +80 -0
  63. package/lib/shared-files-s3.ts +3 -0
  64. package/lib/sidecar-guard.ts +58 -0
  65. package/lib/sidecar-resume.ts +51 -3
  66. package/lib/standard-image.ts +50 -12
  67. package/lib/task-env-forwarder.ts +228 -0
  68. package/lib/task-env-source.ts +193 -0
  69. package/lib/task-environment/apple-container.ts +45 -11
  70. package/lib/task-environment/docker.ts +51 -12
  71. package/lib/task-environment/index.ts +6 -2
  72. package/lib/task-environment/machine-task-up.ts +258 -25
  73. package/lib/task-environment/machine.ts +80 -22
  74. package/lib/task-environment/registry.ts +3 -2
  75. package/lib/task-environment/self-identity.ts +20 -0
  76. package/lib/task-environment/self.ts +92 -16
  77. package/lib/task-environment/types.ts +63 -0
  78. package/lib/task-gateway-port.ts +22 -0
  79. package/lib/task-gateway-protocol.ts +190 -0
  80. package/lib/task-gateway-recovery.ts +44 -0
  81. package/lib/task-gateway-state.ts +497 -0
  82. package/lib/transcript.ts +1 -0
  83. package/package.json +13 -1
  84. package/scripts/agent/env-hook.sh +626 -0
  85. package/scripts/agent/task-up.sh +476 -27
  86. package/scripts/agent/uai-env-exec.sh +26 -0
  87. package/src/cli.ts +26 -3
  88. package/src/index.ts +273 -19
  89. package/src/main.ts +939 -48
  90. package/src/project-env-latest.ts +130 -0
  91. package/src/project-env-policy.ts +281 -0
  92. package/src/project-env-wording.ts +47 -0
  93. 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