agent-control-plane 0.6.0 → 0.7.1
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 +38 -5
- package/package.json +5 -3
- package/tools/bin/adapter-capabilities.sh +84 -0
- package/tools/bin/adapter-interface.sh +97 -0
- package/tools/bin/claude-adapter.sh +73 -0
- package/tools/bin/codex-adapter.sh +123 -0
- package/tools/bin/flow-config-lib.sh +13 -3508
- package/tools/bin/flow-execution-lib.sh +243 -0
- package/tools/bin/flow-forge-lib.sh +1770 -0
- package/tools/bin/flow-profile-lib.sh +335 -0
- package/tools/bin/flow-provider-lib.sh +981 -0
- package/tools/bin/flow-session-lib.sh +317 -0
- package/tools/bin/kilo-adapter.sh +108 -0
- package/tools/bin/ollama-adapter.sh +160 -0
- package/tools/bin/openclaw-adapter.sh +69 -0
- package/tools/bin/opencode-adapter.sh +98 -0
- package/tools/bin/pi-adapter.sh +95 -0
- package/tools/bin/run-with-adapter.sh +34 -0
- package/tools/dashboard/app.js +40 -3
- package/tools/dashboard/requirements.txt +3 -0
- package/tools/dashboard/server.py +250 -152
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
flow_provider_pool_state_get() {
|
|
2
|
+
local config_file="${1:?config file required}"
|
|
3
|
+
local pool_name="${2:?pool name required}"
|
|
4
|
+
local backend=""
|
|
5
|
+
local model=""
|
|
6
|
+
local state_root=""
|
|
7
|
+
local provider_key=""
|
|
8
|
+
local state_file=""
|
|
9
|
+
local attempts="0"
|
|
10
|
+
local next_attempt_epoch="0"
|
|
11
|
+
local next_attempt_at=""
|
|
12
|
+
local last_reason=""
|
|
13
|
+
local updated_at=""
|
|
14
|
+
local ready="yes"
|
|
15
|
+
local valid="yes"
|
|
16
|
+
local now_epoch=""
|
|
17
|
+
local safe_profile=""
|
|
18
|
+
local bypass_profile=""
|
|
19
|
+
local claude_model=""
|
|
20
|
+
local claude_permission_mode=""
|
|
21
|
+
local claude_effort=""
|
|
22
|
+
local claude_timeout_seconds=""
|
|
23
|
+
local claude_max_attempts=""
|
|
24
|
+
local claude_retry_backoff_seconds=""
|
|
25
|
+
local openclaw_model=""
|
|
26
|
+
local openclaw_thinking=""
|
|
27
|
+
local openclaw_timeout_seconds=""
|
|
28
|
+
local ollama_model=""
|
|
29
|
+
local ollama_base_url=""
|
|
30
|
+
local ollama_timeout_seconds=""
|
|
31
|
+
local pi_model=""
|
|
32
|
+
local pi_thinking=""
|
|
33
|
+
local pi_timeout_seconds=""
|
|
34
|
+
local opencode_model=""
|
|
35
|
+
local opencode_timeout_seconds=""
|
|
36
|
+
local kilo_model=""
|
|
37
|
+
local kilo_timeout_seconds=""
|
|
38
|
+
|
|
39
|
+
backend="$(flow_provider_pool_backend "${config_file}" "${pool_name}")"
|
|
40
|
+
safe_profile="$(flow_provider_pool_safe_profile "${config_file}" "${pool_name}")"
|
|
41
|
+
bypass_profile="$(flow_provider_pool_bypass_profile "${config_file}" "${pool_name}")"
|
|
42
|
+
claude_model="$(flow_provider_pool_claude_model "${config_file}" "${pool_name}")"
|
|
43
|
+
claude_permission_mode="$(flow_provider_pool_claude_permission_mode "${config_file}" "${pool_name}")"
|
|
44
|
+
claude_effort="$(flow_provider_pool_claude_effort "${config_file}" "${pool_name}")"
|
|
45
|
+
claude_timeout_seconds="$(flow_provider_pool_claude_timeout_seconds "${config_file}" "${pool_name}")"
|
|
46
|
+
claude_max_attempts="$(flow_provider_pool_claude_max_attempts "${config_file}" "${pool_name}")"
|
|
47
|
+
claude_retry_backoff_seconds="$(flow_provider_pool_claude_retry_backoff_seconds "${config_file}" "${pool_name}")"
|
|
48
|
+
openclaw_model="$(flow_provider_pool_openclaw_model "${config_file}" "${pool_name}")"
|
|
49
|
+
openclaw_thinking="$(flow_provider_pool_openclaw_thinking "${config_file}" "${pool_name}")"
|
|
50
|
+
openclaw_timeout_seconds="$(flow_provider_pool_openclaw_timeout_seconds "${config_file}" "${pool_name}")"
|
|
51
|
+
ollama_model="$(flow_provider_pool_ollama_model "${config_file}" "${pool_name}")"
|
|
52
|
+
ollama_base_url="$(flow_provider_pool_ollama_base_url "${config_file}" "${pool_name}")"
|
|
53
|
+
ollama_timeout_seconds="$(flow_provider_pool_ollama_timeout_seconds "${config_file}" "${pool_name}")"
|
|
54
|
+
pi_model="$(flow_provider_pool_pi_model "${config_file}" "${pool_name}")"
|
|
55
|
+
pi_thinking="$(flow_provider_pool_pi_thinking "${config_file}" "${pool_name}")"
|
|
56
|
+
pi_timeout_seconds="$(flow_provider_pool_pi_timeout_seconds "${config_file}" "${pool_name}")"
|
|
57
|
+
opencode_model="$(flow_provider_pool_opencode_model "${config_file}" "${pool_name}")"
|
|
58
|
+
opencode_timeout_seconds="$(flow_provider_pool_opencode_timeout_seconds "${config_file}" "${pool_name}")"
|
|
59
|
+
kilo_model="$(flow_provider_pool_kilo_model "${config_file}" "${pool_name}")"
|
|
60
|
+
kilo_timeout_seconds="$(flow_provider_pool_kilo_timeout_seconds "${config_file}" "${pool_name}")"
|
|
61
|
+
model="$(flow_provider_pool_model_identity "${config_file}" "${pool_name}")"
|
|
62
|
+
|
|
63
|
+
case "${backend}" in
|
|
64
|
+
codex)
|
|
65
|
+
[[ -n "${safe_profile}" && -n "${bypass_profile}" ]] || valid="no"
|
|
66
|
+
;;
|
|
67
|
+
claude)
|
|
68
|
+
[[ -n "${claude_model}" && -n "${claude_permission_mode}" && -n "${claude_effort}" && -n "${claude_timeout_seconds}" && -n "${claude_max_attempts}" && -n "${claude_retry_backoff_seconds}" ]] || valid="no"
|
|
69
|
+
;;
|
|
70
|
+
openclaw)
|
|
71
|
+
[[ -n "${openclaw_model}" && -n "${openclaw_thinking}" && -n "${openclaw_timeout_seconds}" ]] || valid="no"
|
|
72
|
+
;;
|
|
73
|
+
ollama)
|
|
74
|
+
[[ -n "${ollama_model}" ]] || valid="no"
|
|
75
|
+
;;
|
|
76
|
+
pi)
|
|
77
|
+
[[ -n "${pi_model}" ]] || valid="no"
|
|
78
|
+
;;
|
|
79
|
+
opencode)
|
|
80
|
+
[[ -n "${opencode_model}" && -n "${opencode_timeout_seconds}" ]] || valid="no"
|
|
81
|
+
;;
|
|
82
|
+
kilo)
|
|
83
|
+
[[ -n "${kilo_model}" && -n "${kilo_timeout_seconds}" ]] || valid="no"
|
|
84
|
+
;;
|
|
85
|
+
*)
|
|
86
|
+
valid="no"
|
|
87
|
+
;;
|
|
88
|
+
esac
|
|
89
|
+
|
|
90
|
+
if [[ "${valid}" == "yes" && -n "${model}" ]]; then
|
|
91
|
+
state_root="$(flow_resolve_state_root "${config_file}")"
|
|
92
|
+
provider_key="$(flow_sanitize_provider_key "${backend}-${model}")"
|
|
93
|
+
state_file="${state_root}/retries/providers/${provider_key}.env"
|
|
94
|
+
|
|
95
|
+
if [[ -f "${state_file}" ]]; then
|
|
96
|
+
set -a
|
|
97
|
+
# shellcheck source=/dev/null
|
|
98
|
+
source "${state_file}"
|
|
99
|
+
set +a
|
|
100
|
+
attempts="${ATTEMPTS:-0}"
|
|
101
|
+
next_attempt_epoch="${NEXT_ATTEMPT_EPOCH:-0}"
|
|
102
|
+
next_attempt_at="${NEXT_ATTEMPT_AT:-}"
|
|
103
|
+
last_reason="${LAST_REASON:-}"
|
|
104
|
+
updated_at="${UPDATED_AT:-}"
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
now_epoch="$(date +%s)"
|
|
108
|
+
if [[ "${next_attempt_epoch}" =~ ^[0-9]+$ ]] && (( next_attempt_epoch > now_epoch )); then
|
|
109
|
+
ready="no"
|
|
110
|
+
fi
|
|
111
|
+
else
|
|
112
|
+
ready="no"
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
printf 'POOL_NAME=%s\n' "${pool_name}"
|
|
116
|
+
printf 'VALID=%s\n' "${valid}"
|
|
117
|
+
printf 'BACKEND=%s\n' "${backend}"
|
|
118
|
+
printf 'MODEL=%s\n' "${model}"
|
|
119
|
+
printf 'PROVIDER_KEY=%s\n' "${provider_key}"
|
|
120
|
+
printf 'ATTEMPTS=%s\n' "${attempts}"
|
|
121
|
+
printf 'NEXT_ATTEMPT_EPOCH=%s\n' "${next_attempt_epoch}"
|
|
122
|
+
printf 'NEXT_ATTEMPT_AT=%s\n' "${next_attempt_at}"
|
|
123
|
+
printf 'READY=%s\n' "${ready}"
|
|
124
|
+
printf 'LAST_REASON=%s\n' "${last_reason}"
|
|
125
|
+
printf 'UPDATED_AT=%s\n' "${updated_at}"
|
|
126
|
+
printf 'SAFE_PROFILE=%s\n' "${safe_profile}"
|
|
127
|
+
printf 'BYPASS_PROFILE=%s\n' "${bypass_profile}"
|
|
128
|
+
printf 'CLAUDE_MODEL=%s\n' "${claude_model}"
|
|
129
|
+
printf 'CLAUDE_PERMISSION_MODE=%s\n' "${claude_permission_mode}"
|
|
130
|
+
printf 'CLAUDE_EFFORT=%s\n' "${claude_effort}"
|
|
131
|
+
printf 'CLAUDE_TIMEOUT_SECONDS=%s\n' "${claude_timeout_seconds}"
|
|
132
|
+
printf 'CLAUDE_MAX_ATTEMPTS=%s\n' "${claude_max_attempts}"
|
|
133
|
+
printf 'CLAUDE_RETRY_BACKOFF_SECONDS=%s\n' "${claude_retry_backoff_seconds}"
|
|
134
|
+
printf 'OPENCLAW_MODEL=%s\n' "${openclaw_model}"
|
|
135
|
+
printf 'OPENCLAW_THINKING=%s\n' "${openclaw_thinking}"
|
|
136
|
+
printf 'OPENCLAW_TIMEOUT_SECONDS=%s\n' "${openclaw_timeout_seconds}"
|
|
137
|
+
printf 'OLLAMA_MODEL=%s\n' "${ollama_model}"
|
|
138
|
+
printf 'OLLAMA_BASE_URL=%s\n' "${ollama_base_url}"
|
|
139
|
+
printf 'OLLAMA_TIMEOUT_SECONDS=%s\n' "${ollama_timeout_seconds}"
|
|
140
|
+
printf 'PI_MODEL=%s\n' "${pi_model}"
|
|
141
|
+
printf 'PI_THINKING=%s\n' "${pi_thinking}"
|
|
142
|
+
printf 'PI_TIMEOUT_SECONDS=%s\n' "${pi_timeout_seconds}"
|
|
143
|
+
printf 'OPENCODE_MODEL=%s\n' "${opencode_model}"
|
|
144
|
+
printf 'OPENCODE_TIMEOUT_SECONDS=%s\n' "${opencode_timeout_seconds}"
|
|
145
|
+
printf 'KILO_MODEL=%s\n' "${kilo_model}"
|
|
146
|
+
printf 'KILO_TIMEOUT_SECONDS=%s\n' "${kilo_timeout_seconds}"
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
flow_selected_provider_pool_env() {
|
|
150
|
+
local config_file="${1:-}"
|
|
151
|
+
local pool_name=""
|
|
152
|
+
local candidate=""
|
|
153
|
+
local candidate_valid=""
|
|
154
|
+
local candidate_ready=""
|
|
155
|
+
local candidate_next_epoch="0"
|
|
156
|
+
local exhausted_candidate=""
|
|
157
|
+
local exhausted_epoch=""
|
|
158
|
+
|
|
159
|
+
if [[ -z "${config_file}" ]]; then
|
|
160
|
+
config_file="$(resolve_flow_config_yaml "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}")"
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
if ! flow_provider_pools_enabled "${config_file}"; then
|
|
164
|
+
return 1
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
while IFS= read -r pool_name; do
|
|
168
|
+
[[ -n "${pool_name}" ]] || continue
|
|
169
|
+
candidate="$(flow_provider_pool_state_get "${config_file}" "${pool_name}")"
|
|
170
|
+
candidate_valid="$(awk -F= '/^VALID=/{print $2}' <<<"${candidate}")"
|
|
171
|
+
[[ "${candidate_valid}" == "yes" ]] || continue
|
|
172
|
+
|
|
173
|
+
candidate_ready="$(awk -F= '/^READY=/{print $2}' <<<"${candidate}")"
|
|
174
|
+
if [[ "${candidate_ready}" == "yes" ]]; then
|
|
175
|
+
printf '%s\n' "${candidate}"
|
|
176
|
+
printf 'POOLS_EXHAUSTED=no\n'
|
|
177
|
+
printf 'SELECTION_REASON=ready\n'
|
|
178
|
+
return 0
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
candidate_next_epoch="$(awk -F= '/^NEXT_ATTEMPT_EPOCH=/{print $2}' <<<"${candidate}")"
|
|
182
|
+
if [[ -z "${exhausted_candidate}" ]]; then
|
|
183
|
+
exhausted_candidate="${candidate}"
|
|
184
|
+
exhausted_epoch="${candidate_next_epoch}"
|
|
185
|
+
continue
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
if [[ "${candidate_next_epoch}" =~ ^[0-9]+$ && "${exhausted_epoch}" =~ ^[0-9]+$ ]] && (( candidate_next_epoch < exhausted_epoch )); then
|
|
189
|
+
exhausted_candidate="${candidate}"
|
|
190
|
+
exhausted_epoch="${candidate_next_epoch}"
|
|
191
|
+
fi
|
|
192
|
+
done < <(flow_provider_pool_names "${config_file}")
|
|
193
|
+
|
|
194
|
+
[[ -n "${exhausted_candidate}" ]] || return 1
|
|
195
|
+
|
|
196
|
+
printf '%s\n' "${exhausted_candidate}"
|
|
197
|
+
printf 'POOLS_EXHAUSTED=yes\n'
|
|
198
|
+
printf 'SELECTION_REASON=all-cooldown\n'
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
flow_resolve_issue_session_prefix() {
|
|
202
|
+
local config_file="${1:-}"
|
|
203
|
+
local default_value=""
|
|
204
|
+
if [[ -z "${config_file}" ]]; then
|
|
205
|
+
config_file="$(resolve_flow_config_yaml "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}")"
|
|
206
|
+
fi
|
|
207
|
+
default_value="$(flow_default_issue_session_prefix "${config_file}")"
|
|
208
|
+
flow_env_or_config "${config_file}" "ACP_ISSUE_SESSION_PREFIX F_LOSNING_ISSUE_SESSION_PREFIX" "session_naming.issue_prefix" "${default_value}"
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
flow_resolve_pr_session_prefix() {
|
|
212
|
+
local config_file="${1:-}"
|
|
213
|
+
local default_value=""
|
|
214
|
+
if [[ -z "${config_file}" ]]; then
|
|
215
|
+
config_file="$(resolve_flow_config_yaml "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}")"
|
|
216
|
+
fi
|
|
217
|
+
default_value="$(flow_default_pr_session_prefix "${config_file}")"
|
|
218
|
+
flow_env_or_config "${config_file}" "ACP_PR_SESSION_PREFIX F_LOSNING_PR_SESSION_PREFIX" "session_naming.pr_prefix" "${default_value}"
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
flow_resolve_issue_branch_prefix() {
|
|
222
|
+
local config_file="${1:-}"
|
|
223
|
+
local default_value=""
|
|
224
|
+
if [[ -z "${config_file}" ]]; then
|
|
225
|
+
config_file="$(resolve_flow_config_yaml "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}")"
|
|
226
|
+
fi
|
|
227
|
+
default_value="$(flow_default_issue_branch_prefix "${config_file}")"
|
|
228
|
+
flow_env_or_config "${config_file}" "ACP_ISSUE_BRANCH_PREFIX F_LOSNING_ISSUE_BRANCH_PREFIX" "session_naming.issue_branch_prefix" "${default_value}"
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
flow_resolve_pr_worktree_branch_prefix() {
|
|
232
|
+
local config_file="${1:-}"
|
|
233
|
+
local default_value=""
|
|
234
|
+
if [[ -z "${config_file}" ]]; then
|
|
235
|
+
config_file="$(resolve_flow_config_yaml "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}")"
|
|
236
|
+
fi
|
|
237
|
+
default_value="$(flow_default_pr_worktree_branch_prefix "${config_file}")"
|
|
238
|
+
flow_env_or_config "${config_file}" "ACP_PR_WORKTREE_BRANCH_PREFIX F_LOSNING_PR_WORKTREE_BRANCH_PREFIX" "session_naming.pr_worktree_branch_prefix" "${default_value}"
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
flow_resolve_managed_pr_branch_globs() {
|
|
242
|
+
local config_file="${1:-}"
|
|
243
|
+
local default_value=""
|
|
244
|
+
if [[ -z "${config_file}" ]]; then
|
|
245
|
+
config_file="$(resolve_flow_config_yaml "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}")"
|
|
246
|
+
fi
|
|
247
|
+
default_value="$(flow_default_managed_pr_branch_globs "${config_file}")"
|
|
248
|
+
flow_env_or_config "${config_file}" "ACP_MANAGED_PR_BRANCH_GLOBS F_LOSNING_MANAGED_PR_BRANCH_GLOBS" "session_naming.managed_pr_branch_globs" "${default_value}"
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
flow_escape_regex() {
|
|
252
|
+
local raw_value="${1:-}"
|
|
253
|
+
python3 - "${raw_value}" <<'PY'
|
|
254
|
+
import re
|
|
255
|
+
import sys
|
|
256
|
+
|
|
257
|
+
print(re.escape(sys.argv[1]))
|
|
258
|
+
PY
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
flow_managed_pr_prefixes() {
|
|
262
|
+
local config_file="${1:-}"
|
|
263
|
+
local managed_globs=""
|
|
264
|
+
local branch_glob=""
|
|
265
|
+
local prefix=""
|
|
266
|
+
|
|
267
|
+
managed_globs="$(flow_resolve_managed_pr_branch_globs "${config_file}")"
|
|
268
|
+
for branch_glob in ${managed_globs}; do
|
|
269
|
+
prefix="${branch_glob%\*}"
|
|
270
|
+
[[ -n "${prefix}" ]] || continue
|
|
271
|
+
printf '%s\n' "${prefix}"
|
|
272
|
+
done
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
flow_managed_pr_prefixes_json() {
|
|
276
|
+
local config_file="${1:-}"
|
|
277
|
+
local prefixes=()
|
|
278
|
+
local prefix=""
|
|
279
|
+
|
|
280
|
+
while IFS= read -r prefix; do
|
|
281
|
+
[[ -n "${prefix}" ]] || continue
|
|
282
|
+
prefixes+=("${prefix}")
|
|
283
|
+
done < <(flow_managed_pr_prefixes "${config_file}")
|
|
284
|
+
|
|
285
|
+
python3 - "${prefixes[@]}" <<'PY'
|
|
286
|
+
import json
|
|
287
|
+
import sys
|
|
288
|
+
|
|
289
|
+
print(json.dumps(sys.argv[1:]))
|
|
290
|
+
PY
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
flow_managed_issue_branch_regex() {
|
|
294
|
+
local config_file="${1:-}"
|
|
295
|
+
local prefix=""
|
|
296
|
+
local normalized_prefix=""
|
|
297
|
+
local escaped_prefix=""
|
|
298
|
+
local joined=""
|
|
299
|
+
|
|
300
|
+
while IFS= read -r prefix; do
|
|
301
|
+
[[ -n "${prefix}" ]] || continue
|
|
302
|
+
normalized_prefix="${prefix%/}"
|
|
303
|
+
escaped_prefix="$(flow_escape_regex "${normalized_prefix}")"
|
|
304
|
+
if [[ -n "${joined}" ]]; then
|
|
305
|
+
joined="${joined}|${escaped_prefix}"
|
|
306
|
+
else
|
|
307
|
+
joined="${escaped_prefix}"
|
|
308
|
+
fi
|
|
309
|
+
done < <(flow_managed_pr_prefixes "${config_file}")
|
|
310
|
+
|
|
311
|
+
if [[ -z "${joined}" ]]; then
|
|
312
|
+
joined="$(flow_escape_regex "agent/$(flow_resolve_adapter_id "${config_file}")")"
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
printf '^(?:%s)/issue-(?<id>[0-9]+)(?:-|$)\n' "${joined}"
|
|
316
|
+
}
|
|
317
|
+
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# kilo-adapter.sh
|
|
3
|
+
# Adapter implementation for Kilo Code
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
source "${SCRIPT_DIR}/adapter-interface.sh"
|
|
9
|
+
source "${SCRIPT_DIR}/adapter-capabilities.sh"
|
|
10
|
+
|
|
11
|
+
ADAPTER_ID="kilo"
|
|
12
|
+
ADAPTER_NAME="Kilo Code"
|
|
13
|
+
ADAPTER_TYPE="cloud-api"
|
|
14
|
+
ADAPTER_VERSION="1.0.0"
|
|
15
|
+
ADAPTER_MODEL="${KILO_MODEL:-anthropic/claude-sonnet-4-20250514}"
|
|
16
|
+
|
|
17
|
+
# Kilo capabilities
|
|
18
|
+
ADAPTER_CAP_CLOUD_API=true
|
|
19
|
+
ADAPTER_CAP_STREAMING=true
|
|
20
|
+
ADAPTER_CAP_JSON_OUTPUT=true
|
|
21
|
+
ADAPTER_CAP_MAX_TIMEOUT=900
|
|
22
|
+
|
|
23
|
+
adapter_info() {
|
|
24
|
+
cat <<EOF
|
|
25
|
+
id=${ADAPTER_ID}
|
|
26
|
+
name=${ADAPTER_NAME}
|
|
27
|
+
type=${ADAPTER_TYPE}
|
|
28
|
+
version=${ADAPTER_VERSION}
|
|
29
|
+
model=${ADAPTER_MODEL}
|
|
30
|
+
EOF
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
adapter_health_check() {
|
|
34
|
+
if ! command -v kilo >/dev/null 2>&1; then
|
|
35
|
+
echo "ERROR: kilo CLI not found in PATH"
|
|
36
|
+
return 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Verify kilo can actually run (version check)
|
|
40
|
+
if ! kilo --version >/dev/null 2>&1; then
|
|
41
|
+
echo "ERROR: kilo CLI cannot run (check installation)"
|
|
42
|
+
return 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
local version
|
|
46
|
+
version="$(kilo --version 2>/dev/null || true)"
|
|
47
|
+
if [[ -z "$version" ]]; then
|
|
48
|
+
echo "WARN: Could not detect kilo version"
|
|
49
|
+
else
|
|
50
|
+
echo "INFO: Kilo version: $version"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Verify model is specified
|
|
54
|
+
if [[ -z "${ADAPTER_MODEL}" ]]; then
|
|
55
|
+
echo "WARN: No model specified for Kilo adapter"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
echo "OK: Kilo adapter healthy (model: ${ADAPTER_MODEL})"
|
|
59
|
+
return 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
adapter_run() {
|
|
63
|
+
local mode="${1:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
64
|
+
local session="${2:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
65
|
+
local worktree="${3:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
66
|
+
local prompt_file="${4:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
67
|
+
|
|
68
|
+
# Validate prompt file
|
|
69
|
+
if [[ ! -f "${prompt_file}" ]]; then
|
|
70
|
+
echo "ERROR: Prompt file not found: ${prompt_file}"
|
|
71
|
+
return 1
|
|
72
|
+
fi
|
|
73
|
+
if [[ ! -s "${prompt_file}" ]]; then
|
|
74
|
+
echo "ERROR: Prompt file is empty: ${prompt_file}"
|
|
75
|
+
return 1
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
local timeout_seconds="${KILO_TIMEOUT_SECONDS:-900}"
|
|
79
|
+
|
|
80
|
+
echo "Kilo adapter: Running session ${session} with model ${ADAPTER_MODEL}"
|
|
81
|
+
|
|
82
|
+
cd "${worktree}" || return 1
|
|
83
|
+
|
|
84
|
+
prompt="$(cat "${prompt_file}")"
|
|
85
|
+
|
|
86
|
+
# Run kilo and capture output
|
|
87
|
+
local output
|
|
88
|
+
if ! output="$(timeout "${timeout_seconds}" kilo --model "${ADAPTER_MODEL}" "${prompt}" 2>&1)"; then
|
|
89
|
+
echo "ERROR: Kilo run failed or timed out after ${timeout_seconds}s"
|
|
90
|
+
return 1
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# Validate JSON stream output (kilo outputs JSON events)
|
|
94
|
+
if ! echo "$output" | python3 -c "import sys, json; [json.loads(line) for line in sys.stdin if line.strip()]" 2>/dev/null; then
|
|
95
|
+
echo "WARN: Kilo output is not valid JSON stream"
|
|
96
|
+
else
|
|
97
|
+
echo "INFO: Kilo output validated as JSON stream"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
echo "Kilo adapter: Session ${session} completed"
|
|
101
|
+
return 0
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
105
|
+
adapter_info
|
|
106
|
+
echo "---"
|
|
107
|
+
adapter_health_check
|
|
108
|
+
fi
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ollama-adapter.sh
|
|
3
|
+
# Adapter implementation for Ollama local models
|
|
4
|
+
# Implements: adapter-interface.sh
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
source "${SCRIPT_DIR}/adapter-interface.sh"
|
|
10
|
+
source "${SCRIPT_DIR}/adapter-capabilities.sh"
|
|
11
|
+
|
|
12
|
+
# Ollama adapter metadata
|
|
13
|
+
ADAPTER_ID="ollama"
|
|
14
|
+
ADAPTER_NAME="Ollama Local Models"
|
|
15
|
+
ADAPTER_TYPE="local-model"
|
|
16
|
+
ADAPTER_VERSION="1.0.0"
|
|
17
|
+
ADAPTER_MODEL="${OLLAMA_MODEL:-qwen2.5-coder:7b}"
|
|
18
|
+
ADAPTER_BASE_URL="${OLLAMA_BASE_URL:-http://localhost:11434}"
|
|
19
|
+
|
|
20
|
+
# Ollama capabilities
|
|
21
|
+
ADAPTER_CAP_LOCAL_MODEL=true
|
|
22
|
+
ADAPTER_CAP_STREAMING=true
|
|
23
|
+
ADAPTER_CAP_TOOLS_SUPPORT=true
|
|
24
|
+
ADAPTER_CAP_CONTEXT_WINDOW=32768 # Default, will be detected dynamically
|
|
25
|
+
ADAPTER_CAP_MAX_TIMEOUT=3600
|
|
26
|
+
|
|
27
|
+
# Print adapter info
|
|
28
|
+
adapter_info() {
|
|
29
|
+
cat <<EOF
|
|
30
|
+
id=${ADAPTER_ID}
|
|
31
|
+
name=${ADAPTER_NAME}
|
|
32
|
+
type=${ADAPTER_TYPE}
|
|
33
|
+
version=${ADAPTER_VERSION}
|
|
34
|
+
model=${ADAPTER_MODEL}
|
|
35
|
+
base_url=${ADAPTER_BASE_URL}
|
|
36
|
+
EOF
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Health check: verify ollama is running and model is available
|
|
40
|
+
adapter_health_check() {
|
|
41
|
+
# Check if ollama is running
|
|
42
|
+
if ! curl -sf "${ADAPTER_BASE_URL}/api/tags" >/dev/null 2>&1; then
|
|
43
|
+
echo "ERROR: Ollama not reachable at ${ADAPTER_BASE_URL}"
|
|
44
|
+
return 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Check if model is available (try to pull if not)
|
|
48
|
+
if ! ollama list 2>/dev/null | grep -q "${ADAPTER_MODEL}"; then
|
|
49
|
+
echo "WARN: Model ${ADAPTER_MODEL} not found locally. Attempting pull..."
|
|
50
|
+
if ! ollama pull "${ADAPTER_MODEL}" 2>&1; then
|
|
51
|
+
echo "ERROR: Failed to pull model ${ADAPTER_MODEL}"
|
|
52
|
+
return 1
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Detect context window and update capability dynamically
|
|
57
|
+
local context_window
|
|
58
|
+
# Ollama API returns context_length inside model_info with architecture prefix
|
|
59
|
+
# e.g., qwen2.context_length, llama.context_length, etc.
|
|
60
|
+
if command -v jq &>/dev/null; then
|
|
61
|
+
context_window="$(curl -sf "${ADAPTER_BASE_URL}/api/show" -d "{\"name\":\"${ADAPTER_MODEL}\"}" 2>/dev/null | jq -r '.model_info // {} | to_entries[] | select(.key | endswith("context_length")) | .value' 2>/dev/null | head -1 || true)"
|
|
62
|
+
else
|
|
63
|
+
# Fallback: use grep for common patterns
|
|
64
|
+
context_window="$(curl -sf "${ADAPTER_BASE_URL}/api/show" -d "{\"name\":\"${ADAPTER_MODEL}\"}" 2>/dev/null | grep -o '"[a-z]*\.context_length":[0-9]*' | head -1 | grep -o '[0-9]*$' || true)"
|
|
65
|
+
fi
|
|
66
|
+
if [[ -n "$context_window" ]]; then
|
|
67
|
+
ADAPTER_CAP_CONTEXT_WINDOW="$context_window"
|
|
68
|
+
echo "INFO: Detected context window: $context_window tokens"
|
|
69
|
+
else
|
|
70
|
+
echo "WARN: Could not detect context window from Ollama API"
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
echo "OK: Ollama healthy, model ${ADAPTER_MODEL} available"
|
|
74
|
+
return 0
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Run a task using ollama
|
|
78
|
+
adapter_run() {
|
|
79
|
+
local mode="${1:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
80
|
+
local session="${2:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
81
|
+
local worktree="${3:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
82
|
+
local prompt_file="${4:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
83
|
+
|
|
84
|
+
# Validate prompt file
|
|
85
|
+
if [[ ! -f "${prompt_file}" ]]; then
|
|
86
|
+
echo "ERROR: Prompt file not found: ${prompt_file}"
|
|
87
|
+
return 1
|
|
88
|
+
fi
|
|
89
|
+
if [[ ! -s "${prompt_file}" ]]; then
|
|
90
|
+
echo "ERROR: Prompt file is empty: ${prompt_file}"
|
|
91
|
+
return 1
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
local timeout_seconds="${OLLAMA_TIMEOUT_SECONDS:-900}"
|
|
95
|
+
|
|
96
|
+
echo "Ollama adapter: Running session ${session} with model ${ADAPTER_MODEL}"
|
|
97
|
+
|
|
98
|
+
# Read the prompt
|
|
99
|
+
local prompt
|
|
100
|
+
prompt="$(cat "${prompt_file}")"
|
|
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
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
echo "Ollama adapter: Session ${session} completed"
|
|
123
|
+
return 0
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Status check (override default)
|
|
127
|
+
adapter_status() {
|
|
128
|
+
local runs_root="${1:?usage: adapter_status RUNS_ROOT SESSION}"
|
|
129
|
+
local session="${2:?usage: adapter_status RUNS_ROOT SESSION}"
|
|
130
|
+
local run_dir="${runs_root}/${session}"
|
|
131
|
+
|
|
132
|
+
if [[ ! -d "$run_dir" ]]; then
|
|
133
|
+
echo "NOT_FOUND"
|
|
134
|
+
return 1
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# Check for result file
|
|
138
|
+
if [[ -f "$run_dir/result.env" ]]; then
|
|
139
|
+
source "$run_dir/result.env"
|
|
140
|
+
echo "${OUTCOME:-UNKNOWN}"
|
|
141
|
+
return 0
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# Check if ollama process is running
|
|
145
|
+
if pgrep -f "ollama run ${ADAPTER_MODEL}" >/dev/null 2>&1; then
|
|
146
|
+
echo "RUNNING"
|
|
147
|
+
return 0
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
echo "UNKNOWN"
|
|
151
|
+
return 0
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Self-register: validate this adapter implements required functions
|
|
155
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
156
|
+
# Running directly - print info
|
|
157
|
+
adapter_info
|
|
158
|
+
echo "---"
|
|
159
|
+
adapter_health_check
|
|
160
|
+
fi
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# openclaw-adapter.sh
|
|
3
|
+
# Adapter implementation for OpenClaw.
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
source "${SCRIPT_DIR}/adapter-interface.sh"
|
|
9
|
+
source "${SCRIPT_DIR}/adapter-capabilities.sh"
|
|
10
|
+
|
|
11
|
+
ADAPTER_ID="openclaw"
|
|
12
|
+
ADAPTER_NAME="OpenClaw"
|
|
13
|
+
ADAPTER_TYPE="cloud-api"
|
|
14
|
+
ADAPTER_VERSION="1.0.0"
|
|
15
|
+
ADAPTER_MODEL="${OPENCLAW_MODEL:-openrouter/qwen/qwen3.5-plus:free}"
|
|
16
|
+
|
|
17
|
+
# OpenClaw capabilities
|
|
18
|
+
ADAPTER_CAP_CLOUD_API=true
|
|
19
|
+
ADAPTER_CAP_STREAMING=true
|
|
20
|
+
ADAPTER_CAP_JSON_OUTPUT=true
|
|
21
|
+
ADAPTER_CAP_RESIDENT_MODE=true
|
|
22
|
+
ADAPTER_CAP_MAX_TIMEOUT=900
|
|
23
|
+
|
|
24
|
+
adapter_info() {
|
|
25
|
+
cat <<EOF
|
|
26
|
+
id=${ADAPTER_ID}
|
|
27
|
+
name=${ADAPTER_NAME}
|
|
28
|
+
type=${ADAPTER_TYPE}
|
|
29
|
+
version=${ADAPTER_VERSION}
|
|
30
|
+
model=${ADAPTER_MODEL}
|
|
31
|
+
EOF
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
adapter_health_check() {
|
|
35
|
+
if ! command -v openclaw >/dev/null 2>&1; then
|
|
36
|
+
echo "ERROR: openclaw CLI not found in PATH"
|
|
37
|
+
return 1
|
|
38
|
+
fi
|
|
39
|
+
echo "OK: OpenClaw adapter healthy"
|
|
40
|
+
return 0
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
adapter_run() {
|
|
44
|
+
local mode="${1:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
45
|
+
local session="${2:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
46
|
+
local worktree="${3:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
47
|
+
local prompt_file="${4:?usage: adapter_run MODE SESSION WORKTREE PROMPT_FILE}"
|
|
48
|
+
|
|
49
|
+
local timeout_seconds="${OPENCLAW_TIMEOUT_SECONDS:-900}"
|
|
50
|
+
|
|
51
|
+
echo "OpenClaw adapter: Running session ${session}"
|
|
52
|
+
|
|
53
|
+
cd "${worktree}" || return 1
|
|
54
|
+
|
|
55
|
+
prompt="$(cat "${prompt_file}")"
|
|
56
|
+
|
|
57
|
+
if ! timeout "${timeout_seconds}" openclaw --model "${ADAPTER_MODEL}" "${prompt}" 2>&1; then
|
|
58
|
+
echo "ERROR: OpenClaw run failed"
|
|
59
|
+
return 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
return 0
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
66
|
+
adapter_info
|
|
67
|
+
echo "---"
|
|
68
|
+
adapter_health_check
|
|
69
|
+
fi
|