@ngockhoale/ukit 2.4.2 → 2.4.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/manifests/platform.full.yaml +19 -111
- package/package.json +2 -1
- package/scripts/index/refresh-index.mjs +47 -22
- package/src/core/compact/threshold.js +36 -6
- package/src/diagnostics/classifyHang.js +246 -0
- package/src/index/buildIndex.js +1033 -62
- package/templates/.claude/hooks/auto-allow-bash.sh +82 -93
- package/templates/.claude/hooks/block-dangerous.sh +31 -5
- package/templates/.claude/hooks/completion-gate.sh +51 -10
- package/templates/.claude/hooks/compress-output.sh +38 -6
- package/templates/.claude/hooks/context-hardcap-gate.sh +35 -6
- package/templates/.claude/hooks/context-window-guard.sh +128 -18
- package/templates/.claude/hooks/handoff-model-guard.sh +31 -5
- package/templates/.claude/hooks/handoff-resume.sh +31 -5
- package/templates/.claude/hooks/post-edit-verify.sh +31 -5
- package/templates/.claude/hooks/pre-edit-backup.sh +31 -5
- package/templates/.claude/hooks/protect-files.sh +31 -5
- package/templates/.claude/hooks/record-execution.sh +31 -5
- package/templates/.claude/hooks/sensitive-data-guard.sh +44 -8
- package/templates/.claude/hooks/skill-router.sh +31 -5
- package/templates/.claude/hooks/stale-spec-guard.sh +31 -5
- package/templates/.claude/hooks/task-watchdog.sh +108 -123
- package/templates/.claude/hooks/verification-guard.sh +107 -112
- package/templates/.claude/hooks/vision-router.sh +49 -13
- package/templates/.claude/settings.json +0 -5
- package/templates/.claude/ukit/index/lib/index-core.mjs +960 -63
- package/templates/.claude/ukit/index/refresh-index.mjs +47 -22
- package/templates/.claude/ukit/index/route-task.mjs +610 -4
- package/templates/.claude/ukit/runtime/async-lock.mjs +340 -0
- package/templates/.claude/ukit/runtime/compact-threshold.mjs +73 -24
- package/templates/.claude/ukit/runtime/context-capacity.mjs +144 -0
- package/templates/.claude/ukit/runtime/execution-ledger.mjs +664 -170
- package/templates/.claude/ukit/runtime/hook-chain-budget.mjs +92 -0
- package/templates/.claude/ukit/runtime/hook-chain-runner.mjs +84 -29
- package/templates/.claude/ukit/runtime/hook-input.sh +85 -5
- package/templates/.claude/ukit/runtime/hook-payload-store.mjs +160 -0
- package/templates/.claude/ukit/runtime/hook-process.mjs +250 -0
- package/templates/.claude/ukit/runtime/hook-telemetry.mjs +255 -0
- package/templates/.claude/ukit/runtime/hook-telemetry.sh +60 -0
- package/templates/.claude/ukit/runtime/stop-coordinator.mjs +509 -0
- package/templates/.claude/ukit/runtime/task-watchdog.mjs +180 -6
- package/templates/.claude/ukit/runtime/transcript-tail.mjs +1 -1
- package/templates/.omp/hooks/pre/ukit-bridge.js +171 -57
|
@@ -25,16 +25,42 @@
|
|
|
25
25
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
26
|
# shellcheck source=/dev/null
|
|
27
27
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
28
|
+
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
29
|
+
# shared cleanup path before the staged payload is removed. A missing
|
|
30
|
+
# runtime (pre-install tree) simply leaves telemetry off.
|
|
31
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
28
32
|
trap ukit_cleanup_hook_input EXIT
|
|
29
33
|
# Bounded stdin (H01): stage before anything reads it; Node gets a path, never the payload.
|
|
30
34
|
ukit_stage_hook_input 2097152 truncate || exit 0
|
|
31
35
|
else
|
|
32
|
-
# Runtime helper missing (pre-install tree): bounded
|
|
33
|
-
#
|
|
36
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
37
|
+
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
38
|
+
# closes the pipe (R4.5). Read cap+1 in the background, bound the wait.
|
|
34
39
|
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
if [ -e /dev/fd/0 ]; then
|
|
41
|
+
exec 8<&0
|
|
42
|
+
head -c 2097153 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
43
|
+
else
|
|
44
|
+
head -c 2097153 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
45
|
+
fi
|
|
46
|
+
__ukit_reader=$!
|
|
47
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
48
|
+
case "$__ukit_stage_ms" in
|
|
49
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
50
|
+
esac
|
|
51
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
52
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
53
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
54
|
+
__ukit_waiter=$!
|
|
55
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
56
|
+
__ukit_stage_rc=$?
|
|
57
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
58
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
59
|
+
exec 8<&-
|
|
60
|
+
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
61
|
+
# A bounded cut (rc != 0) leaves a short payload that the JSON consumer
|
|
62
|
+
# already degrades on - the shared helper's truncate posture (R4.5).
|
|
63
|
+
if [ "$__ukit_size" -gt 2097152 ]; then
|
|
38
64
|
truncate -s 2097152 "$UKIT_INPUT_FILE" 2>/dev/null
|
|
39
65
|
fi
|
|
40
66
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
@@ -80,12 +106,18 @@ const projectRoot = process.env.PROJECT_ROOT;
|
|
|
80
106
|
const transcriptPath = typeof payload.transcript_path === 'string' ? payload.transcript_path.trim() : '';
|
|
81
107
|
if (!transcriptPath) process.exit(0);
|
|
82
108
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
109
|
+
// Operator config is read once; a missing/corrupt file just means the defaults.
|
|
110
|
+
let runtimeConfig = {};
|
|
111
|
+
try {
|
|
112
|
+
runtimeConfig = JSON.parse(fs.readFileSync(path.join(projectRoot, '.ukit', 'storage', 'config.json'), 'utf8'));
|
|
113
|
+
} catch { /* fall through to the defaults */ }
|
|
114
|
+
if (!runtimeConfig || typeof runtimeConfig !== 'object') runtimeConfig = {};
|
|
115
|
+
|
|
116
|
+
// Absolute operator ceiling (compact.hardCapTokens). Negotiated capacity can only ever
|
|
117
|
+
// tighten this, never raise it.
|
|
118
|
+
function operatorHardCap() {
|
|
119
|
+
const value = runtimeConfig?.compact?.hardCapTokens;
|
|
120
|
+
if (typeof value === 'number' && Number.isFinite(value) && Number.isInteger(value) && value > 0) return value;
|
|
89
121
|
return 500_000;
|
|
90
122
|
}
|
|
91
123
|
|
|
@@ -160,9 +192,89 @@ for (let i = start; i < entries.length; i += 1) {
|
|
|
160
192
|
}
|
|
161
193
|
|
|
162
194
|
const estimatedTokens = Math.round(contentChars / CHARS_PER_TOKEN);
|
|
163
|
-
|
|
195
|
+
|
|
196
|
+
// Latest assistant model in the live window — this is the model that must fit the NEXT
|
|
197
|
+
// request, so capacity negotiation follows it. A mid-session gateway swap to a smaller
|
|
198
|
+
// model tightens the cap immediately; the earlier model's window is never sticky.
|
|
199
|
+
// (Hoisted above the ratio so both capacity negotiation and the swap note share it.)
|
|
200
|
+
const RECENT_MODEL_WINDOW = 40;
|
|
201
|
+
const recentModels = modelSequence.slice(-RECENT_MODEL_WINDOW);
|
|
202
|
+
const currentModel = recentModels[recentModels.length - 1] || null;
|
|
203
|
+
|
|
204
|
+
// Negotiated capacity (H22): derive the advisory cap from the best available evidence —
|
|
205
|
+
// env override > config override > verified model table > conservative default — instead
|
|
206
|
+
// of assuming one hard-coded 500k window. The source is surfaced in the warning lines
|
|
207
|
+
// below (capacitySource=...) so a mis-negotiated cap is diagnosable at a glance. A
|
|
208
|
+
// missing runtime helper (pre-install tree) keeps the shipped fallback behavior —
|
|
209
|
+
// advisory and fail-open either way.
|
|
210
|
+
async function loadCapacityResolver() {
|
|
211
|
+
try {
|
|
212
|
+
return await import(pathToFileURL(path.join(
|
|
213
|
+
process.env.SCRIPT_DIR || process.cwd(),
|
|
214
|
+
'..',
|
|
215
|
+
'ukit',
|
|
216
|
+
'runtime',
|
|
217
|
+
'context-capacity.mjs',
|
|
218
|
+
)).href);
|
|
219
|
+
} catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let hardCap = operatorHardCap();
|
|
225
|
+
let capacitySource = 'default';
|
|
226
|
+
let negotiatedCapacity = null;
|
|
227
|
+
try {
|
|
228
|
+
const capacityModule = await loadCapacityResolver();
|
|
229
|
+
if (capacityModule && typeof capacityModule.resolveContextCapTokens === 'function') {
|
|
230
|
+
const resolved = capacityModule.resolveContextCapTokens({
|
|
231
|
+
env: process.env,
|
|
232
|
+
config: runtimeConfig,
|
|
233
|
+
modelMetadata: { model: currentModel },
|
|
234
|
+
});
|
|
235
|
+
hardCap = resolved.capTokens;
|
|
236
|
+
capacitySource = resolved.capacity?.source ?? 'default';
|
|
237
|
+
negotiatedCapacity = resolved.capacity ?? null;
|
|
238
|
+
}
|
|
239
|
+
} catch { /* negotiation is advisory — the operator ceiling already applies */ }
|
|
164
240
|
const ratio = estimatedTokens / hardCap;
|
|
165
241
|
|
|
242
|
+
// Publish what this hook negotiated so the compact-threshold/hard-cap-gate path stops
|
|
243
|
+
// assuming a 500k window: this is the only place with live evidence (env + config + the
|
|
244
|
+
// transcript's assistant models). Without the record, automatic compact waits until 240k
|
|
245
|
+
// and the fail-closed gate until 500k on a verified 200k route — after the provider already
|
|
246
|
+
// rejected the request. Best-effort: an unwritable cache dir must never break the guard.
|
|
247
|
+
const CAPACITY_RECORD_PATH = path.join(projectRoot, '.ukit', 'storage', 'cache', 'context-capacity.json');
|
|
248
|
+
if (negotiatedCapacity) {
|
|
249
|
+
try {
|
|
250
|
+
fs.mkdirSync(path.dirname(CAPACITY_RECORD_PATH), { recursive: true });
|
|
251
|
+
const previous = readCapacityRecord();
|
|
252
|
+
// Only rewrite when the evidence changed, so prompt caching sees a stable file.
|
|
253
|
+
if (!previous
|
|
254
|
+
|| previous.tokens !== negotiatedCapacity.tokens
|
|
255
|
+
|| previous.source !== negotiatedCapacity.source
|
|
256
|
+
|| previous.model !== (currentModel ?? null)) {
|
|
257
|
+
const record = {
|
|
258
|
+
tokens: negotiatedCapacity.tokens,
|
|
259
|
+
source: negotiatedCapacity.source,
|
|
260
|
+
confidence: negotiatedCapacity.confidence,
|
|
261
|
+
model: currentModel ?? null,
|
|
262
|
+
updatedAt: Date.now(),
|
|
263
|
+
};
|
|
264
|
+
fs.writeFileSync(CAPACITY_RECORD_PATH, `${JSON.stringify(record)}\n`, 'utf8');
|
|
265
|
+
}
|
|
266
|
+
} catch { /* fail open: negotiation is advisory */ }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function readCapacityRecord() {
|
|
270
|
+
try {
|
|
271
|
+
const parsed = JSON.parse(fs.readFileSync(CAPACITY_RECORD_PATH, 'utf8'));
|
|
272
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
273
|
+
} catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
166
278
|
// Debounce/state file shared by the context warning and the gateway model-swap note.
|
|
167
279
|
const GUARD_STATE_PATH = path.join(projectRoot, '.ukit', 'storage', 'cache', 'context-guard-state.json');
|
|
168
280
|
|
|
@@ -194,11 +306,9 @@ let persistedState = { ...guardState };
|
|
|
194
306
|
// keep working through swaps, and on a tool error after a swap, re-check the tool and
|
|
195
307
|
// call it again instead of stopping. Runs before the ratio early-exit because a swap
|
|
196
308
|
// can happen at any context level.
|
|
197
|
-
const RECENT_MODEL_WINDOW = 40;
|
|
198
309
|
const SWAP_NOTE_COOLDOWN_MS = 60 * 60 * 1000;
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
const currentModel = recentModels[recentModels.length - 1] || null;
|
|
310
|
+
// recentModels/currentModel were hoisted above for capacity negotiation; modelSequence
|
|
311
|
+
// was collected in the single pass over entries.
|
|
202
312
|
let previousModel = null;
|
|
203
313
|
for (let i = recentModels.length - 2; i >= 0; i -= 1) {
|
|
204
314
|
if (recentModels[i] !== currentModel) {
|
|
@@ -286,10 +396,10 @@ const run = readRunCursor();
|
|
|
286
396
|
|
|
287
397
|
const lines_out = [];
|
|
288
398
|
if (ratio >= 1) {
|
|
289
|
-
lines_out.push(`UKIT CONTEXT ALERT — live context ~${estimatedTokens.toLocaleString()} tokens, at/over the ${hardCap.toLocaleString()} cap (${pct}
|
|
399
|
+
lines_out.push(`UKIT CONTEXT ALERT — live context ~${estimatedTokens.toLocaleString()} tokens, at/over the ${hardCap.toLocaleString()} cap (${pct}%, capacitySource=${capacitySource}).`);
|
|
290
400
|
lines_out.push('The next few turns risk "Your input exceeds the context window of this model".');
|
|
291
401
|
} else {
|
|
292
|
-
lines_out.push(`UKIT CONTEXT WARNING — live context ~${estimatedTokens.toLocaleString()} tokens (${pct}% of the ${hardCap.toLocaleString()} cap).`);
|
|
402
|
+
lines_out.push(`UKIT CONTEXT WARNING — live context ~${estimatedTokens.toLocaleString()} tokens (${pct}% of the ${hardCap.toLocaleString()} cap, capacitySource=${capacitySource}).`);
|
|
293
403
|
}
|
|
294
404
|
|
|
295
405
|
if (estimatePartial) {
|
|
@@ -14,16 +14,42 @@
|
|
|
14
14
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
15
|
# shellcheck source=/dev/null
|
|
16
16
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
17
|
+
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
18
|
+
# shared cleanup path before the staged payload is removed. A missing
|
|
19
|
+
# runtime (pre-install tree) simply leaves telemetry off.
|
|
20
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
17
21
|
trap ukit_cleanup_hook_input EXIT
|
|
18
22
|
# Bounded stdin (H01): stage before anything reads it; the shell var is capped.
|
|
19
23
|
ukit_stage_hook_input 2097152 truncate || exit 0
|
|
20
24
|
else
|
|
21
|
-
# Runtime helper missing (pre-install tree): bounded
|
|
22
|
-
#
|
|
25
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
26
|
+
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
27
|
+
# closes the pipe (R4.5). Read cap+1 in the background, bound the wait.
|
|
23
28
|
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
if [ -e /dev/fd/0 ]; then
|
|
30
|
+
exec 8<&0
|
|
31
|
+
head -c 2097153 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
32
|
+
else
|
|
33
|
+
head -c 2097153 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
34
|
+
fi
|
|
35
|
+
__ukit_reader=$!
|
|
36
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
37
|
+
case "$__ukit_stage_ms" in
|
|
38
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
39
|
+
esac
|
|
40
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
41
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
42
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
43
|
+
__ukit_waiter=$!
|
|
44
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
45
|
+
__ukit_stage_rc=$?
|
|
46
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
47
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
48
|
+
exec 8<&-
|
|
49
|
+
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
50
|
+
# A bounded cut (rc != 0) leaves a short payload that the JSON consumer
|
|
51
|
+
# already degrades on - the shared helper's truncate posture (R4.5).
|
|
52
|
+
if [ "$__ukit_size" -gt 2097152 ]; then
|
|
27
53
|
truncate -s 2097152 "$UKIT_INPUT_FILE" 2>/dev/null
|
|
28
54
|
fi
|
|
29
55
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
@@ -22,16 +22,42 @@
|
|
|
22
22
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
23
23
|
# shellcheck source=/dev/null
|
|
24
24
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
25
|
+
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
26
|
+
# shared cleanup path before the staged payload is removed. A missing
|
|
27
|
+
# runtime (pre-install tree) simply leaves telemetry off.
|
|
28
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
25
29
|
trap ukit_cleanup_hook_input EXIT
|
|
26
30
|
# Bounded stdin (H01): stage before anything reads it; Node gets a path, never the payload.
|
|
27
31
|
ukit_stage_hook_input 2097152 truncate || exit 0
|
|
28
32
|
else
|
|
29
|
-
# Runtime helper missing (pre-install tree): bounded
|
|
30
|
-
#
|
|
33
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
34
|
+
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
35
|
+
# closes the pipe (R4.5). Read cap+1 in the background, bound the wait.
|
|
31
36
|
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
if [ -e /dev/fd/0 ]; then
|
|
38
|
+
exec 8<&0
|
|
39
|
+
head -c 2097153 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
40
|
+
else
|
|
41
|
+
head -c 2097153 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
42
|
+
fi
|
|
43
|
+
__ukit_reader=$!
|
|
44
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
45
|
+
case "$__ukit_stage_ms" in
|
|
46
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
47
|
+
esac
|
|
48
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
49
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
50
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
51
|
+
__ukit_waiter=$!
|
|
52
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
53
|
+
__ukit_stage_rc=$?
|
|
54
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
55
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
56
|
+
exec 8<&-
|
|
57
|
+
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
58
|
+
# A bounded cut (rc != 0) leaves a short payload that the JSON consumer
|
|
59
|
+
# already degrades on - the shared helper's truncate posture (R4.5).
|
|
60
|
+
if [ "$__ukit_size" -gt 2097152 ]; then
|
|
35
61
|
truncate -s 2097152 "$UKIT_INPUT_FILE" 2>/dev/null
|
|
36
62
|
fi
|
|
37
63
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
@@ -5,16 +5,42 @@
|
|
|
5
5
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
6
|
# shellcheck source=/dev/null
|
|
7
7
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
8
|
+
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
9
|
+
# shared cleanup path before the staged payload is removed. A missing
|
|
10
|
+
# runtime (pre-install tree) simply leaves telemetry off.
|
|
11
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
8
12
|
trap ukit_cleanup_hook_input EXIT
|
|
9
13
|
# Bounded stdin (H01): stage before anything reads it; the shell var is capped.
|
|
10
14
|
ukit_stage_hook_input 2097152 truncate || exit 0
|
|
11
15
|
else
|
|
12
|
-
# Runtime helper missing (pre-install tree): bounded
|
|
13
|
-
#
|
|
16
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
17
|
+
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
18
|
+
# closes the pipe (R4.5). Read cap+1 in the background, bound the wait.
|
|
14
19
|
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
if [ -e /dev/fd/0 ]; then
|
|
21
|
+
exec 8<&0
|
|
22
|
+
head -c 2097153 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
23
|
+
else
|
|
24
|
+
head -c 2097153 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
25
|
+
fi
|
|
26
|
+
__ukit_reader=$!
|
|
27
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
28
|
+
case "$__ukit_stage_ms" in
|
|
29
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
30
|
+
esac
|
|
31
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
32
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
33
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
34
|
+
__ukit_waiter=$!
|
|
35
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
36
|
+
__ukit_stage_rc=$?
|
|
37
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
38
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
39
|
+
exec 8<&-
|
|
40
|
+
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
41
|
+
# A bounded cut (rc != 0) leaves a short payload that the JSON consumer
|
|
42
|
+
# already degrades on - the shared helper's truncate posture (R4.5).
|
|
43
|
+
if [ "$__ukit_size" -gt 2097152 ]; then
|
|
18
44
|
truncate -s 2097152 "$UKIT_INPUT_FILE" 2>/dev/null
|
|
19
45
|
fi
|
|
20
46
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
@@ -5,16 +5,42 @@
|
|
|
5
5
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
6
|
# shellcheck source=/dev/null
|
|
7
7
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
8
|
+
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
9
|
+
# shared cleanup path before the staged payload is removed. A missing
|
|
10
|
+
# runtime (pre-install tree) simply leaves telemetry off.
|
|
11
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
8
12
|
trap ukit_cleanup_hook_input EXIT
|
|
9
13
|
# Bounded stdin (H01): stage before anything reads it; the shell var is capped.
|
|
10
14
|
ukit_stage_hook_input 2097152 truncate || exit 0
|
|
11
15
|
else
|
|
12
|
-
# Runtime helper missing (pre-install tree): bounded
|
|
13
|
-
#
|
|
16
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
17
|
+
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
18
|
+
# closes the pipe (R4.5). Read cap+1 in the background, bound the wait.
|
|
14
19
|
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
if [ -e /dev/fd/0 ]; then
|
|
21
|
+
exec 8<&0
|
|
22
|
+
head -c 2097153 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
23
|
+
else
|
|
24
|
+
head -c 2097153 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
25
|
+
fi
|
|
26
|
+
__ukit_reader=$!
|
|
27
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
28
|
+
case "$__ukit_stage_ms" in
|
|
29
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
30
|
+
esac
|
|
31
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
32
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
33
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
34
|
+
__ukit_waiter=$!
|
|
35
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
36
|
+
__ukit_stage_rc=$?
|
|
37
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
38
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
39
|
+
exec 8<&-
|
|
40
|
+
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
41
|
+
# A bounded cut (rc != 0) leaves a short payload that the JSON consumer
|
|
42
|
+
# already degrades on - the shared helper's truncate posture (R4.5).
|
|
43
|
+
if [ "$__ukit_size" -gt 2097152 ]; then
|
|
18
44
|
truncate -s 2097152 "$UKIT_INPUT_FILE" 2>/dev/null
|
|
19
45
|
fi
|
|
20
46
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
@@ -5,16 +5,42 @@
|
|
|
5
5
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
6
|
# shellcheck source=/dev/null
|
|
7
7
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
8
|
+
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
9
|
+
# shared cleanup path before the staged payload is removed. A missing
|
|
10
|
+
# runtime (pre-install tree) simply leaves telemetry off.
|
|
11
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
8
12
|
trap ukit_cleanup_hook_input EXIT
|
|
9
13
|
# Bounded stdin (H01): stage before anything reads it; the shell var is capped.
|
|
10
14
|
ukit_stage_hook_input 2097152 truncate || exit 0
|
|
11
15
|
else
|
|
12
|
-
# Runtime helper missing (pre-install tree): bounded
|
|
13
|
-
#
|
|
16
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
17
|
+
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
18
|
+
# closes the pipe (R4.5). Read cap+1 in the background, bound the wait.
|
|
14
19
|
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
if [ -e /dev/fd/0 ]; then
|
|
21
|
+
exec 8<&0
|
|
22
|
+
head -c 2097153 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
23
|
+
else
|
|
24
|
+
head -c 2097153 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
25
|
+
fi
|
|
26
|
+
__ukit_reader=$!
|
|
27
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
28
|
+
case "$__ukit_stage_ms" in
|
|
29
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
30
|
+
esac
|
|
31
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
32
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
33
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
34
|
+
__ukit_waiter=$!
|
|
35
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
36
|
+
__ukit_stage_rc=$?
|
|
37
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
38
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
39
|
+
exec 8<&-
|
|
40
|
+
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
41
|
+
# A bounded cut (rc != 0) leaves a short payload that the JSON consumer
|
|
42
|
+
# already degrades on - the shared helper's truncate posture (R4.5).
|
|
43
|
+
if [ "$__ukit_size" -gt 2097152 ]; then
|
|
18
44
|
truncate -s 2097152 "$UKIT_INPUT_FILE" 2>/dev/null
|
|
19
45
|
fi
|
|
20
46
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
@@ -5,17 +5,43 @@
|
|
|
5
5
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
6
|
# shellcheck source=/dev/null
|
|
7
7
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
8
|
+
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
9
|
+
# shared cleanup path before the staged payload is removed. A missing
|
|
10
|
+
# runtime (pre-install tree) simply leaves telemetry off.
|
|
11
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
8
12
|
trap ukit_cleanup_hook_input EXIT
|
|
9
13
|
# Bounded stdin (H01): receipts can legitimately be large, so the staged file
|
|
10
14
|
# is the transport and the shell var only mirrors the capped payload.
|
|
11
15
|
ukit_stage_hook_input 33554432 temp-file || exit 0
|
|
12
16
|
else
|
|
13
|
-
# Runtime helper missing (pre-install tree): bounded
|
|
14
|
-
#
|
|
17
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
18
|
+
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
19
|
+
# closes the pipe (R4.5). Read cap+1 in the background, bound the wait.
|
|
15
20
|
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
if [ -e /dev/fd/0 ]; then
|
|
22
|
+
exec 8<&0
|
|
23
|
+
head -c 33554433 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
24
|
+
else
|
|
25
|
+
head -c 33554433 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
26
|
+
fi
|
|
27
|
+
__ukit_reader=$!
|
|
28
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
29
|
+
case "$__ukit_stage_ms" in
|
|
30
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
31
|
+
esac
|
|
32
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
33
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
34
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
35
|
+
__ukit_waiter=$!
|
|
36
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
37
|
+
__ukit_stage_rc=$?
|
|
38
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
39
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
40
|
+
exec 8<&-
|
|
41
|
+
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
42
|
+
# A bounded cut (rc != 0) leaves a short payload that the JSON consumer
|
|
43
|
+
# already degrades on - the shared helper's truncate posture (R4.5).
|
|
44
|
+
if [ "$__ukit_size" -gt 33554432 ]; then
|
|
19
45
|
truncate -s 33554432 "$UKIT_INPUT_FILE" 2>/dev/null
|
|
20
46
|
fi
|
|
21
47
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
# 1. Read/Grep of secret files (.env*, *.pem, id_rsa, credentials.json, ...)
|
|
12
12
|
# 2. Bash commands that dump secrets (cat .env, bare `env`, curl -u user:pass,
|
|
13
13
|
# URL-embedded credentials, any high-confidence token in the command text)
|
|
14
|
-
# 3. UserPromptSubmit prompt text containing high-confidence secret patterns
|
|
14
|
+
# 3. UserPromptSubmit prompt text containing high-confidence secret patterns;
|
|
15
|
+
# complete machine-generated envelopes are delivered with a redacted advisory
|
|
15
16
|
#
|
|
16
17
|
# FAIL-CLOSED on detection (exit 2). Never echoes the detected value — only a
|
|
17
18
|
# redacted preview plus a truncated sha256 (12 hex chars: enough for the human
|
|
@@ -32,6 +33,10 @@ PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
|
32
33
|
# unknown overflow and never materializes it in a shell variable.
|
|
33
34
|
# shellcheck source=/dev/null
|
|
34
35
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
36
|
+
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
37
|
+
# shared cleanup path before the staged payload is removed. A missing
|
|
38
|
+
# runtime (pre-install tree) simply leaves telemetry off.
|
|
39
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
35
40
|
trap ukit_cleanup_hook_input EXIT
|
|
36
41
|
ukit_stage_hook_input 2097152 refuse
|
|
37
42
|
STAGE_RC=$?
|
|
@@ -39,11 +44,34 @@ if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
|
39
44
|
exit "$STAGE_RC"
|
|
40
45
|
fi
|
|
41
46
|
else
|
|
42
|
-
# Runtime helper missing (pre-install tree):
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if [
|
|
47
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
48
|
+
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
49
|
+
# closes the pipe (R4.5). Read cap+1 in the background, bound the wait.
|
|
50
|
+
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
51
|
+
if [ -e /dev/fd/0 ]; then
|
|
52
|
+
exec 8<&0
|
|
53
|
+
head -c 2097153 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
54
|
+
else
|
|
55
|
+
head -c 2097153 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
56
|
+
fi
|
|
57
|
+
__ukit_reader=$!
|
|
58
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
59
|
+
case "$__ukit_stage_ms" in
|
|
60
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
61
|
+
esac
|
|
62
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
63
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
64
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
65
|
+
__ukit_waiter=$!
|
|
66
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
67
|
+
__ukit_stage_rc=$?
|
|
68
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
69
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
70
|
+
exec 8<&-
|
|
71
|
+
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
72
|
+
if [ "$__ukit_stage_rc" -ne 0 ] || [ "$__ukit_size" -gt 2097152 ]; then
|
|
73
|
+
# Fail-closed: an unstaged/oversized payload was never inspected
|
|
74
|
+
# (exit 2 blocks; 1 would be a non-blocking hook error).
|
|
47
75
|
rm -f "$UKIT_INPUT_FILE"
|
|
48
76
|
exit 2
|
|
49
77
|
fi
|
|
@@ -289,9 +317,17 @@ if (findings.length === 0) {
|
|
|
289
317
|
// ("Waiting for N background agents"). These envelopes flow with a redacted
|
|
290
318
|
// advisory: labels + counts only, never values. Everything a human types —
|
|
291
319
|
// and every other event/channel — stays fail-closed below.
|
|
292
|
-
const MACHINE_ENVELOPE_RE =
|
|
320
|
+
const MACHINE_ENVELOPE_RE = /^(?:task-notification|system-reminder|cross-session-message)$/i;
|
|
321
|
+
function isCompleteMachineEnvelope(text) {
|
|
322
|
+
const trimmed = String(text || '').trim();
|
|
323
|
+
const opening = /^<([a-z-]+)(?:\s[^>]*)?>/i.exec(trimmed);
|
|
324
|
+
if (!opening || !MACHINE_ENVELOPE_RE.test(opening[1])) return false;
|
|
325
|
+
const tag = opening[1];
|
|
326
|
+
const closing = new RegExp(`</${tag}>$`, 'i');
|
|
327
|
+
return closing.test(trimmed);
|
|
328
|
+
}
|
|
293
329
|
const promptText = textChannels.length > 0 ? textChannels[0].text : null;
|
|
294
|
-
if (event === 'UserPromptSubmit' && promptText &&
|
|
330
|
+
if (event === 'UserPromptSubmit' && promptText && isCompleteMachineEnvelope(promptText)) {
|
|
295
331
|
const labels = [...new Set(findings.map((f) => f.label))];
|
|
296
332
|
process.stdout.write(`${JSON.stringify({
|
|
297
333
|
systemMessage: `UKit sensitive-data gate: ${findings.length} secret-shaped value(s) inside a machine-generated notification (${labels.slice(0, 3).join('; ')}${labels.length > 3 ? '; …' : ''}). Delivered without blocking — treat as fixture/log plumbing, not a typed secret. Never repeat the values in your reply.`,
|
|
@@ -5,17 +5,43 @@
|
|
|
5
5
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
6
|
# shellcheck source=/dev/null
|
|
7
7
|
if source "$SCRIPT_DIR/../ukit/runtime/hook-input.sh" 2>/dev/null; then
|
|
8
|
+
# TASK-019: arm advisory latency telemetry; the finish row is written by the
|
|
9
|
+
# shared cleanup path before the staged payload is removed. A missing
|
|
10
|
+
# runtime (pre-install tree) simply leaves telemetry off.
|
|
11
|
+
source "$SCRIPT_DIR/../ukit/runtime/hook-telemetry.sh" 2>/dev/null || true
|
|
8
12
|
trap ukit_cleanup_hook_input EXIT
|
|
9
13
|
# Bounded stdin (H01): stage before anything reads it; the shell var is capped,
|
|
10
14
|
# env passes only the staged-file path — never the payload.
|
|
11
15
|
ukit_stage_hook_input 2097152 truncate || exit 0
|
|
12
16
|
else
|
|
13
|
-
# Runtime helper missing (pre-install tree): bounded
|
|
14
|
-
#
|
|
17
|
+
# Runtime helper missing (pre-install tree): the SAME bounded staging,
|
|
18
|
+
# inline - `cat >/dev/null` used to block forever on a producer that never
|
|
19
|
+
# closes the pipe (R4.5). Read cap+1 in the background, bound the wait.
|
|
15
20
|
UKIT_INPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/ukit-hook-in.XXXXXX")"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
if [ -e /dev/fd/0 ]; then
|
|
22
|
+
exec 8<&0
|
|
23
|
+
head -c 2097153 <&8 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
24
|
+
else
|
|
25
|
+
head -c 2097153 > "$UKIT_INPUT_FILE" 2>/dev/null &
|
|
26
|
+
fi
|
|
27
|
+
__ukit_reader=$!
|
|
28
|
+
__ukit_stage_ms="${UKIT_HOOK_STAGE_MS:-2000}"
|
|
29
|
+
case "$__ukit_stage_ms" in
|
|
30
|
+
''|*[!0-9]*) __ukit_stage_ms=2000 ;;
|
|
31
|
+
esac
|
|
32
|
+
[ "$__ukit_stage_ms" -gt 0 ] 2>/dev/null || __ukit_stage_ms=2000
|
|
33
|
+
printf -v __ukit_stage_s '%d.%03d' $((__ukit_stage_ms / 1000)) $((__ukit_stage_ms % 1000))
|
|
34
|
+
( sleep "$__ukit_stage_s" 2>/dev/null; kill -9 "$__ukit_reader" 2>/dev/null ) <&- >/dev/null 2>&1 &
|
|
35
|
+
__ukit_waiter=$!
|
|
36
|
+
wait "$__ukit_reader" 2>/dev/null
|
|
37
|
+
__ukit_stage_rc=$?
|
|
38
|
+
kill "$__ukit_waiter" 2>/dev/null
|
|
39
|
+
wait "$__ukit_waiter" 2>/dev/null
|
|
40
|
+
exec 8<&-
|
|
41
|
+
__ukit_size="$(wc -c < "$UKIT_INPUT_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
42
|
+
# A bounded cut (rc != 0) leaves a short payload that the JSON consumer
|
|
43
|
+
# already degrades on - the shared helper's truncate posture (R4.5).
|
|
44
|
+
if [ "$__ukit_size" -gt 2097152 ]; then
|
|
19
45
|
truncate -s 2097152 "$UKIT_INPUT_FILE" 2>/dev/null
|
|
20
46
|
fi
|
|
21
47
|
trap '[ -n "$UKIT_INPUT_FILE" ] && rm -f "$UKIT_INPUT_FILE"' EXIT
|