SPB Git

Repository Public

core-task-script.ts 3.9 KB 103 lines typescript

1/**2 * Static helper programs for durable tasks. Both texts are constants: the task command, host,3 * shell and paths reach them through files / CR_TASK_* environment variables, never by4 * interpolation into the script text.5 */67/**8 * Runner (POSIX sh), copied as `tasks/<id>/run.sh` (0700) and executed by tmux in `crt-<id>`.9 *10 *   command  ← `$D/command` (file)                 log   → `$D/log` via the capped sink11 *   started  → `$D/started` (mtime = start time)   exit  → `$D/exit` (atomic; mtime = end time)12 *   rusage   → `$D/time` (`/usr/bin/time -l`, local tasks, when available)13 *14 * TERM/HUP/INT are *trapped* (not ignored) so children keep the default disposition and die15 * on cancel, while the runner survives long enough to record the exit code.16 */17export const TASK_RUNNER_SH = `#!/bin/sh18# Generated by claude-remote runtime (static text; parameters come from files and CR_TASK_* env).19D=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) || exit 9720trap ':' TERM HUP INT21CMD=$(cat "$D/command") || exit 9722rm -f "$D/pipe"23mkfifo -m 600 "$D/pipe" || exit 9724# Job control puts the sink in its own process group: a cancel (TERM/KILL sent to this25# script's group) never reaches it, so it always drains the log to the end.26set -m27"$CR_TASK_NODE" "$CR_TASK_SINK" "$D/log" "$CR_TASK_LOG_CAP" <"$D/pipe" &28SINK=$!29set +m30: >"$D/started"31if [ "$CR_TASK_MODE" = ssh ]; then32  ssh -o BatchMode=yes -o ConnectTimeout=10 "$CR_TASK_HOST" -- "$CMD" >"$D/pipe" 2>&1 </dev/null33elif /usr/bin/time -l -o "$D/time" true >/dev/null 2>&1; then34  /usr/bin/time -l -o "$D/time" "$CR_TASK_SHELL" -lc "$CMD" >"$D/pipe" 2>&1 </dev/null35else36  rm -f "$D/time"37  "$CR_TASK_SHELL" -lc "$CMD" >"$D/pipe" 2>&1 </dev/null38fi39CODE=$?40while kill -0 "$SINK" 2>/dev/null; do wait "$SINK"; done41printf '%s\\n' "$CODE" >"$D/exit.tmp" && mv -f "$D/exit.tmp" "$D/exit"42rm -f "$D/pipe"43exit "$CODE"44`;4546/**47 * Log sink (CommonJS, run with the runtime's own node binary): appends stdin to the log file48 * until `cap` bytes, then keeps draining stdin to nowhere so the task is never blocked or49 * killed by a full log. Independent of the runtime process (survives its restarts).50 */51export const TASK_LOG_SINK_CJS = `'use strict';52const fs = require('fs');53const file = process.argv[2];54const cap = Number(process.argv[3]);55for (const s of ['SIGTERM', 'SIGHUP', 'SIGINT']) process.on(s, () => {});56const fd = fs.openSync(file, 'a', 0o600);57let size = 0;58try { size = fs.fstatSync(fd).size; } catch (e) {}59const buf = Buffer.allocUnsafe(65536);60for (;;) {61  let n;62  try {63    n = fs.readSync(0, buf, 0, buf.length, null);64  } catch (e) {65    if (e && e.code === 'EAGAIN') { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20); continue; }66    if (e && e.code === 'EINTR') continue;67    break;68  }69  if (n === 0) break;70  if (size < cap) {71    const w = Math.min(n, cap - size);72    fs.writeSync(fd, buf, 0, w);73    size += w;74  }75}76`;7778export interface TaskRusage {79  cpuSec: number | null;80  maxRssMb: number | null;81}8283/**84 * Parses the trailer written by BSD/macOS `/usr/bin/time -l` (decimal separator may be `.`85 * or `,` depending on the locale). Anything not recognised yields null — never a guess.86 */87export function parseTimeTrailer(text: string): TaskRusage {88  const num = (s: string): number => Number(s.replace(',', '.'));89  let cpuSec: number | null = null;90  let maxRssMb: number | null = null;91  const t = /([0-9]+[.,][0-9]+)\s+real\s+([0-9]+[.,][0-9]+)\s+user\s+([0-9]+[.,][0-9]+)\s+sys/.exec(text);92  if (t?.[2] !== undefined && t[3] !== undefined) {93    const v = num(t[2]) + num(t[3]);94    if (Number.isFinite(v)) cpuSec = Math.round(v * 1000) / 1000;95  }96  const r = /^\s*([0-9]+)\s+maximum resident set size/m.exec(text);97  if (r?.[1] !== undefined) {98    const bytes = Number(r[1]); // bytes on macOS99    if (Number.isFinite(bytes)) maxRssMb = Math.round((bytes / (1024 * 1024)) * 10) / 10;100  }101  return { cpuSec, maxRssMb };102}103
Files main
Menu

Theme