openclaw-opencode-bridge 2.1.2 → 2.1.4

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
@@ -4,7 +4,9 @@
4
4
  [![license](https://img.shields.io/npm/l/openclaw-opencode-bridge)](LICENSE)
5
5
  [![node](https://img.shields.io/node/v/openclaw-opencode-bridge)](package.json)
6
6
 
7
- > Forked from [openclaw-claude-bridge](https://github.com/bettep-dev/openclaw-claude-bridge) by [@bettep-dev](https://github.com/bettep-dev) — modified to work with OpenCode instead of Claude CLI.
7
+ > Liked this project? Consider donating!
8
+
9
+ > EVM Address: 0xe81c32383C8F21A14E6C2264939dA512e9F9bb42
8
10
 
9
11
  Bridge [OpenClaw](https://openclaw.ai) messaging channels to [OpenCode](https://opencode.ai) via persistent tmux sessions.
10
12
 
@@ -98,6 +100,8 @@ Removes all installed components — plugin, shell scripts, OPENCODE.md addition
98
100
  | Delivery confirmed but no reply | Check `tmux ls` — session may have crashed |
99
101
  | Multiline sends only first line | Re-run `openclaw-opencode-bridge onboard` (v2.0.6+) |
100
102
 
103
+ > Forked from [openclaw-claude-bridge](https://github.com/bettep-dev/openclaw-claude-bridge) by [@bettep-dev](https://github.com/bettep-dev) — modified to work with OpenCode instead of Claude CLI.
104
+
101
105
  ## License
102
106
 
103
107
  [MIT](LICENSE)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-opencode-bridge",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
4
4
  "description": "Bridge OpenClaw messaging channels to OpenCode via tmux persistent sessions",
5
5
  "main": "./lib/onboard.js",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  #!/bin/bash
2
- # bridge-version: 6
2
+ # bridge-version: 10
3
3
  # Start fresh session asynchronously and send instruction
4
4
  MSG="$1"
5
5
  OPENCODE="{{OPENCODE_BIN}}"
@@ -78,6 +78,11 @@ is_trivial_echo() {
78
78
  [ -n "$message_norm" ] && [ "$output_norm" = "$message_norm" ]
79
79
  }
80
80
 
81
+ has_external_delivery_success() {
82
+ local raw="$1"
83
+ printf '%s\n' "$raw" | grep -Eqi 'Sent via [A-Za-z]+|Message ID:[[:space:]]*[0-9]+'
84
+ }
85
+
81
86
  extract_last_marked_block() {
82
87
  local raw="$1"
83
88
  if ! printf '%s' "$raw" | grep -q '🔗'; then
@@ -111,8 +116,10 @@ sanitize_output() {
111
116
 
112
117
  cleaned="$(printf '%s\n' "$cleaned" | sed -E 's/\[[0-9]{1,3}m//g')"
113
118
 
114
- cleaned="$(printf '%s\n' "$cleaned" | grep -Ev \
115
- '^[[:space:]]*$|^[[:space:]]*(build[[:space:]]*·|◇[[:space:]]+Doctor warnings)[[:space:]]*$|^[[:space:]]*◇[[:space:]]+|^[[:space:]]*[←→↳].*|^[[:space:]]*Wrote file successfully\.?$|^[[:space:]]*(\$[[:space:]]*)?openclaw message send --channel[[:space:]]+|^[[:space:]]*error:[[:space:]]*too many arguments for '\''send'\''.*$|^[[:space:]]*Sent via Telegram|^[[:space:]]*\[(telegram|discord|slack|whatsapp|signal|irc|matrix|line|mattermost|teams)\]|autoSelectFamily=|dnsResultOrder=|^[[:space:]]*[│┌┐└┘├┤┬┴┼─═╭╮╰╯]+[[:space:]]*$')"
119
+ cleaned="$(printf '%s\n' "$cleaned" | sed -E 's/^[[:space:]]*>+[[:space:]]?//; /^```/d')"
120
+
121
+ cleaned="$(printf '%s\n' "$cleaned" | grep -Eiv \
122
+ '^[[:space:]]*$|^[[:space:]]*(build[[:space:]]*·|◇[[:space:]]+doctor warnings)[[:space:]]*$|^[[:space:]]*◇[[:space:]]+|^[[:space:]]*exa[[:space:]]+(web|code)[[:space:]]+search([[:space:]]+.*)?$|^[[:space:]]*[←→↳].*|^[[:space:]]*wrote file successfully\.?$|^[[:space:]]*(\$[[:space:]]*)?openclaw message send --channel[[:space:]]+|^[[:space:]]*error:[[:space:]]*too many arguments for '\''send'\''.*$|^[[:space:]]*sent via telegram|^[[:space:]]*\[(telegram|discord|slack|whatsapp|signal|irc|matrix|line|mattermost|teams)\]|autoselectfamily=|dnsresultorder=|^[[:space:]]*[│┌┐└┘├┤┬┴┼─═╭╮╰╯]+[[:space:]]*$')"
116
123
 
117
124
  marked="$(extract_last_marked_block "$cleaned")"
118
125
  if [ -n "$marked" ]; then
@@ -124,13 +131,49 @@ sanitize_output() {
124
131
  printf '%s' "$(trim_text "$cleaned")"
125
132
  }
126
133
 
134
+ sentence_case_first() {
135
+ local text="$1"
136
+ printf '%s' "$text" | awk '
137
+ BEGIN { done = 0 }
138
+ {
139
+ if (done) { print; next }
140
+ line = $0
141
+ for (i = 1; i <= length(line); i++) {
142
+ ch = substr(line, i, 1)
143
+ if (ch ~ /[a-z]/) {
144
+ pre = substr(line, 1, i - 1)
145
+ post = substr(line, i + 1)
146
+ line = pre toupper(ch) post
147
+ done = 1
148
+ break
149
+ } else if (ch ~ /[A-Z]/) {
150
+ done = 1
151
+ break
152
+ }
153
+ }
154
+ print line
155
+ }'
156
+ }
157
+
158
+ apply_reply_style() {
159
+ local text="$1"
160
+ text="$(trim_text "$text")"
161
+ [ -z "$text" ] && { printf '%s' "$text"; return; }
162
+ text="$(sentence_case_first "$text")"
163
+ printf '%s' "$text"
164
+ }
165
+
127
166
  run_with_timeout() {
128
167
  local mode="$1"
129
168
  local prompt="$2"
130
169
  local output rc tmp pid watchdog
131
170
 
132
171
  tmp="$(mktemp /tmp/opencode-run.XXXXXX)"
133
- "$OPENCODE" run "$mode" "$prompt" >"$tmp" 2>&1 &
172
+ if [ -n "$mode" ]; then
173
+ "$OPENCODE" run "$mode" "$prompt" >"$tmp" 2>&1 &
174
+ else
175
+ "$OPENCODE" run "$prompt" >"$tmp" 2>&1 &
176
+ fi
134
177
  pid=$!
135
178
 
136
179
  (
@@ -172,10 +215,11 @@ run_with_timeout() {
172
215
  exit 0
173
216
  fi
174
217
 
175
- run_result="$(run_with_timeout --fork "$FULL_MSG")"
218
+ # Fresh request: run without --continue to avoid session carryover.
219
+ run_result="$(run_with_timeout "" "$FULL_MSG")"
176
220
  rc="$(printf '%s' "$run_result" | head -n 1)"
177
- output="$(printf '%s' "$run_result" | tail -n +2)"
178
- output="$(sanitize_output "$output")"
221
+ raw_output="$(printf '%s' "$run_result" | tail -n +2)"
222
+ output="$(sanitize_output "$raw_output")"
179
223
 
180
224
  if [ "$rc" -eq 124 ] || [ "$rc" -eq 137 ]; then
181
225
  output="OpenCode timed out after ${RUN_TIMEOUT_SEC}s. Task may still be running. Try waiting a bit or send a follow-up."
@@ -185,7 +229,13 @@ run_with_timeout() {
185
229
  output="OpenCode ran, but returned a non-informative echo. Please retry with a more specific prompt."
186
230
  fi
187
231
 
188
- openclaw message send --channel "$CHANNEL" --target "$TARGET" -m "$output"
232
+ output="$(apply_reply_style "$output")"
233
+
234
+ if has_external_delivery_success "$raw_output"; then
235
+ printf '[%s] /ccn skipped bridge send (already sent by OpenCode)\n' "$(date '+%Y-%m-%d %H:%M:%S')"
236
+ else
237
+ openclaw message send --channel "$CHANNEL" --target "$TARGET" -m "$output"
238
+ fi
189
239
 
190
240
  ended_at=$(date +%s)
191
241
  elapsed=$((ended_at - started_at))
@@ -1,5 +1,5 @@
1
1
  #!/bin/bash
2
- # bridge-version: 6
2
+ # bridge-version: 10
3
3
  # Dispatch instruction to OpenCode asynchronously and relay response
4
4
  MSG="$1"
5
5
  OPENCODE="{{OPENCODE_BIN}}"
@@ -78,6 +78,11 @@ is_trivial_echo() {
78
78
  [ -n "$message_norm" ] && [ "$output_norm" = "$message_norm" ]
79
79
  }
80
80
 
81
+ has_external_delivery_success() {
82
+ local raw="$1"
83
+ printf '%s\n' "$raw" | grep -Eqi 'Sent via [A-Za-z]+|Message ID:[[:space:]]*[0-9]+'
84
+ }
85
+
81
86
  extract_last_marked_block() {
82
87
  local raw="$1"
83
88
  if ! printf '%s' "$raw" | grep -q '🔗'; then
@@ -111,8 +116,10 @@ sanitize_output() {
111
116
 
112
117
  cleaned="$(printf '%s\n' "$cleaned" | sed -E 's/\[[0-9]{1,3}m//g')"
113
118
 
114
- cleaned="$(printf '%s\n' "$cleaned" | grep -Ev \
115
- '^[[:space:]]*$|^[[:space:]]*(build[[:space:]]*·|◇[[:space:]]+Doctor warnings)[[:space:]]*$|^[[:space:]]*◇[[:space:]]+|^[[:space:]]*[←→↳].*|^[[:space:]]*Wrote file successfully\.?$|^[[:space:]]*(\$[[:space:]]*)?openclaw message send --channel[[:space:]]+|^[[:space:]]*error:[[:space:]]*too many arguments for '\''send'\''.*$|^[[:space:]]*Sent via Telegram|^[[:space:]]*\[(telegram|discord|slack|whatsapp|signal|irc|matrix|line|mattermost|teams)\]|autoSelectFamily=|dnsResultOrder=|^[[:space:]]*[│┌┐└┘├┤┬┴┼─═╭╮╰╯]+[[:space:]]*$')"
119
+ cleaned="$(printf '%s\n' "$cleaned" | sed -E 's/^[[:space:]]*>+[[:space:]]?//; /^```/d')"
120
+
121
+ cleaned="$(printf '%s\n' "$cleaned" | grep -Eiv \
122
+ '^[[:space:]]*$|^[[:space:]]*(build[[:space:]]*·|◇[[:space:]]+doctor warnings)[[:space:]]*$|^[[:space:]]*◇[[:space:]]+|^[[:space:]]*exa[[:space:]]+(web|code)[[:space:]]+search([[:space:]]+.*)?$|^[[:space:]]*[←→↳].*|^[[:space:]]*wrote file successfully\.?$|^[[:space:]]*(\$[[:space:]]*)?openclaw message send --channel[[:space:]]+|^[[:space:]]*error:[[:space:]]*too many arguments for '\''send'\''.*$|^[[:space:]]*sent via telegram|^[[:space:]]*\[(telegram|discord|slack|whatsapp|signal|irc|matrix|line|mattermost|teams)\]|autoselectfamily=|dnsresultorder=|^[[:space:]]*[│┌┐└┘├┤┬┴┼─═╭╮╰╯]+[[:space:]]*$')"
116
123
 
117
124
  marked="$(extract_last_marked_block "$cleaned")"
118
125
  if [ -n "$marked" ]; then
@@ -124,6 +131,38 @@ sanitize_output() {
124
131
  printf '%s' "$(trim_text "$cleaned")"
125
132
  }
126
133
 
134
+ sentence_case_first() {
135
+ local text="$1"
136
+ printf '%s' "$text" | awk '
137
+ BEGIN { done = 0 }
138
+ {
139
+ if (done) { print; next }
140
+ line = $0
141
+ for (i = 1; i <= length(line); i++) {
142
+ ch = substr(line, i, 1)
143
+ if (ch ~ /[a-z]/) {
144
+ pre = substr(line, 1, i - 1)
145
+ post = substr(line, i + 1)
146
+ line = pre toupper(ch) post
147
+ done = 1
148
+ break
149
+ } else if (ch ~ /[A-Z]/) {
150
+ done = 1
151
+ break
152
+ }
153
+ }
154
+ print line
155
+ }'
156
+ }
157
+
158
+ apply_reply_style() {
159
+ local text="$1"
160
+ text="$(trim_text "$text")"
161
+ [ -z "$text" ] && { printf '%s' "$text"; return; }
162
+ text="$(sentence_case_first "$text")"
163
+ printf '%s' "$text"
164
+ }
165
+
127
166
  run_with_timeout() {
128
167
  local mode="$1"
129
168
  local prompt="$2"
@@ -174,8 +213,8 @@ run_with_timeout() {
174
213
 
175
214
  run_result="$(run_with_timeout --continue "$FULL_MSG")"
176
215
  rc="$(printf '%s' "$run_result" | head -n 1)"
177
- output="$(printf '%s' "$run_result" | tail -n +2)"
178
- output="$(sanitize_output "$output")"
216
+ raw_output="$(printf '%s' "$run_result" | tail -n +2)"
217
+ output="$(sanitize_output "$raw_output")"
179
218
 
180
219
  if [ "$rc" -eq 124 ] || [ "$rc" -eq 137 ]; then
181
220
  output="OpenCode timed out after ${RUN_TIMEOUT_SEC}s. Task may still be running. Try waiting a bit or send a follow-up."
@@ -185,7 +224,13 @@ run_with_timeout() {
185
224
  output="OpenCode ran, but returned a non-informative echo. Please retry with a more specific prompt."
186
225
  fi
187
226
 
188
- openclaw message send --channel "$CHANNEL" --target "$TARGET" -m "$output"
227
+ output="$(apply_reply_style "$output")"
228
+
229
+ if has_external_delivery_success "$raw_output"; then
230
+ printf '[%s] /cc skipped bridge send (already sent by OpenCode)\n' "$(date '+%Y-%m-%d %H:%M:%S')"
231
+ else
232
+ openclaw message send --channel "$CHANNEL" --target "$TARGET" -m "$output"
233
+ fi
189
234
 
190
235
  ended_at=$(date +%s)
191
236
  elapsed=$((ended_at - started_at))
@@ -39,4 +39,6 @@ The format is: `[CHANNEL:ID] actual message`
39
39
  - For follow-up status questions such as "have you created it?", do not answer with only yes/no.
40
40
  Always include short status details and the run command.
41
41
  - Keep tone clear, proactive, and helpful. Prefer concise but complete responses.
42
+ - Use proper sentence case and punctuation. Start the first sentence with an uppercase letter.
43
+ - Avoid slang-only openings like "hey!" or "yep!" without context; provide a complete helpful sentence.
42
44
  - Length guidance: trivial questions can be 1-2 lines; implementation results should usually be 4-10 lines.