@cyanautomation/kaseki-agent 1.64.2 → 1.65.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanautomation/kaseki-agent",
3
- "version": "1.64.2",
3
+ "version": "1.65.2",
4
4
  "description": "Admin/helper/doctor toolbox and local API client for Kaseki diagnostics, setup, and API-backed coding-agent task workflows",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -57,7 +57,7 @@
57
57
  "test:cli:install-local": "npm link && kaseki-agent --version && npm unlink -g @cyanautomation/kaseki-agent",
58
58
  "test:cli:install-npx": "npx . --version 2>&1 | grep -q 'version' && echo 'npx test passed'",
59
59
  "test:cli:verify": "npm run build && kaseki-agent --help && kaseki-agent doctor --help && kaseki-agent run --help && kaseki-agent list --help && kaseki-agent report --help && kaseki-agent status --help && kaseki-agent cancel --help",
60
- "test:ci": "npm run build && npm run type-check && jest --passWithNoTests && bash run-kaseki-json.test.sh && bash tests/allowlist-glob.test.sh && bash tests/restore-disallowed-changes.test.sh && bash tests/auto-lint-cleanup-allowlist.test.sh && bash tests/auto-lint-cleanup-classification.test.sh && bash tests/validation-allowlist-state.test.sh && bash tests/trailing-whitespace-cleanup.test.sh && bash tests/dependency-cache-key.test.sh && bash tests/dependency-restore-mode.test.sh && bash tests/doctor-template-parity.test.sh && bash tests/npm-install-flags.test.sh && bash tests/repo-memory.test.sh && bash tests/pre-agent-validation-order.test.sh && bash tests/scouting-order.test.sh && bash tests/goal-check-malformed-artifact.test.sh && bash scripts/test-github-app.sh && bash tests/github-app-token-install-layout.test.sh && bash tests/github-preflight-helper-load.test.sh",
60
+ "test:ci": "npm run build && npm run type-check && jest --passWithNoTests && bash run-kaseki-json.test.sh && bash tests/allowlist-glob.test.sh && bash tests/restore-disallowed-changes.test.sh && bash tests/auto-lint-cleanup-allowlist.test.sh && bash tests/auto-lint-cleanup-classification.test.sh && bash tests/validation-allowlist-state.test.sh && bash tests/trailing-whitespace-cleanup.test.sh && bash tests/dependency-cache-key.test.sh && bash tests/dependency-restore-mode.test.sh && bash tests/startup-check-status-aggregation.test.sh && bash tests/doctor-template-parity.test.sh && bash tests/npm-install-flags.test.sh && bash tests/repo-memory.test.sh && bash tests/unset-path-defaults.test.sh && bash tests/pre-agent-validation-order.test.sh && bash tests/scouting-order.test.sh && bash tests/goal-check-malformed-artifact.test.sh && bash scripts/test-github-app.sh && bash tests/github-app-token-install-layout.test.sh && bash tests/github-preflight-helper-load.test.sh",
61
61
  "test:watch": "jest --watch",
62
62
  "test:coverage": "jest --coverage",
63
63
  "lint": "npm run lint:js && npm run lint:sh || true",
@@ -24,6 +24,15 @@ set -euo pipefail
24
24
  # - Express server starts listening on configured port
25
25
  # - Ready to accept requests
26
26
 
27
+ # Export shared container path defaults before command dispatch so every mode
28
+ # (including the default agent branch) inherits the same core paths.
29
+ export KASEKI_RESULTS_DIR="${KASEKI_RESULTS_DIR:-/results}"
30
+ export KASEKI_WORKSPACE_DIR="${KASEKI_WORKSPACE_DIR:-/workspace}"
31
+ export KASEKI_WORKSPACE_BASELINE_DIR="${KASEKI_WORKSPACE_BASELINE_DIR:-${KASEKI_WORKSPACE_DIR}/baseline}"
32
+ export KASEKI_APP_LIB_DIR="${KASEKI_APP_LIB_DIR:-/app/lib}"
33
+ export KASEKI_CACHE_DIR="${KASEKI_CACHE_DIR:-/cache}"
34
+ export KASEKI_AGENT_BIN="${KASEKI_AGENT_BIN:-/usr/local/bin/kaseki-agent}"
35
+
27
36
  # Phase 2: Run early startup checks to catch permission and config issues
28
37
  # This runs before any kaseki operation to prevent silent failures
29
38
  # Auto-remediation enabled by default (KASEKI_STARTUP_CHECK_AUTO_REMEDIATE=1)
@@ -40,6 +49,48 @@ if [ "${KASEKI_SKIP_STARTUP_CHECKS:-0}" != "1" ]; then
40
49
  }
41
50
  fi
42
51
 
52
+ # Phase 2b: Validate directory permissions for container user (UID 10000)
53
+ # This is a critical check before the API starts—if directories aren't writable,
54
+ # the API will fail to store results. Container runs as UID 10000:10000 per docker-compose.yml
55
+ validate_directory_permissions() {
56
+ local uid="${KASEKI_CONTAINER_UID:-10000}"
57
+ local gid="${KASEKI_CONTAINER_GID:-10000}"
58
+
59
+ # Directories that must be writable by the container user
60
+ local required_dirs=(
61
+ "${KASEKI_ROOT:-/agents}"
62
+ "${KASEKI_ROOT:-/agents}/kaseki-results"
63
+ "${KASEKI_ROOT:-/agents}/kaseki-runs"
64
+ "${KASEKI_ROOT:-/agents}/kaseki-cache"
65
+ )
66
+
67
+ for dir in "${required_dirs[@]}"; do
68
+ if [ ! -d "$dir" ]; then
69
+ echo "warning: required directory does not exist: $dir" >&2
70
+ echo " remediation: run 'sudo kaseki-agent host setup --fix' on the host" >&2
71
+ continue
72
+ fi
73
+
74
+ # Test write access by the current process (running as UID 10000)
75
+ if ! [ -w "$dir" ]; then
76
+ echo "error: directory is not writable by container user ($uid:$gid): $dir" >&2
77
+ echo " current ownership: $(stat -c '%U:%G' "$dir")" >&2
78
+ echo " remediation: run 'sudo kaseki-agent host setup --fix' on the host" >&2
79
+ return 1
80
+ fi
81
+ done
82
+
83
+ return 0
84
+ }
85
+
86
+ # Only validate permissions for API mode (not for agent or one-off runs)
87
+ if [ "${1:-agent}" = "api" ] || [ "${1:-agent}" = "kaseki-api" ]; then
88
+ validate_directory_permissions || {
89
+ echo "error: directory permissions validation failed; cannot start API" >&2
90
+ exit 1
91
+ }
92
+ fi
93
+
43
94
  # Phase 1: Dispatch to appropriate command handler
44
95
  case "${1:-agent}" in
45
96
  setup)
@@ -53,7 +104,7 @@ case "${1:-agent}" in
53
104
  # Health check and diagnostics
54
105
  # Usage: docker run kaseki-agent doctor
55
106
  shift || true
56
- exec /usr/local/bin/kaseki-agent --doctor
107
+ exec "$KASEKI_AGENT_BIN" --doctor
57
108
  ;;
58
109
 
59
110
  run-mode)
@@ -78,9 +129,7 @@ case "${1:-agent}" in
78
129
  # Set required variables and execute agent
79
130
  export OPENROUTER_API_KEY_FILE=/secrets/openrouter_api_key
80
131
  export KASEKI_INSTANCE="${KASEKI_INSTANCE:-kaseki-run}"
81
- export KASEKI_RESULTS_DIR="${KASEKI_RESULTS_DIR:-/results}"
82
-
83
- exec /usr/local/bin/kaseki-agent "$@"
132
+ exec "$KASEKI_AGENT_BIN" "$@"
84
133
  ;;
85
134
 
86
135
  setup-remote)
@@ -94,7 +143,7 @@ case "${1:-agent}" in
94
143
  # Standard agent execution (existing mode)
95
144
  # Usage: docker run kaseki-agent agent <repo> <ref>
96
145
  shift || true
97
- exec /usr/local/bin/kaseki-agent "$@"
146
+ exec "$KASEKI_AGENT_BIN" "$@"
98
147
  ;;
99
148
 
100
149
  api|kaseki-api)
@@ -372,7 +372,13 @@ run_privilege_tools_parallel() {
372
372
  : >"$stderr_file"
373
373
 
374
374
  local temp_dir
375
- temp_dir=$(mktemp -d)
375
+ # Create temp directory with TMPDIR support (for containerized environments)
376
+ # Fail fast with clear error if temp directory cannot be created
377
+ if ! temp_dir=$(TMPDIR="${TMPDIR:-/tmp}" mktemp -d 2>/dev/null); then
378
+ printf 'error: failed to create temporary directory for privilege probe in %s\n' "${TMPDIR:-/tmp}" >"$stderr_file"
379
+ return 1
380
+ fi
381
+
376
382
  local success_marker="$temp_dir/success"
377
383
  local pids=()
378
384
  local failure_stderr_files=()
@@ -656,21 +662,42 @@ ensure_git_safe_directory() {
656
662
 
657
663
  local status_code=0
658
664
 
659
- # Configure safe.directory for current context (usually root when run via sudo)
660
- local existing_safe_dirs
661
- existing_safe_dirs="$(git config --global --get-all safe.directory 2>/dev/null || true)"
662
- if printf '%s\n' "$existing_safe_dirs" | grep -Fxq "$KASEKI_CHECKOUT_DIR"; then
663
- printf 'ok: git safe.directory already present for current user context\n'
665
+ # Phase 1: Configure system-wide safe.directory (preferred approach for container isolation)
666
+ # System config (/etc/gitconfig) is visible to all users including container UID 10000
667
+ local existing_system_dirs
668
+ existing_system_dirs="$(git config --system --get-all safe.directory 2>/dev/null || true)"
669
+ if printf '%s\n' "$existing_system_dirs" | grep -Fxq "$KASEKI_CHECKOUT_DIR"; then
670
+ printf 'ok: git safe.directory already present in system config\n'
664
671
  else
665
- if git config --global --add safe.directory "$KASEKI_CHECKOUT_DIR" >/dev/null 2>&1; then
666
- printf 'ok: configured git safe.directory for current user context\n'
672
+ if git config --system --add safe.directory "$KASEKI_CHECKOUT_DIR" >/dev/null 2>&1; then
673
+ printf 'ok: configured git safe.directory in system config (/etc/gitconfig)\n'
667
674
  else
668
- printf 'warning: failed to configure git safe.directory for current user context\n'
675
+ printf 'warning: failed to configure git safe.directory in system config (requires root); falling back to user configs\n'
669
676
  status_code=1
670
677
  fi
671
678
  fi
672
679
 
673
- # If running via sudo, also configure safe.directory for the invoking user
680
+ # Phase 2: Fallback to user-level config for current context (usually root when run via sudo)
681
+ # Only perform this if system config failed
682
+ if [ "$status_code" -ne 0 ]; then
683
+ local existing_safe_dirs
684
+ existing_safe_dirs="$(git config --global --get-all safe.directory 2>/dev/null || true)"
685
+ if printf '%s\n' "$existing_safe_dirs" | grep -Fxq "$KASEKI_CHECKOUT_DIR"; then
686
+ printf 'ok: git safe.directory already present for current user context\n'
687
+ status_code=0
688
+ else
689
+ if git config --global --add safe.directory "$KASEKI_CHECKOUT_DIR" >/dev/null 2>&1; then
690
+ printf 'ok: configured git safe.directory for current user context\n'
691
+ status_code=0
692
+ else
693
+ printf 'warning: failed to configure git safe.directory for current user context\n'
694
+ status_code=1
695
+ fi
696
+ fi
697
+ fi
698
+
699
+ # Phase 3: If running via sudo, also configure safe.directory for the invoking user
700
+ # This ensures the user who invoked sudo can work with the checkout directly
674
701
  if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
675
702
  local invoking_user_config
676
703
  invoking_user_config="$(sudo -u "$SUDO_USER" git config --global --get-all safe.directory 2>/dev/null || true)"
@@ -681,17 +708,17 @@ ensure_git_safe_directory() {
681
708
  printf 'ok: configured git safe.directory for invoking user (%s)\n' "$SUDO_USER"
682
709
  else
683
710
  printf 'warning: failed to configure git safe.directory for invoking user (%s)\n' "$SUDO_USER"
684
- status_code=1
685
711
  fi
686
712
  fi
687
713
  fi
688
714
 
689
715
  if [ "$status_code" -ne 0 ]; then
690
- printf 'remediation: if you see dubious ownership errors, try manually running as the invoking user:\n'
716
+ printf 'remediation: git safe.directory configuration failed\n'
717
+ printf ' If you see dubious ownership errors, verify system config or try manually:\n'
718
+ printf ' sudo git config --system --add safe.directory "%s"\n' "$KASEKI_CHECKOUT_DIR"
691
719
  if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
692
- printf ' sudo -u %s git config --global --add safe.directory "%s"\n' "$SUDO_USER" "$KASEKI_CHECKOUT_DIR"
720
+ printf ' Or for invoking user: sudo -u %s git config --global --add safe.directory "%s"\n' "$SUDO_USER" "$KASEKI_CHECKOUT_DIR"
693
721
  fi
694
- printf ' And as root: git config --global --add safe.directory "%s"\n' "$KASEKI_CHECKOUT_DIR"
695
722
  fi
696
723
 
697
724
  return 0
@@ -707,6 +734,14 @@ verify_git_safe_directory() {
707
734
  return 0
708
735
  fi
709
736
 
737
+ # Check system-wide config first (preferred, visible to all users including containers)
738
+ local existing_system_dirs
739
+ existing_system_dirs="$(git config --system --get-all safe.directory 2>/dev/null || true)"
740
+ if printf '%s\n' "$existing_system_dirs" | grep -Fxq "$KASEKI_CHECKOUT_DIR"; then
741
+ return 0
742
+ fi
743
+
744
+ # Check current user context config (usually root)
710
745
  local existing_safe_dirs
711
746
  existing_safe_dirs="$(git config --global --get-all safe.directory 2>/dev/null || true)"
712
747
  if printf '%s\n' "$existing_safe_dirs" | grep -Fxq "$KASEKI_CHECKOUT_DIR"; then
@@ -724,10 +759,10 @@ verify_git_safe_directory() {
724
759
 
725
760
  # Safe.directory not configured anywhere; warn but don't fail (bootstrap might still work)
726
761
  printf 'warning: git safe.directory not found for checkout. If bootstrap fails with "dubious ownership", run:\n'
762
+ printf ' sudo git config --system --add safe.directory "%s"\n' "$KASEKI_CHECKOUT_DIR"
727
763
  if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
728
- printf ' sudo -u %s git config --global --add safe.directory "%s"\n' "$SUDO_USER" "$KASEKI_CHECKOUT_DIR"
764
+ printf ' Or: sudo -u %s git config --global --add safe.directory "%s"\n' "$SUDO_USER" "$KASEKI_CHECKOUT_DIR"
729
765
  fi
730
- printf ' git config --global --add safe.directory "%s"\n' "$KASEKI_CHECKOUT_DIR"
731
766
  return 0
732
767
  }
733
768
 
@@ -58,6 +58,21 @@ log_warn() { echo -e "${YELLOW}⚠${NC} $*" >&2; }
58
58
  log_error() { echo -e "${RED}✗${NC} $*" >&2; }
59
59
  log_info() { echo -e "${BLUE}ℹ${NC} $*" >&2; }
60
60
 
61
+ merge_startup_status() {
62
+ local current="$1"
63
+ local next="$2"
64
+
65
+ if [ "$current" -eq 2 ] || [ "$next" -eq 2 ]; then
66
+ printf '2'
67
+ elif [ "$current" -eq 3 ] || [ "$next" -eq 3 ]; then
68
+ printf '3'
69
+ elif [ "$next" -ne 0 ]; then
70
+ printf '%s' "$next"
71
+ else
72
+ printf '%s' "$current"
73
+ fi
74
+ }
75
+
61
76
  check_path_components_traversable() {
62
77
  local target_path="$1"
63
78
  local current=""
@@ -302,15 +317,15 @@ check_github_app_secrets() {
302
317
 
303
318
  check_secret_file_sources \
304
319
  "GitHub App ID" \
305
- "$github_app_id_file" || exit_code=$((exit_code > $? ? exit_code : $?))
320
+ "$github_app_id_file" || exit_code=$(merge_startup_status "$exit_code" "$?")
306
321
 
307
322
  check_secret_file_sources \
308
323
  "GitHub App Client ID" \
309
- "$github_app_client_id_file" || exit_code=$((exit_code > $? ? exit_code : $?))
324
+ "$github_app_client_id_file" || exit_code=$(merge_startup_status "$exit_code" "$?")
310
325
 
311
326
  check_secret_file_sources \
312
327
  "GitHub App private key" \
313
- "$github_app_private_key_file" || exit_code=$((exit_code > $? ? exit_code : $?))
328
+ "$github_app_private_key_file" || exit_code=$(merge_startup_status "$exit_code" "$?")
314
329
 
315
330
  if [ "$exit_code" -eq 0 ]; then
316
331
  return 0
@@ -526,38 +541,38 @@ main() {
526
541
 
527
542
  case "$MODE" in
528
543
  all)
529
- check_kaseki_root || overall_exit=$?
530
- check_subdirectories || overall_exit=$((overall_exit > $? ? overall_exit : $?))
531
- check_bootstrap_status || overall_exit=$((overall_exit > $? ? overall_exit : $?))
532
- check_secret_paths || overall_exit=$((overall_exit > $? ? overall_exit : $?))
533
- check_api_key || overall_exit=$((overall_exit > $? ? overall_exit : $?))
534
- check_github_app_secrets || overall_exit=$((overall_exit > $? ? overall_exit : $?))
535
- check_github_app_secret_paths || overall_exit=$((overall_exit > $? ? overall_exit : $?))
536
- check_git_safe_directory || overall_exit=$((overall_exit > $? ? overall_exit : $?))
544
+ check_kaseki_root || overall_exit=$(merge_startup_status "$overall_exit" "$?")
545
+ check_subdirectories || overall_exit=$(merge_startup_status "$overall_exit" "$?")
546
+ check_bootstrap_status || overall_exit=$(merge_startup_status "$overall_exit" "$?")
547
+ check_secret_paths || overall_exit=$(merge_startup_status "$overall_exit" "$?")
548
+ check_api_key || overall_exit=$(merge_startup_status "$overall_exit" "$?")
549
+ check_github_app_secrets || overall_exit=$(merge_startup_status "$overall_exit" "$?")
550
+ check_github_app_secret_paths || overall_exit=$(merge_startup_status "$overall_exit" "$?")
551
+ check_git_safe_directory || overall_exit=$(merge_startup_status "$overall_exit" "$?")
537
552
  ;;
538
553
  permissions)
539
- check_kaseki_root || overall_exit=$?
540
- check_subdirectories || overall_exit=$?
554
+ check_kaseki_root || overall_exit=$(merge_startup_status "$overall_exit" "$?")
555
+ check_subdirectories || overall_exit=$(merge_startup_status "$overall_exit" "$?")
541
556
  ;;
542
557
  bootstrap)
543
- check_bootstrap_status || overall_exit=$?
558
+ check_bootstrap_status || overall_exit=$(merge_startup_status "$overall_exit" "$?")
544
559
  ;;
545
560
  quick|boot)
546
- check_kaseki_root || overall_exit=$?
561
+ check_kaseki_root || overall_exit=$(merge_startup_status "$overall_exit" "$?")
547
562
  ;;
548
563
  worker)
549
- check_worker_mounts || overall_exit=$?
550
- check_results_writable || overall_exit=$((overall_exit > $? ? overall_exit : $?))
551
- check_secret_paths || overall_exit=$((overall_exit > $? ? overall_exit : $?))
552
- check_api_key || overall_exit=$((overall_exit > $? ? overall_exit : $?))
553
- check_github_app_secrets || overall_exit=$((overall_exit > $? ? overall_exit : $?))
554
- check_github_app_secret_paths || overall_exit=$((overall_exit > $? ? overall_exit : $?))
564
+ check_worker_mounts || overall_exit=$(merge_startup_status "$overall_exit" "$?")
565
+ check_results_writable || overall_exit=$(merge_startup_status "$overall_exit" "$?")
566
+ check_secret_paths || overall_exit=$(merge_startup_status "$overall_exit" "$?")
567
+ check_api_key || overall_exit=$(merge_startup_status "$overall_exit" "$?")
568
+ check_github_app_secrets || overall_exit=$(merge_startup_status "$overall_exit" "$?")
569
+ check_github_app_secret_paths || overall_exit=$(merge_startup_status "$overall_exit" "$?")
555
570
  ;;
556
571
  baseline-validation)
557
- check_kaseki_root || overall_exit=$?
558
- check_subdirectories || overall_exit=$((overall_exit > $? ? overall_exit : $?))
559
- check_bootstrap_status || overall_exit=$((overall_exit > $? ? overall_exit : $?))
560
- check_secret_paths || overall_exit=$((overall_exit > $? ? overall_exit : $?))
572
+ check_kaseki_root || overall_exit=$(merge_startup_status "$overall_exit" "$?")
573
+ check_subdirectories || overall_exit=$(merge_startup_status "$overall_exit" "$?")
574
+ check_bootstrap_status || overall_exit=$(merge_startup_status "$overall_exit" "$?")
575
+ check_secret_paths || overall_exit=$(merge_startup_status "$overall_exit" "$?")
561
576
  ;;
562
577
  *)
563
578
  log_error "Unknown mode: $MODE"