openclaw-node-harness 2.0.3 → 2.1.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 +646 -3
- package/bin/hyperagent.mjs +419 -0
- package/bin/mesh-agent.js +603 -81
- package/bin/mesh-bridge.js +340 -11
- package/bin/mesh-deploy-listener.js +119 -97
- package/bin/mesh-deploy.js +8 -0
- package/bin/mesh-task-daemon.js +1005 -40
- package/bin/mesh.js +423 -6
- package/config/claude-settings.json +95 -0
- package/config/daemon.json.template +2 -1
- package/config/git-hooks/pre-commit +13 -0
- package/config/git-hooks/pre-push +12 -0
- package/config/harness-rules.json +174 -0
- package/config/plan-templates/team-bugfix.yaml +52 -0
- package/config/plan-templates/team-deploy.yaml +50 -0
- package/config/plan-templates/team-feature.yaml +71 -0
- package/config/roles/qa-engineer.yaml +36 -0
- package/config/roles/solidity-dev.yaml +51 -0
- package/config/roles/tech-architect.yaml +36 -0
- package/config/rules/framework/solidity.md +22 -0
- package/config/rules/framework/typescript.md +21 -0
- package/config/rules/framework/unity.md +21 -0
- package/config/rules/universal/design-docs.md +18 -0
- package/config/rules/universal/git-hygiene.md +18 -0
- package/config/rules/universal/security.md +19 -0
- package/config/rules/universal/test-standards.md +19 -0
- package/identity/DELEGATION.md +6 -6
- package/install.sh +300 -8
- package/lib/circling-parser.js +119 -0
- package/lib/hyperagent-store.mjs +652 -0
- package/lib/kanban-io.js +59 -10
- package/lib/mcp-knowledge/bench.mjs +118 -0
- package/lib/mcp-knowledge/core.mjs +528 -0
- package/lib/mcp-knowledge/package.json +25 -0
- package/lib/mcp-knowledge/server.mjs +245 -0
- package/lib/mcp-knowledge/test.mjs +802 -0
- package/lib/memory-budget.mjs +261 -0
- package/lib/mesh-collab.js +354 -4
- package/lib/mesh-harness.js +427 -0
- package/lib/mesh-plans.js +13 -5
- package/lib/mesh-registry.js +11 -2
- package/lib/mesh-tasks.js +67 -0
- package/lib/plan-templates.js +226 -0
- package/lib/pre-compression-flush.mjs +320 -0
- package/lib/role-loader.js +292 -0
- package/lib/rule-loader.js +358 -0
- package/lib/session-store.mjs +458 -0
- package/lib/transcript-parser.mjs +292 -0
- package/mission-control/drizzle/soul_schema_update.sql +29 -0
- package/mission-control/drizzle.config.ts +1 -4
- package/mission-control/package-lock.json +1571 -83
- package/mission-control/package.json +6 -2
- package/mission-control/scripts/gen-chronology.js +3 -3
- package/mission-control/scripts/import-pipeline-v2.js +0 -16
- package/mission-control/scripts/import-pipeline.js +0 -15
- package/mission-control/src/app/api/cowork/clusters/[id]/members/route.ts +117 -0
- package/mission-control/src/app/api/cowork/clusters/[id]/route.ts +84 -0
- package/mission-control/src/app/api/cowork/clusters/route.ts +141 -0
- package/mission-control/src/app/api/cowork/dispatch/route.ts +128 -0
- package/mission-control/src/app/api/cowork/events/route.ts +65 -0
- package/mission-control/src/app/api/cowork/intervene/route.ts +259 -0
- package/mission-control/src/app/api/cowork/sessions/[id]/route.ts +37 -0
- package/mission-control/src/app/api/cowork/sessions/route.ts +64 -0
- package/mission-control/src/app/api/diagnostics/route.ts +97 -0
- package/mission-control/src/app/api/diagnostics/test-runner/route.ts +990 -0
- package/mission-control/src/app/api/mesh/events/route.ts +95 -19
- package/mission-control/src/app/api/mesh/identity/route.ts +11 -0
- package/mission-control/src/app/api/mesh/tasks/[id]/route.ts +92 -0
- package/mission-control/src/app/api/mesh/tasks/route.ts +91 -0
- package/mission-control/src/app/api/tasks/[id]/handoff/route.ts +1 -1
- package/mission-control/src/app/api/tasks/[id]/route.ts +90 -4
- package/mission-control/src/app/api/tasks/route.ts +21 -30
- package/mission-control/src/app/cowork/page.tsx +261 -0
- package/mission-control/src/app/diagnostics/page.tsx +385 -0
- package/mission-control/src/app/graph/page.tsx +26 -0
- package/mission-control/src/app/memory/page.tsx +1 -1
- package/mission-control/src/app/obsidian/page.tsx +36 -6
- package/mission-control/src/app/roadmap/page.tsx +24 -0
- package/mission-control/src/app/souls/page.tsx +2 -2
- package/mission-control/src/components/board/execution-config.tsx +431 -0
- package/mission-control/src/components/board/kanban-board.tsx +75 -9
- package/mission-control/src/components/board/kanban-column.tsx +135 -19
- package/mission-control/src/components/board/task-card.tsx +55 -2
- package/mission-control/src/components/board/unified-task-dialog.tsx +82 -4
- package/mission-control/src/components/cowork/cluster-card.tsx +176 -0
- package/mission-control/src/components/cowork/create-cluster-dialog.tsx +251 -0
- package/mission-control/src/components/cowork/dispatch-form.tsx +423 -0
- package/mission-control/src/components/cowork/role-picker.tsx +102 -0
- package/mission-control/src/components/cowork/session-card.tsx +284 -0
- package/mission-control/src/components/layout/sidebar.tsx +39 -2
- package/mission-control/src/lib/__tests__/daily-log.test.ts +82 -0
- package/mission-control/src/lib/__tests__/memory-md.test.ts +87 -0
- package/mission-control/src/lib/__tests__/mesh-kv-sync.test.ts +465 -0
- package/mission-control/src/lib/__tests__/mocks/mock-kv.ts +131 -0
- package/mission-control/src/lib/__tests__/status-kanban.test.ts +46 -0
- package/mission-control/src/lib/__tests__/task-markdown.test.ts +188 -0
- package/mission-control/src/lib/__tests__/wikilinks.test.ts +175 -0
- package/mission-control/src/lib/config.ts +58 -0
- package/mission-control/src/lib/db/index.ts +69 -0
- package/mission-control/src/lib/db/schema.ts +61 -3
- package/mission-control/src/lib/hooks.ts +309 -0
- package/mission-control/src/lib/memory/entities.ts +3 -2
- package/mission-control/src/lib/nats.ts +66 -1
- package/mission-control/src/lib/parsers/task-markdown.ts +52 -2
- package/mission-control/src/lib/parsers/transcript.ts +4 -4
- package/mission-control/src/lib/scheduler.ts +12 -11
- package/mission-control/src/lib/sync/mesh-kv.ts +279 -0
- package/mission-control/src/lib/sync/tasks.ts +23 -1
- package/mission-control/src/lib/task-id.ts +32 -0
- package/mission-control/src/lib/tts/index.ts +33 -9
- package/mission-control/tsconfig.json +2 -1
- package/mission-control/vitest.config.ts +14 -0
- package/package.json +15 -2
- package/services/service-manifest.json +1 -1
- package/skills/cc-godmode/references/agents.md +8 -8
- package/workspace-bin/memory-daemon.mjs +199 -5
- package/workspace-bin/session-search.mjs +204 -0
- package/workspace-bin/web-fetch.mjs +65 -0
package/install.sh
CHANGED
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
# bash install.sh # Full install
|
|
8
8
|
# bash install.sh --dry-run # Show what would happen
|
|
9
9
|
# bash install.sh --update # Re-copy scripts/configs, skip deps
|
|
10
|
+
#
|
|
11
|
+
# --dry-run behavior:
|
|
12
|
+
# Echoes every command that would execute (prefixed with [DRY-RUN]) without
|
|
13
|
+
# modifying the filesystem. Also verifies that all source paths exist —
|
|
14
|
+
# a missing source prints [DRY-RUN ERROR] so path bugs (like SCRIPT_DIR)
|
|
15
|
+
# are caught without running the install. Does NOT check destination
|
|
16
|
+
# writability (that requires actual fs calls). Exit code 0 on success,
|
|
17
|
+
# 1 if any source path is missing.
|
|
10
18
|
|
|
11
19
|
set -euo pipefail
|
|
12
20
|
|
|
@@ -60,10 +68,28 @@ step() { echo -e "\n${GREEN}━━━ $* ━━━${NC}"; }
|
|
|
60
68
|
run() {
|
|
61
69
|
if $DRY_RUN; then
|
|
62
70
|
echo " [dry-run] $*"
|
|
71
|
+
# Verify source paths exist for cp/rsync commands (catches path bugs)
|
|
72
|
+
case "$1" in
|
|
73
|
+
cp)
|
|
74
|
+
if [ ! -e "$2" ]; then
|
|
75
|
+
error "[dry-run] SOURCE MISSING: $2"
|
|
76
|
+
DRY_RUN_ERRORS=$((${DRY_RUN_ERRORS:-0} + 1))
|
|
77
|
+
fi
|
|
78
|
+
;;
|
|
79
|
+
rsync)
|
|
80
|
+
# rsync source is the last arg before the destination
|
|
81
|
+
local src="${@:(-2):1}"
|
|
82
|
+
if [ ! -e "${src%/}" ] && [ ! -d "${src%/}" ]; then
|
|
83
|
+
error "[dry-run] SOURCE MISSING: ${src}"
|
|
84
|
+
DRY_RUN_ERRORS=$((${DRY_RUN_ERRORS:-0} + 1))
|
|
85
|
+
fi
|
|
86
|
+
;;
|
|
87
|
+
esac
|
|
63
88
|
else
|
|
64
89
|
"$@"
|
|
65
90
|
fi
|
|
66
91
|
}
|
|
92
|
+
DRY_RUN_ERRORS=0
|
|
67
93
|
|
|
68
94
|
detect_os() {
|
|
69
95
|
case "$(uname -s)" in
|
|
@@ -230,6 +256,10 @@ if [ -z "$NODE_ROLE" ]; then
|
|
|
230
256
|
NODE_ROLE="worker"
|
|
231
257
|
fi
|
|
232
258
|
fi
|
|
259
|
+
if [ "$NODE_ROLE" != "lead" ] && [ "$NODE_ROLE" != "worker" ]; then
|
|
260
|
+
error "Invalid role: $NODE_ROLE (must be 'lead' or 'worker')"
|
|
261
|
+
exit 1
|
|
262
|
+
fi
|
|
233
263
|
export OPENCLAW_NODE_ROLE="$NODE_ROLE"
|
|
234
264
|
info "Node role: $NODE_ROLE"
|
|
235
265
|
|
|
@@ -532,10 +562,54 @@ fi
|
|
|
532
562
|
run mkdir -p "$MC_DIR/data"
|
|
533
563
|
|
|
534
564
|
# ============================================================
|
|
535
|
-
# Step 12:
|
|
565
|
+
# Step 12: Playwright (web-fetch fallback)
|
|
566
|
+
# ============================================================
|
|
567
|
+
|
|
568
|
+
step "Step 12: Playwright Browser"
|
|
569
|
+
|
|
570
|
+
if [ -f "$WORKSPACE/node_modules/.package-lock.json" ] && grep -q '"playwright"' "$WORKSPACE/node_modules/.package-lock.json" 2>/dev/null; then
|
|
571
|
+
info "Playwright already installed in workspace"
|
|
572
|
+
else
|
|
573
|
+
info "Installing Playwright + Chromium (web-fetch fallback for anti-bot sites)..."
|
|
574
|
+
(cd "$WORKSPACE" && run npm install --save playwright 2>/dev/null) || warn "Playwright npm install failed"
|
|
575
|
+
(cd "$WORKSPACE" && run npx playwright install chromium 2>/dev/null) || warn "Chromium browser install failed"
|
|
576
|
+
fi
|
|
577
|
+
|
|
578
|
+
# ============================================================
|
|
579
|
+
# Step 13: Companion Bridge (OpenAI-compatible Claude adapter)
|
|
580
|
+
# ============================================================
|
|
581
|
+
|
|
582
|
+
step "Step 13: Companion Bridge"
|
|
583
|
+
|
|
584
|
+
if command -v companion-bridge >/dev/null 2>&1; then
|
|
585
|
+
info "companion-bridge already installed: $(companion-bridge --version 2>/dev/null || echo 'found')"
|
|
586
|
+
else
|
|
587
|
+
info "Installing companion-bridge (OpenAI-compatible adapter for Claude Code)..."
|
|
588
|
+
if [ "$OS" = "linux" ]; then
|
|
589
|
+
run sudo npm install -g companion-bridge || warn "companion-bridge install failed"
|
|
590
|
+
else
|
|
591
|
+
run npm install -g companion-bridge || warn "companion-bridge install failed"
|
|
592
|
+
fi
|
|
593
|
+
fi
|
|
594
|
+
|
|
595
|
+
# Deploy harness rules (user-level override for companion-bridge)
|
|
596
|
+
HARNESS_SRC="${REPO_DIR}/config/harness-rules.json"
|
|
597
|
+
HARNESS_DST="${HOME}/.openclaw/harness-rules.json"
|
|
598
|
+
if [ -f "$HARNESS_SRC" ]; then
|
|
599
|
+
if [ ! -f "$HARNESS_DST" ]; then
|
|
600
|
+
info "Deploying default harness rules to $HARNESS_DST"
|
|
601
|
+
mkdir -p "$(dirname "$HARNESS_DST")"
|
|
602
|
+
cp "$HARNESS_SRC" "$HARNESS_DST"
|
|
603
|
+
else
|
|
604
|
+
info "Harness rules already exist at $HARNESS_DST (skipping — user-owned)"
|
|
605
|
+
fi
|
|
606
|
+
fi
|
|
607
|
+
|
|
608
|
+
# ============================================================
|
|
609
|
+
# Step 14: ClawVault
|
|
536
610
|
# ============================================================
|
|
537
611
|
|
|
538
|
-
step "Step
|
|
612
|
+
step "Step 14: ClawVault"
|
|
539
613
|
|
|
540
614
|
if command -v clawvault >/dev/null 2>&1; then
|
|
541
615
|
info "ClawVault already installed: $(which clawvault)"
|
|
@@ -552,10 +626,10 @@ else
|
|
|
552
626
|
fi
|
|
553
627
|
|
|
554
628
|
# ============================================================
|
|
555
|
-
# Step
|
|
629
|
+
# Step 15: Initialize Memory
|
|
556
630
|
# ============================================================
|
|
557
631
|
|
|
558
|
-
step "Step
|
|
632
|
+
step "Step 15: Initialize Memory"
|
|
559
633
|
|
|
560
634
|
TODAY=$(date +%Y-%m-%d)
|
|
561
635
|
DAILY_FILE="$WORKSPACE/memory/$TODAY.md"
|
|
@@ -646,10 +720,27 @@ MEM
|
|
|
646
720
|
fi
|
|
647
721
|
|
|
648
722
|
# ============================================================
|
|
649
|
-
# Step
|
|
723
|
+
# Step 15.5: HyperAgent Protocol
|
|
724
|
+
# ============================================================
|
|
725
|
+
|
|
726
|
+
step "Step 15.5: HyperAgent Protocol"
|
|
727
|
+
|
|
728
|
+
if [ -f "$MESH_BIN/hyperagent.mjs" ]; then
|
|
729
|
+
mkdir -p "$HOME/.openclaw/state"
|
|
730
|
+
if node "$MESH_BIN/hyperagent.mjs" status 2>/dev/null; then
|
|
731
|
+
info "HyperAgent store initialized"
|
|
732
|
+
else
|
|
733
|
+
warn "HyperAgent init deferred (will init on first use)"
|
|
734
|
+
fi
|
|
735
|
+
else
|
|
736
|
+
warn "hyperagent.mjs not found in $MESH_BIN — skipping"
|
|
737
|
+
fi
|
|
738
|
+
|
|
739
|
+
# ============================================================
|
|
740
|
+
# Step 16: Install Services (role-aware, template-based)
|
|
650
741
|
# ============================================================
|
|
651
742
|
|
|
652
|
-
step "Step
|
|
743
|
+
step "Step 16: Install Services (role=$NODE_ROLE)"
|
|
653
744
|
|
|
654
745
|
MANIFEST="$REPO_DIR/services/service-manifest.json"
|
|
655
746
|
LAUNCHD_TEMPLATES="$REPO_DIR/services/launchd"
|
|
@@ -692,6 +783,9 @@ else
|
|
|
692
783
|
if command -v envsubst >/dev/null 2>&1; then
|
|
693
784
|
envsubst < "$TEMPLATE" > "$DEST"
|
|
694
785
|
else
|
|
786
|
+
# NOTE: sed delimiter is |. If OPENCLAW_NATS_TOKEN ever contains |
|
|
787
|
+
# (unlikely — tokens are hex/base64), this substitution will break.
|
|
788
|
+
# Prefer envsubst (above) when available; it has no delimiter issue.
|
|
695
789
|
sed \
|
|
696
790
|
-e "s|\${HOME}|$HOME|g" \
|
|
697
791
|
-e "s|\${NODE_BIN}|$NODE_BIN|g" \
|
|
@@ -800,10 +894,10 @@ else
|
|
|
800
894
|
fi
|
|
801
895
|
|
|
802
896
|
# ============================================================
|
|
803
|
-
# Step
|
|
897
|
+
# Step 17: Mesh Network (optional — if Tailscale detected)
|
|
804
898
|
# ============================================================
|
|
805
899
|
|
|
806
|
-
step "Step
|
|
900
|
+
step "Step 17: Mesh Network"
|
|
807
901
|
|
|
808
902
|
if $SKIP_MESH; then
|
|
809
903
|
info "Skipped (--skip-mesh flag set by meta-installer)"
|
|
@@ -855,6 +949,204 @@ fi
|
|
|
855
949
|
|
|
856
950
|
fi # end SKIP_MESH else block
|
|
857
951
|
|
|
952
|
+
# ============================================================
|
|
953
|
+
# Step 18: Path-Scoped Rules
|
|
954
|
+
# ============================================================
|
|
955
|
+
|
|
956
|
+
step "Step 18: Path-Scoped Rules"
|
|
957
|
+
|
|
958
|
+
RULES_DIR="${OPENCLAW_ROOT}/rules"
|
|
959
|
+
mkdir -p "$RULES_DIR"
|
|
960
|
+
|
|
961
|
+
# install_rule — version-aware rule deployment.
|
|
962
|
+
# Fresh install: copy. Update: compare versions. If source is newer and local
|
|
963
|
+
# was modified (hash mismatch), save as .new instead of overwriting.
|
|
964
|
+
install_rule() {
|
|
965
|
+
local src="$1" dst="$2" name="$3"
|
|
966
|
+
if [ ! -f "$src" ]; then return; fi
|
|
967
|
+
|
|
968
|
+
if [ ! -f "$dst" ]; then
|
|
969
|
+
cp "$src" "$dst"
|
|
970
|
+
info "Installed rule: ${name}"
|
|
971
|
+
return
|
|
972
|
+
fi
|
|
973
|
+
|
|
974
|
+
# Both exist — compare version fields
|
|
975
|
+
local src_ver dst_ver
|
|
976
|
+
src_ver=$(grep -m1 '^version:' "$src" 2>/dev/null | sed 's/version:[[:space:]]*//' || echo "0.0.0")
|
|
977
|
+
dst_ver=$(grep -m1 '^version:' "$dst" 2>/dev/null | sed 's/version:[[:space:]]*//' || echo "0.0.0")
|
|
978
|
+
|
|
979
|
+
if [ "$src_ver" = "$dst_ver" ]; then
|
|
980
|
+
return # Same version, nothing to do
|
|
981
|
+
fi
|
|
982
|
+
|
|
983
|
+
# Different versions — check if user modified the local copy
|
|
984
|
+
local src_hash dst_hash
|
|
985
|
+
if command -v md5sum &>/dev/null; then
|
|
986
|
+
src_hash=$(md5sum "$src" | cut -d' ' -f1)
|
|
987
|
+
dst_hash=$(md5sum "$dst" | cut -d' ' -f1)
|
|
988
|
+
elif command -v md5 &>/dev/null; then
|
|
989
|
+
src_hash=$(md5 -q "$src")
|
|
990
|
+
dst_hash=$(md5 -q "$dst")
|
|
991
|
+
else
|
|
992
|
+
# Can't compare hashes — save as .new to be safe
|
|
993
|
+
cp "$src" "${dst}.new"
|
|
994
|
+
warn "Rule ${name}: new version ${src_ver} available (saved as ${name}.new)"
|
|
995
|
+
return
|
|
996
|
+
fi
|
|
997
|
+
|
|
998
|
+
if [ "$src_hash" = "$dst_hash" ]; then
|
|
999
|
+
# Same content despite different version — just update
|
|
1000
|
+
cp "$src" "$dst"
|
|
1001
|
+
info "Updated rule: ${name} (${dst_ver} → ${src_ver})"
|
|
1002
|
+
else
|
|
1003
|
+
# User-modified — don't overwrite, save as .new
|
|
1004
|
+
cp "$src" "${dst}.new"
|
|
1005
|
+
warn "Rule ${name}: new version ${src_ver} available but local copy modified. Saved as ${name}.new for manual merge."
|
|
1006
|
+
fi
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
# Copy universal rules (always)
|
|
1010
|
+
for rule in security test-standards design-docs git-hygiene; do
|
|
1011
|
+
install_rule "${REPO_DIR}/config/rules/universal/${rule}.md" "${RULES_DIR}/${rule}.md" "${rule}.md"
|
|
1012
|
+
done
|
|
1013
|
+
|
|
1014
|
+
# Detect frameworks and install matching rules
|
|
1015
|
+
if [ -f "package.json" ] || [ -f "${WORKSPACE}/../../package.json" ]; then
|
|
1016
|
+
PKG_FILE="package.json"
|
|
1017
|
+
[ ! -f "$PKG_FILE" ] && PKG_FILE="${WORKSPACE}/../../package.json"
|
|
1018
|
+
|
|
1019
|
+
if [ -f "$PKG_FILE" ]; then
|
|
1020
|
+
# Solidity detection
|
|
1021
|
+
if grep -q '"hardhat"' "$PKG_FILE" 2>/dev/null || [ -f "hardhat.config.js" ] || [ -f "hardhat.config.ts" ] || [ -f "foundry.toml" ]; then
|
|
1022
|
+
install_rule "${REPO_DIR}/config/rules/framework/solidity.md" "${RULES_DIR}/solidity.md" "solidity.md"
|
|
1023
|
+
[ ! -f "${RULES_DIR}/solidity.md.new" ] || true # install_rule handles logging
|
|
1024
|
+
fi
|
|
1025
|
+
|
|
1026
|
+
# TypeScript detection
|
|
1027
|
+
if [ -f "tsconfig.json" ] || [ -f "${WORKSPACE}/../../tsconfig.json" ]; then
|
|
1028
|
+
install_rule "${REPO_DIR}/config/rules/framework/typescript.md" "${RULES_DIR}/typescript.md" "typescript.md"
|
|
1029
|
+
fi
|
|
1030
|
+
|
|
1031
|
+
# Unity detection
|
|
1032
|
+
if [ -d "ProjectSettings" ] || [ -d "Assets" ]; then
|
|
1033
|
+
install_rule "${REPO_DIR}/config/rules/framework/unity.md" "${RULES_DIR}/unity.md" "unity.md"
|
|
1034
|
+
fi
|
|
1035
|
+
fi
|
|
1036
|
+
fi
|
|
1037
|
+
|
|
1038
|
+
info "Rules directory: ${RULES_DIR} ($(ls -1 "$RULES_DIR" 2>/dev/null | wc -l | tr -d ' ') rules)"
|
|
1039
|
+
|
|
1040
|
+
# ============================================================
|
|
1041
|
+
# Step 19: Plan Templates
|
|
1042
|
+
# ============================================================
|
|
1043
|
+
|
|
1044
|
+
step "Step 19: Plan Templates"
|
|
1045
|
+
|
|
1046
|
+
TEMPLATES_DIR="${OPENCLAW_ROOT}/plan-templates"
|
|
1047
|
+
mkdir -p "$TEMPLATES_DIR"
|
|
1048
|
+
|
|
1049
|
+
for tmpl in team-feature team-bugfix team-deploy; do
|
|
1050
|
+
TMPL_SRC="${REPO_DIR}/config/plan-templates/${tmpl}.yaml"
|
|
1051
|
+
TMPL_DST="${TEMPLATES_DIR}/${tmpl}.yaml"
|
|
1052
|
+
if [ -f "$TMPL_SRC" ] && [ ! -f "$TMPL_DST" ]; then
|
|
1053
|
+
cp "$TMPL_SRC" "$TMPL_DST"
|
|
1054
|
+
info "Installed plan template: ${tmpl}.yaml"
|
|
1055
|
+
fi
|
|
1056
|
+
done
|
|
1057
|
+
|
|
1058
|
+
info "Templates directory: ${TEMPLATES_DIR}"
|
|
1059
|
+
|
|
1060
|
+
# ============================================================
|
|
1061
|
+
# Step 20: Claude Code Integration
|
|
1062
|
+
# ============================================================
|
|
1063
|
+
|
|
1064
|
+
step "Step 20: Claude Code Integration"
|
|
1065
|
+
|
|
1066
|
+
# Create .claude directory structure (in workspace root)
|
|
1067
|
+
CLAUDE_DIR="${WORKSPACE}/.claude"
|
|
1068
|
+
mkdir -p "${CLAUDE_DIR}/hooks"
|
|
1069
|
+
|
|
1070
|
+
# Symlink rules directory
|
|
1071
|
+
RULES_LINK="${CLAUDE_DIR}/rules"
|
|
1072
|
+
if [ ! -L "$RULES_LINK" ] && [ ! -d "$RULES_LINK" ]; then
|
|
1073
|
+
ln -s "${RULES_DIR}" "$RULES_LINK"
|
|
1074
|
+
info "Symlinked .claude/rules → ${RULES_DIR}"
|
|
1075
|
+
fi
|
|
1076
|
+
|
|
1077
|
+
# Deploy settings.json — merge hooks into existing if present, never overwrite permissions
|
|
1078
|
+
SETTINGS_SRC="${REPO_DIR}/config/claude-settings.json"
|
|
1079
|
+
SETTINGS_DST="${CLAUDE_DIR}/settings.json"
|
|
1080
|
+
if [ -f "$SETTINGS_SRC" ]; then
|
|
1081
|
+
if [ ! -f "$SETTINGS_DST" ]; then
|
|
1082
|
+
# Fresh install — copy wholesale
|
|
1083
|
+
cp "$SETTINGS_SRC" "$SETTINGS_DST"
|
|
1084
|
+
info "Deployed Claude Code settings.json"
|
|
1085
|
+
elif command -v jq &>/dev/null; then
|
|
1086
|
+
# Existing settings — merge hooks only, preserve user permissions
|
|
1087
|
+
# Strategy: for each hook lifecycle key (SessionStart, PreToolUse, etc.),
|
|
1088
|
+
# append our hook entries if they don't already exist (matched by command string)
|
|
1089
|
+
MERGED=$(jq -s '
|
|
1090
|
+
.[0] as $existing | .[1] as $new |
|
|
1091
|
+
$existing * {
|
|
1092
|
+
hooks: (
|
|
1093
|
+
($new.hooks // {}) | to_entries | reduce .[] as $entry (
|
|
1094
|
+
($existing.hooks // {});
|
|
1095
|
+
.[$entry.key] as $current |
|
|
1096
|
+
if $current == null then
|
|
1097
|
+
. + {($entry.key): $entry.value}
|
|
1098
|
+
else
|
|
1099
|
+
# Append hook entries whose command is not already present
|
|
1100
|
+
($current | map(.hooks) | flatten | map(.command)) as $existing_cmds |
|
|
1101
|
+
($entry.value | map(
|
|
1102
|
+
.hooks |= [.[] | select(.command as $cmd | $existing_cmds | index($cmd) | not)]
|
|
1103
|
+
| select(.hooks | length > 0)
|
|
1104
|
+
)) as $new_entries |
|
|
1105
|
+
if ($new_entries | length) > 0 then
|
|
1106
|
+
. + {($entry.key): ($current + $new_entries)}
|
|
1107
|
+
else .
|
|
1108
|
+
end
|
|
1109
|
+
end
|
|
1110
|
+
)
|
|
1111
|
+
)
|
|
1112
|
+
}
|
|
1113
|
+
' "$SETTINGS_DST" "$SETTINGS_SRC")
|
|
1114
|
+
echo "$MERGED" > "$SETTINGS_DST"
|
|
1115
|
+
info "Merged OpenClaw hooks into existing settings.json (permissions preserved)"
|
|
1116
|
+
else
|
|
1117
|
+
# No jq — can't safely merge. Dump patch file for manual merge.
|
|
1118
|
+
cp "$SETTINGS_SRC" "${SETTINGS_DST}.openclaw-hooks"
|
|
1119
|
+
warn "jq not found — hooks config saved to settings.json.openclaw-hooks for manual merge"
|
|
1120
|
+
fi
|
|
1121
|
+
fi
|
|
1122
|
+
|
|
1123
|
+
# Deploy hook scripts
|
|
1124
|
+
for hook in session-start validate-commit validate-push pre-compact session-stop log-agent; do
|
|
1125
|
+
HOOK_SRC="${REPO_DIR}/.claude/hooks/${hook}.sh"
|
|
1126
|
+
HOOK_DST="${CLAUDE_DIR}/hooks/${hook}.sh"
|
|
1127
|
+
if [ -f "$HOOK_SRC" ]; then
|
|
1128
|
+
cp "$HOOK_SRC" "$HOOK_DST"
|
|
1129
|
+
chmod +x "$HOOK_DST"
|
|
1130
|
+
fi
|
|
1131
|
+
done
|
|
1132
|
+
info "Deployed Claude Code hooks"
|
|
1133
|
+
|
|
1134
|
+
# Deploy git hooks (LLM-agnostic enforcement)
|
|
1135
|
+
if [ -d ".git/hooks" ] || [ -d "${WORKSPACE}/../../.git/hooks" ]; then
|
|
1136
|
+
GIT_HOOKS_DIR=".git/hooks"
|
|
1137
|
+
[ ! -d "$GIT_HOOKS_DIR" ] && GIT_HOOKS_DIR="${WORKSPACE}/../../.git/hooks"
|
|
1138
|
+
|
|
1139
|
+
for ghook in pre-commit pre-push; do
|
|
1140
|
+
GHOOK_SRC="${REPO_DIR}/config/git-hooks/${ghook}"
|
|
1141
|
+
GHOOK_DST="${GIT_HOOKS_DIR}/${ghook}"
|
|
1142
|
+
if [ -f "$GHOOK_SRC" ] && [ ! -f "$GHOOK_DST" ]; then
|
|
1143
|
+
cp "$GHOOK_SRC" "$GHOOK_DST"
|
|
1144
|
+
chmod +x "$GHOOK_DST"
|
|
1145
|
+
info "Installed git hook: ${ghook}"
|
|
1146
|
+
fi
|
|
1147
|
+
done
|
|
1148
|
+
fi
|
|
1149
|
+
|
|
858
1150
|
# ============================================================
|
|
859
1151
|
# Done!
|
|
860
1152
|
# ============================================================
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* circling-parser.js — Standalone parser for Circling Strategy LLM output.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from mesh-agent.js so both production code and tests import
|
|
5
|
+
* the same module. Zero external dependencies.
|
|
6
|
+
*
|
|
7
|
+
* Handles both single-artifact and multi-artifact output formats.
|
|
8
|
+
*
|
|
9
|
+
* Single artifact: everything before ===CIRCLING_REFLECTION=== is the artifact.
|
|
10
|
+
* Multi artifact: ===CIRCLING_ARTIFACT=== / ===END_ARTIFACT=== pairs delimit
|
|
11
|
+
* artifact content by position (content BETWEEN previous END_ARTIFACT and
|
|
12
|
+
* next CIRCLING_ARTIFACT marker).
|
|
13
|
+
*
|
|
14
|
+
* @param {string} output — raw LLM output
|
|
15
|
+
* @param {object} [opts]
|
|
16
|
+
* @param {function} [opts.log] — optional logger (default: no-op)
|
|
17
|
+
* @param {function} [opts.legacyParser] — optional fallback parser for output
|
|
18
|
+
* without circling delimiters. Called as legacyParser(output). Should return
|
|
19
|
+
* { summary, confidence, vote, parse_failed }. If not provided, missing
|
|
20
|
+
* delimiters produce parse_failed: true.
|
|
21
|
+
* @returns {{ circling_artifacts: Array<{type: string, content: string}>, summary: string, confidence: number, vote: string, parse_failed: boolean }}
|
|
22
|
+
*/
|
|
23
|
+
function parseCirclingReflection(output, opts = {}) {
|
|
24
|
+
const log = opts.log || (() => {});
|
|
25
|
+
|
|
26
|
+
const VALID_VOTES = new Set(['continue', 'converged', 'blocked']);
|
|
27
|
+
const result = {
|
|
28
|
+
circling_artifacts: [],
|
|
29
|
+
summary: '',
|
|
30
|
+
confidence: 0.5,
|
|
31
|
+
vote: 'continue',
|
|
32
|
+
parse_failed: false,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Extract the reflection metadata block
|
|
36
|
+
const reflMatch = output.match(/===CIRCLING_REFLECTION===([\s\S]*?)===END_REFLECTION===/);
|
|
37
|
+
if (!reflMatch) {
|
|
38
|
+
// No circling delimiters — try legacy fallback if provided
|
|
39
|
+
if (opts.legacyParser) {
|
|
40
|
+
const legacy = opts.legacyParser(output);
|
|
41
|
+
return {
|
|
42
|
+
circling_artifacts: [],
|
|
43
|
+
summary: legacy.summary,
|
|
44
|
+
confidence: legacy.confidence,
|
|
45
|
+
vote: legacy.vote,
|
|
46
|
+
parse_failed: legacy.parse_failed,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return { ...result, parse_failed: true, vote: 'parse_error' };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Parse reflection key-value pairs
|
|
53
|
+
const reflBlock = reflMatch[1];
|
|
54
|
+
const typeMatch = reflBlock.match(/^type:\s*(.+)$/m);
|
|
55
|
+
const summaryMatch = reflBlock.match(/^summary:\s*(.+)$/m);
|
|
56
|
+
const confMatch = reflBlock.match(/^confidence:\s*([\d.]+)$/m);
|
|
57
|
+
const voteMatch = reflBlock.match(/^vote:\s*(\w+)$/m);
|
|
58
|
+
|
|
59
|
+
result.summary = summaryMatch ? summaryMatch[1].trim() : '';
|
|
60
|
+
result.confidence = confMatch ? parseFloat(confMatch[1]) : 0.5;
|
|
61
|
+
const voteRaw = voteMatch ? voteMatch[1].trim().toLowerCase() : 'continue';
|
|
62
|
+
result.vote = VALID_VOTES.has(voteRaw) ? voteRaw : 'parse_error';
|
|
63
|
+
if (!VALID_VOTES.has(voteRaw)) result.parse_failed = true;
|
|
64
|
+
|
|
65
|
+
const artifactType = typeMatch ? typeMatch[1].trim() : 'unknown';
|
|
66
|
+
|
|
67
|
+
// Check for multi-artifact format
|
|
68
|
+
const artifactBlocks = [...output.matchAll(/===CIRCLING_ARTIFACT===([\s\S]*?)===END_ARTIFACT===/g)];
|
|
69
|
+
|
|
70
|
+
if (artifactBlocks.length > 0) {
|
|
71
|
+
// Multi-artifact: parse each block.
|
|
72
|
+
// Content for artifact N is between the previous ===END_ARTIFACT=== (or start
|
|
73
|
+
// of output for N=0) and this artifact's ===CIRCLING_ARTIFACT=== marker.
|
|
74
|
+
const parts = output.split('===CIRCLING_REFLECTION===')[0]; // everything before reflection
|
|
75
|
+
const artMatches = [...parts.matchAll(/===CIRCLING_ARTIFACT===\s*\n([\s\S]*?)===END_ARTIFACT===/g)];
|
|
76
|
+
const chunks = [];
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < artMatches.length; i++) {
|
|
79
|
+
const m = artMatches[i];
|
|
80
|
+
const header = m[1].trim();
|
|
81
|
+
const typeLineMatch = header.match(/^type:\s*(.+)$/m);
|
|
82
|
+
const artType = typeLineMatch ? typeLineMatch[1].trim() : `artifact_${i}`;
|
|
83
|
+
|
|
84
|
+
const artStart = m.index;
|
|
85
|
+
const prevEnd = i === 0 ? 0 : (artMatches[i - 1].index + artMatches[i - 1][0].length);
|
|
86
|
+
const content = parts.slice(prevEnd, artStart).trim();
|
|
87
|
+
|
|
88
|
+
if (content) {
|
|
89
|
+
chunks.push({ type: artType, content });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Last chunk: content after the last END_ARTIFACT before CIRCLING_REFLECTION
|
|
94
|
+
if (artMatches.length > 0) {
|
|
95
|
+
const lastArt = artMatches[artMatches.length - 1];
|
|
96
|
+
const afterLast = parts.slice(lastArt.index + lastArt[0].length).trim();
|
|
97
|
+
if (afterLast) {
|
|
98
|
+
chunks.push({ type: 'extra', content: afterLast });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
result.circling_artifacts = chunks;
|
|
103
|
+
|
|
104
|
+
} else {
|
|
105
|
+
// Single-artifact: everything before ===CIRCLING_REFLECTION=== is the artifact
|
|
106
|
+
const beforeReflection = output.split('===CIRCLING_REFLECTION===')[0].trim();
|
|
107
|
+
if (beforeReflection) {
|
|
108
|
+
result.circling_artifacts = [{ type: artifactType, content: beforeReflection }];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (result.circling_artifacts.length === 0 && !result.parse_failed) {
|
|
113
|
+
log('CIRCLING PARSE WARNING: No artifacts extracted from output');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = { parseCirclingReflection };
|