mirror of
https://github.com/logseq/logseq.git
synced 2026-05-24 12:44:22 +00:00
299 lines
8.4 KiB
Bash
Executable File
299 lines
8.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage: agent_bridge_demo.sh [options]
|
|
|
|
Creates a fresh Logseq graph, starts `logseq agent bridge`, writes a routable
|
|
task after the bridge listener is ready, and verifies that a fake Codex worker
|
|
executed the task and that AgentBridge wrote agent-session-id.
|
|
|
|
Options:
|
|
--cli PATH Path to static/logseq-cli.js. Default: <repo-root>/static/logseq-cli.js
|
|
--root-dir DIR Logseq CLI root dir. Default: a new temp dir
|
|
--graph NAME Graph name. Default: agent-bridge-demo-<timestamp>
|
|
--repo-root DIR Repository root. Default: inferred from this script
|
|
--timeout-sec N Wait timeout for bridge events. Default: 45
|
|
-h, --help Show this help
|
|
USAGE
|
|
}
|
|
|
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
repo_root="$(cd "$script_dir/../.." && pwd)"
|
|
cli_path=""
|
|
root_dir=""
|
|
graph="agent-bridge-demo-$(date +%s)"
|
|
timeout_sec=45
|
|
agent_name="AgentBridgeDemo"
|
|
task_title="AgentBridge demo task: mark this block done"
|
|
expected_session="thread-agent-bridge-demo"
|
|
bridge_pid=""
|
|
graph_created=0
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--cli)
|
|
cli_path="$2"
|
|
shift 2
|
|
;;
|
|
--root-dir)
|
|
root_dir="$2"
|
|
shift 2
|
|
;;
|
|
--graph)
|
|
graph="$2"
|
|
shift 2
|
|
;;
|
|
--repo-root)
|
|
repo_root="$2"
|
|
shift 2
|
|
;;
|
|
--timeout-sec)
|
|
timeout_sec="$2"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown option: $1" >&2
|
|
usage >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
cli_path="${cli_path:-$repo_root/static/logseq-cli.js}"
|
|
root_dir="${root_dir:-$(mktemp -d "${TMPDIR:-/tmp}/logseq-agent-bridge-demo.XXXXXX")}"
|
|
config_path="$root_dir/cli.edn"
|
|
work_dir="$root_dir/agent-bridge-demo"
|
|
fake_bin="$work_dir/fake-bin"
|
|
bridge_log="$work_dir/agent-bridge.log"
|
|
bridge_err="$work_dir/agent-bridge.err"
|
|
codex_log="$work_dir/codex-invocations.jsonl"
|
|
|
|
cleanup() {
|
|
local status=$?
|
|
if [[ -n "${bridge_pid:-}" ]] && kill -0 "$bridge_pid" 2>/dev/null; then
|
|
kill "$bridge_pid" 2>/dev/null || true
|
|
wait "$bridge_pid" 2>/dev/null || true
|
|
fi
|
|
if [[ "$graph_created" -eq 1 ]]; then
|
|
node "$cli_path" --root-dir "$root_dir" --config "$config_path" --output json server stop --graph "$graph" >/dev/null 2>&1 || true
|
|
fi
|
|
exit "$status"
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
require_command() {
|
|
if ! command -v "$1" >/dev/null 2>&1; then
|
|
echo "Required command is missing: $1" >&2
|
|
exit 2
|
|
fi
|
|
}
|
|
|
|
json_result() {
|
|
python3 -c 'import json,sys; print(json.load(sys.stdin)["data"]["result"])'
|
|
}
|
|
|
|
json_result_first() {
|
|
python3 -c 'import json,sys; r=json.load(sys.stdin)["data"]["result"]; print(r[0] if isinstance(r, list) else r)'
|
|
}
|
|
|
|
run_cli_json() {
|
|
node "$cli_path" --root-dir "$root_dir" --config "$config_path" --output json "$@"
|
|
}
|
|
|
|
wait_for_file_text() {
|
|
local path="$1"
|
|
local text="$2"
|
|
local deadline=$((SECONDS + timeout_sec))
|
|
while (( SECONDS < deadline )); do
|
|
if [[ -f "$path" ]] && grep -Fq "$text" "$path"; then
|
|
return 0
|
|
fi
|
|
if [[ -n "${bridge_pid:-}" ]] && ! kill -0 "$bridge_pid" 2>/dev/null; then
|
|
echo "agent bridge exited before '$text'" >&2
|
|
[[ -f "$bridge_log" ]] && cat "$bridge_log" >&2
|
|
[[ -f "$bridge_err" ]] && cat "$bridge_err" >&2
|
|
exit 1
|
|
fi
|
|
sleep 0.2
|
|
done
|
|
echo "Timed out waiting for '$text' in $path" >&2
|
|
[[ -f "$bridge_log" ]] && cat "$bridge_log" >&2
|
|
[[ -f "$bridge_err" ]] && cat "$bridge_err" >&2
|
|
exit 1
|
|
}
|
|
|
|
query_task_status() {
|
|
run_cli_json query --graph "$graph" --query "[:find ?status-ident . :where [$1 :logseq.property/status ?status] [?status :db/ident ?status-ident]]" | json_result
|
|
}
|
|
|
|
query_agent_session() {
|
|
local payload
|
|
payload="$(run_cli_json query --graph "$graph" --query "[:find ?session . :where [$1 :logseq.property.agent/session-id ?session]]")"
|
|
python3 - "$root_dir" "$config_path" "$graph" "$cli_path" "$payload" <<'PY'
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
|
|
payload = json.loads(sys.argv[5])
|
|
value = payload.get("data", {}).get("result")
|
|
if isinstance(value, int):
|
|
root_dir, config_path, graph, cli_path = sys.argv[1:5]
|
|
query = f"[:find ?title . :where [{value} :block/title ?title]]"
|
|
resolved = subprocess.check_output(
|
|
[
|
|
"node",
|
|
cli_path,
|
|
"--root-dir",
|
|
root_dir,
|
|
"--config",
|
|
config_path,
|
|
"--output",
|
|
"json",
|
|
"query",
|
|
"--graph",
|
|
graph,
|
|
"--query",
|
|
query,
|
|
],
|
|
text=True,
|
|
)
|
|
value = json.loads(resolved)["data"]["result"]
|
|
print("" if value is None else value)
|
|
PY
|
|
}
|
|
|
|
write_fake_codex() {
|
|
mkdir -p "$fake_bin"
|
|
cat > "$fake_bin/codex" <<'FAKE_CODEX'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
if [[ "$#" -eq 1 && "$1" == "--version" ]]; then
|
|
echo "codex-cli 0.0.0-agent-bridge-demo"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ "$#" -ge 3 && "$1" == "exec" && "$2" == "--json" ]]; then
|
|
prompt="$3"
|
|
python3 - "$CODEX_FAKE_LOG" "$prompt" "$@" <<'PY'
|
|
import json
|
|
import pathlib
|
|
import sys
|
|
|
|
log_path = pathlib.Path(sys.argv[1])
|
|
prompt = sys.argv[2]
|
|
args = sys.argv[3:]
|
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
with log_path.open("a", encoding="utf8") as f:
|
|
f.write(json.dumps({"args": args, "prompt": prompt}, ensure_ascii=False) + "\n")
|
|
PY
|
|
block_uuid="$(python3 - "$prompt" <<'PY'
|
|
import re
|
|
import sys
|
|
|
|
match = re.search(r"^Block UUID:\s*([0-9a-fA-F-]+)\s*$", sys.argv[1], re.MULTILINE)
|
|
if not match:
|
|
raise SystemExit("Block UUID not found in AgentBridge prompt")
|
|
print(match.group(1))
|
|
PY
|
|
)"
|
|
node "$DEMO_CLI" --root-dir "$DEMO_ROOT_DIR" --config "$DEMO_CONFIG" --output json upsert task --graph "$DEMO_GRAPH" --uuid "$block_uuid" --status done >/dev/null
|
|
printf '{"type":"thread.started","thread_id":"thread-agent-bridge-demo"}\n'
|
|
exit 0
|
|
fi
|
|
|
|
echo "unexpected codex args: $*" >&2
|
|
exit 2
|
|
FAKE_CODEX
|
|
chmod +x "$fake_bin/codex"
|
|
}
|
|
|
|
require_command node
|
|
require_command python3
|
|
|
|
mkdir -p "$work_dir"
|
|
printf '{:output-format :json :agent-name "%s"}\n' "$agent_name" > "$config_path"
|
|
write_fake_codex
|
|
|
|
echo "creating graph: $graph"
|
|
echo "root dir: $root_dir"
|
|
run_cli_json graph create --graph "$graph" >/dev/null
|
|
graph_created=1
|
|
|
|
echo "starting agent bridge"
|
|
PATH="$fake_bin:$PATH" \
|
|
CODEX_FAKE_LOG="$codex_log" \
|
|
DEMO_CLI="$cli_path" \
|
|
DEMO_ROOT_DIR="$root_dir" \
|
|
DEMO_CONFIG="$config_path" \
|
|
DEMO_GRAPH="$graph" \
|
|
node "$cli_path" --root-dir "$root_dir" --config "$config_path" --output human agent bridge --graph "$graph" >"$bridge_log" 2>"$bridge_err" &
|
|
bridge_pid=$!
|
|
|
|
wait_for_file_text "$bridge_log" "listening graph changes"
|
|
|
|
task_id="$(run_cli_json upsert task --graph "$graph" --target-page AgentBridgeDemo --content "$task_title" --status todo | json_result_first)"
|
|
run_cli_json upsert block --graph "$graph" --id "$task_id" --update-properties "{\"Assignee\" \"$agent_name\"}" >/dev/null
|
|
|
|
deadline=$((SECONDS + timeout_sec))
|
|
task_status=""
|
|
agent_session=""
|
|
while (( SECONDS < deadline )); do
|
|
task_status="$(query_task_status "$task_id")"
|
|
agent_session="$(query_agent_session "$task_id")"
|
|
if [[ "$task_status" == "logseq.property/status.done" && "$agent_session" == "$expected_session" ]]; then
|
|
break
|
|
fi
|
|
if [[ -n "${bridge_pid:-}" ]] && ! kill -0 "$bridge_pid" 2>/dev/null; then
|
|
echo "agent bridge exited before verification completed" >&2
|
|
cat "$bridge_log" >&2
|
|
cat "$bridge_err" >&2
|
|
exit 1
|
|
fi
|
|
sleep 0.5
|
|
done
|
|
|
|
if [[ "$task_status" != "logseq.property/status.done" ]]; then
|
|
echo "Expected task status done, got: ${task_status:-<empty>}" >&2
|
|
cat "$bridge_log" >&2
|
|
cat "$bridge_err" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$agent_session" != "$expected_session" ]]; then
|
|
echo "Expected agent-session-id $expected_session, got: ${agent_session:-<empty>}" >&2
|
|
cat "$bridge_log" >&2
|
|
cat "$bridge_err" >&2
|
|
exit 1
|
|
fi
|
|
|
|
python3 - "$codex_log" "$task_title" "$graph" <<'PY'
|
|
import json
|
|
import pathlib
|
|
import sys
|
|
|
|
log_path = pathlib.Path(sys.argv[1])
|
|
task_title = sys.argv[2]
|
|
graph = sys.argv[3]
|
|
lines = [json.loads(line) for line in log_path.read_text(encoding="utf8").splitlines() if line.strip()]
|
|
if len(lines) != 1:
|
|
raise SystemExit(f"expected one Codex invocation, got {len(lines)}")
|
|
prompt = lines[0]["prompt"]
|
|
if task_title not in prompt:
|
|
raise SystemExit("task title missing from Codex prompt")
|
|
if f"Graph: {graph}" not in prompt:
|
|
raise SystemExit("graph missing from Codex prompt")
|
|
if "Block UUID:" not in prompt:
|
|
raise SystemExit("block uuid missing from Codex prompt")
|
|
PY
|
|
|
|
echo "task status: done"
|
|
echo "agent-session-id: $agent_session"
|
|
echo "agent bridge demo completed"
|