agent-control-plane 0.8.0 → 0.9.0

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/README.md CHANGED
@@ -1,3 +1,9 @@
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="assets/logo-dark.svg">
3
+ <source media="(prefers-color-scheme: light)" srcset="assets/logo-light.svg">
4
+ <img alt="ACP — agent-control-plane" src="assets/logo-light.svg" height="80">
5
+ </picture>
6
+
1
7
  # agent-control-plane (ACP) v0.7.1
2
8
 
3
9
  <p>
@@ -39,6 +45,7 @@ going completely off the rails.
39
45
  - Roadmap: [ROADMAP.md](./ROADMAP.md)
40
46
  - Architecture: [references/architecture.md](./references/architecture.md)
41
47
  - Commands: [references/commands.md](./references/commands.md)
48
+ - Examples: [docs/examples.md](./docs/examples.md)
42
49
 
43
50
  ## The Big Idea
44
51
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-control-plane",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Help a repo keep GitHub-driven coding agents running reliably without constant human babysitting",
5
5
  "homepage": "https://github.com/ducminhnguyen0319/agent-control-plane",
6
6
  "bugs": {
@@ -77,6 +77,7 @@
77
77
  "tools/vendor/codex-quota-manager/scripts"
78
78
  ],
79
79
  "publishConfig": {
80
- "access": "public"
80
+ "access": "public",
81
+ "provenance": true
81
82
  }
82
83
  }
@@ -81,6 +81,64 @@ adapter_load() {
81
81
  source "$impl"
82
82
  }
83
83
 
84
+ # Run a command with a wall-clock timeout. Uses GNU `timeout` / `gtimeout`
85
+ # when available, else falls back to a python3 wrapper, else perl. Streams
86
+ # the child's stdout/stderr to the caller. Exits 124 on timeout.
87
+ # Usage: adapter_run_with_timeout SECS CMD [ARGS...]
88
+ adapter_run_with_timeout() {
89
+ local secs="${1:?usage: adapter_run_with_timeout SECS CMD [ARGS...]}"
90
+ shift
91
+ if command -v gtimeout >/dev/null 2>&1; then
92
+ gtimeout "${secs}" "$@"
93
+ return $?
94
+ fi
95
+ if command -v timeout >/dev/null 2>&1; then
96
+ timeout "${secs}" "$@"
97
+ return $?
98
+ fi
99
+ if command -v python3 >/dev/null 2>&1; then
100
+ python3 - "${secs}" "$@" <<'PY'
101
+ import subprocess, sys
102
+ secs = float(sys.argv[1])
103
+ cmd = sys.argv[2:]
104
+ try:
105
+ proc = subprocess.Popen(cmd)
106
+ except FileNotFoundError as exc:
107
+ sys.stderr.write("adapter_run_with_timeout: %s\n" % exc)
108
+ sys.exit(127)
109
+ try:
110
+ proc.wait(timeout=secs)
111
+ except subprocess.TimeoutExpired:
112
+ proc.terminate()
113
+ try:
114
+ proc.wait(timeout=10)
115
+ except subprocess.TimeoutExpired:
116
+ proc.kill()
117
+ proc.wait()
118
+ sys.exit(124)
119
+ sys.exit(proc.returncode)
120
+ PY
121
+ return $?
122
+ fi
123
+ if command -v perl >/dev/null 2>&1; then
124
+ perl -e '
125
+ my $secs = shift @ARGV;
126
+ my $pid = fork();
127
+ die "fork: $!" unless defined $pid;
128
+ if ($pid == 0) { exec { $ARGV[0] } @ARGV or exit 127; }
129
+ local $SIG{ALRM} = sub { kill "TERM", $pid; sleep 10; kill "KILL", $pid; waitpid($pid, 0); exit 124; };
130
+ alarm $secs;
131
+ waitpid($pid, 0);
132
+ alarm 0;
133
+ my $rc = $? >> 8;
134
+ exit $rc;
135
+ ' "${secs}" "$@"
136
+ return $?
137
+ fi
138
+ echo "adapter_run_with_timeout: no gtimeout/timeout/python3/perl available; running without timeout" >&2
139
+ "$@"
140
+ }
141
+
84
142
  # Validate adapter implements required functions
85
143
  adapter_validate() {
86
144
  local missing=()
@@ -49,12 +49,22 @@ adapter_run() {
49
49
  local timeout_seconds="${CLAUDE_TIMEOUT_SECONDS:-900}"
50
50
 
51
51
  echo "Claude adapter: Running session ${session}"
52
-
52
+
53
53
  cd "${worktree}" || return 1
54
-
54
+
55
+ local sandbox_subdir="${ACP_SANDBOX_SUBDIR:-.openclaw-artifacts}"
56
+ local sandbox_run_dir="${worktree%/}/${sandbox_subdir}/${session}"
57
+ mkdir -p "${sandbox_run_dir}" 2>/dev/null || true
58
+ export ACP_SESSION="${session}"
59
+ export ACP_RUN_DIR="${sandbox_run_dir}"
60
+ export ACP_RESULT_FILE="${sandbox_run_dir}/result.env"
61
+ export F_LOSNING_SESSION="${session}"
62
+ export F_LOSNING_RUN_DIR="${sandbox_run_dir}"
63
+ export F_LOSNING_RESULT_FILE="${sandbox_run_dir}/result.env"
64
+
55
65
  prompt="$(cat "${prompt_file}")"
56
-
57
- if ! timeout "${timeout_seconds}" claude \
66
+
67
+ if ! adapter_run_with_timeout "${timeout_seconds}" claude \
58
68
  --permission-mode "${permission_mode}" \
59
69
  --model "${ADAPTER_MODEL}" \
60
70
  --print \
@@ -73,7 +73,7 @@ adapter_run() {
73
73
  cd "${worktree}" || return 1
74
74
 
75
75
  # Run claude with the prompt
76
- if ! timeout "${timeout_seconds}" claude \
76
+ if ! adapter_run_with_timeout "${timeout_seconds}" claude \
77
77
  --permission-mode "${permission_mode}" \
78
78
  --model "${ADAPTER_MODEL}" \
79
79
  --print \
@@ -1151,8 +1151,10 @@ flow_github_pr_view_json() {
1151
1151
  fi
1152
1152
 
1153
1153
  if flow_github_graphql_available "${repo_slug}" \
1154
- && pr_json="$(gh pr view "${pr_number}" -R "${repo_slug}" --json number,title,body,url,headRefName,baseRefName,mergeStateStatus,statusCheckRollup,labels,comments,state,isDraft 2>/dev/null)"; then
1155
- printf '%s\n' "${pr_json}"
1154
+ && pr_json="$(gh pr view "${pr_number}" -R "${repo_slug}" --json number,title,body,url,headRefName,baseRefName,mergeStateStatus,statusCheckRollup,labels,comments,state,isDraft,author 2>/dev/null)"; then
1155
+ printf '%s\n' "${pr_json}" \
1156
+ | jq '. + {authorLogin: ((.author.login) // "")}' 2>/dev/null \
1157
+ || printf '%s\n' "${pr_json}"
1156
1158
  return 0
1157
1159
  fi
1158
1160
 
@@ -88,12 +88,13 @@ printf 'WORKFLOW_CATALOG=%s\n' "${CATALOG_FILE}"
88
88
  printf 'WORKFLOW_CATALOG_EXISTS=%s\n' "${catalog_exists}"
89
89
  # Check timeout command (needed for scheduler cross-platform)
90
90
  if command -v timeout &>/dev/null; then
91
- printf 'TIMEOUT_CMD=%s\n' "timeout"
91
+ TIMEOUT_CMD="timeout"
92
92
  elif command -v gtimeout &>/dev/null; then
93
- printf 'TIMEOUT_CMD=%s\n' "gtimeout (from coreutils)"
93
+ TIMEOUT_CMD="gtimeout (from coreutils)"
94
94
  else
95
- printf 'TIMEOUT_CMD=%s\n' "missing (install coreutils for timeout command)"
95
+ TIMEOUT_CMD="missing (install coreutils for timeout command)"
96
96
  fi
97
+ printf 'TIMEOUT_CMD=%s\n' "${TIMEOUT_CMD}"
97
98
  printf 'DOCTOR_STATUS=%s\n' "${status}"
98
99
 
99
100
  # Provide clear next steps based on state
@@ -116,7 +117,7 @@ else
116
117
  fi
117
118
 
118
119
  # Cross-platform tips
119
- if [[ "${TIMEOUT_CMD}" == *"missing"* ]]; then
120
+ if [[ "${TIMEOUT_CMD:-}" == *"missing"* ]]; then
120
121
  printf '\n⚠ Cross-Platform Tip: Install coreutils for timeout command:\n'
121
122
  if [[ "$(uname -s)" == "Darwin" ]]; then
122
123
  printf ' macOS: brew install coreutils\n'
@@ -85,7 +85,7 @@ adapter_run() {
85
85
 
86
86
  # Run kilo and capture output
87
87
  local output
88
- if ! output="$(timeout "${timeout_seconds}" kilo --model "${ADAPTER_MODEL}" "${prompt}" 2>&1)"; then
88
+ if ! output="$(adapter_run_with_timeout "${timeout_seconds}" kilo --model "${ADAPTER_MODEL}" "${prompt}" 2>&1)"; then
89
89
  echo "ERROR: Kilo run failed or timed out after ${timeout_seconds}s"
90
90
  return 1
91
91
  fi
@@ -99,24 +99,9 @@ adapter_run() {
99
99
  local prompt
100
100
  prompt="$(cat "${prompt_file}")"
101
101
 
102
- # Run ollama with the prompt
103
- # Use perl for timeout on macOS (which lacks GNU timeout)
104
- if command -v timeout >/dev/null 2>&1; then
105
- if ! timeout "${timeout_seconds}" ollama run "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
106
- echo "ERROR: Ollama run failed or timed out after ${timeout_seconds}s"
107
- return 1
108
- fi
109
- elif command -v perl >/dev/null 2>&1; then
110
- if ! perl -e "alarm ${timeout_seconds}; exec @ARGV" ollama run "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
111
- echo "ERROR: Ollama run failed or timed out after ${timeout_seconds}s"
112
- return 1
113
- fi
114
- else
115
- # No timeout available, run without timeout
116
- if ! ollama run "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
117
- echo "ERROR: Ollama run failed"
118
- return 1
119
- fi
102
+ if ! adapter_run_with_timeout "${timeout_seconds}" ollama run "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
103
+ echo "ERROR: Ollama run failed or timed out after ${timeout_seconds}s"
104
+ return 1
120
105
  fi
121
106
 
122
107
  echo "Ollama adapter: Session ${session} completed"
@@ -54,7 +54,7 @@ adapter_run() {
54
54
 
55
55
  prompt="$(cat "${prompt_file}")"
56
56
 
57
- if ! timeout "${timeout_seconds}" openclaw --model "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
57
+ if ! adapter_run_with_timeout "${timeout_seconds}" openclaw --model "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
58
58
  echo "ERROR: OpenClaw run failed"
59
59
  return 1
60
60
  fi
@@ -83,7 +83,7 @@ adapter_run() {
83
83
 
84
84
  prompt="$(cat "${prompt_file}")"
85
85
 
86
- if ! timeout "${timeout_seconds}" crush --model "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
86
+ if ! adapter_run_with_timeout "${timeout_seconds}" crush --model "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
87
87
  echo "ERROR: OpenCode run failed"
88
88
  return 1
89
89
  fi
@@ -80,7 +80,7 @@ adapter_run() {
80
80
 
81
81
  prompt="$(cat "${prompt_file}")"
82
82
 
83
- if ! timeout "${timeout_seconds}" pi --model "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
83
+ if ! adapter_run_with_timeout "${timeout_seconds}" pi --model "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
84
84
  echo "ERROR: Pi run failed"
85
85
  return 1
86
86
  fi