mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-15 12:00:43 +00:00
* fix(feishu): fetch thread context so AI can see bot replies in topic threads When a user replies in a Feishu topic thread, the AI previously could only see the quoted parent message but not the bot's own prior replies in the thread. This made multi-turn conversations in threads feel broken. - Add `threadId` (omt_xxx) to `FeishuMessageInfo` and `getMessageFeishu` - Add `listFeishuThreadMessages()` using `container_id_type=thread` API to fetch all messages in a thread including bot replies - In `handleFeishuMessage`, fetch ThreadStarterBody and ThreadHistoryBody for topic session modes and pass them to the AI context - Reuse quoted message result when rootId === parentId to avoid redundant API calls; exclude root message from thread history to prevent duplication - Fall back to inbound ctx.threadId when rootId is absent or API fails - Fetch newest messages first (ByCreateTimeDesc + reverse) so long threads keep the most recent turns instead of the oldest Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(feishu): skip redundant thread context injection on subsequent turns Only inject ThreadHistoryBody on the first turn of a thread session. On subsequent turns the session already contains prior context, so re-injecting thread history (and starter) would waste tokens. The heuristic checks whether the current user has already sent a non-root message in the thread — if so, the session has prior turns and thread context injection is skipped entirely. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(feishu): handle thread_id-only events in prior-turn detection When ctx.rootId is undefined (thread_id-only events), the starter message exclusion check `msg.messageId !== ctx.rootId` was always true, causing the first follow-up to be misclassified as a prior turn. Fall back to the first message in the chronologically-sorted thread history as the starter. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(feishu): bootstrap topic thread context via session state * test(memory): pin remote embedding hostnames in offline suites * fix(feishu): use plugin-safe session runtime for thread bootstrap --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
101 lines
3.0 KiB
Bash
Executable File
101 lines
3.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
on_error() {
|
|
echo "A2UI bundling failed. Re-run with: pnpm canvas:a2ui:bundle" >&2
|
|
echo "If this persists, verify pnpm deps and try again." >&2
|
|
}
|
|
trap on_error ERR
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
HASH_FILE="$ROOT_DIR/src/canvas-host/a2ui/.bundle.hash"
|
|
OUTPUT_FILE="$ROOT_DIR/src/canvas-host/a2ui/a2ui.bundle.js"
|
|
A2UI_RENDERER_DIR="$ROOT_DIR/vendor/a2ui/renderers/lit"
|
|
A2UI_APP_DIR="$ROOT_DIR/apps/shared/OpenClawKit/Tools/CanvasA2UI"
|
|
|
|
# Docker builds exclude vendor/apps via .dockerignore.
|
|
# In that environment we can keep a prebuilt bundle only if it exists.
|
|
if [[ ! -d "$A2UI_RENDERER_DIR" || ! -d "$A2UI_APP_DIR" ]]; then
|
|
if [[ -f "$OUTPUT_FILE" ]]; then
|
|
echo "A2UI sources missing; keeping prebuilt bundle."
|
|
exit 0
|
|
fi
|
|
echo "A2UI sources missing and no prebuilt bundle found at: $OUTPUT_FILE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
INPUT_PATHS=(
|
|
"$ROOT_DIR/package.json"
|
|
"$ROOT_DIR/pnpm-lock.yaml"
|
|
"$A2UI_RENDERER_DIR"
|
|
"$A2UI_APP_DIR"
|
|
)
|
|
|
|
compute_hash() {
|
|
ROOT_DIR="$ROOT_DIR" node --input-type=module --eval '
|
|
import { createHash } from "node:crypto";
|
|
import { promises as fs } from "node:fs";
|
|
import path from "node:path";
|
|
|
|
const rootDir = process.env.ROOT_DIR ?? process.cwd();
|
|
const inputs = process.argv.slice(1);
|
|
const files = [];
|
|
|
|
async function walk(entryPath) {
|
|
const st = await fs.stat(entryPath);
|
|
if (st.isDirectory()) {
|
|
const entries = await fs.readdir(entryPath);
|
|
for (const entry of entries) {
|
|
await walk(path.join(entryPath, entry));
|
|
}
|
|
return;
|
|
}
|
|
files.push(entryPath);
|
|
}
|
|
|
|
for (const input of inputs) {
|
|
await walk(input);
|
|
}
|
|
|
|
function normalize(p) {
|
|
return p.split(path.sep).join("/");
|
|
}
|
|
|
|
files.sort((a, b) => normalize(a).localeCompare(normalize(b)));
|
|
|
|
const hash = createHash("sha256");
|
|
for (const filePath of files) {
|
|
const rel = normalize(path.relative(rootDir, filePath));
|
|
hash.update(rel);
|
|
hash.update("\0");
|
|
hash.update(await fs.readFile(filePath));
|
|
hash.update("\0");
|
|
}
|
|
|
|
process.stdout.write(hash.digest("hex"));
|
|
' "${INPUT_PATHS[@]}"
|
|
}
|
|
|
|
current_hash="$(compute_hash)"
|
|
if [[ -f "$HASH_FILE" ]]; then
|
|
previous_hash="$(cat "$HASH_FILE")"
|
|
if [[ "$previous_hash" == "$current_hash" && -f "$OUTPUT_FILE" ]]; then
|
|
echo "A2UI bundle up to date; skipping."
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
pnpm -s exec tsc -p "$A2UI_RENDERER_DIR/tsconfig.json"
|
|
if command -v rolldown >/dev/null 2>&1 && rolldown --version >/dev/null 2>&1; then
|
|
rolldown -c "$A2UI_APP_DIR/rolldown.config.mjs"
|
|
elif [[ -f "$ROOT_DIR/node_modules/.pnpm/node_modules/rolldown/bin/cli.mjs" ]]; then
|
|
node "$ROOT_DIR/node_modules/.pnpm/node_modules/rolldown/bin/cli.mjs" -c "$A2UI_APP_DIR/rolldown.config.mjs"
|
|
elif [[ -f "$ROOT_DIR/node_modules/.pnpm/rolldown@1.0.0-rc.9/node_modules/rolldown/bin/cli.mjs" ]]; then
|
|
node "$ROOT_DIR/node_modules/.pnpm/rolldown@1.0.0-rc.9/node_modules/rolldown/bin/cli.mjs" \
|
|
-c "$A2UI_APP_DIR/rolldown.config.mjs"
|
|
else
|
|
pnpm -s dlx rolldown -c "$A2UI_APP_DIR/rolldown.config.mjs"
|
|
fi
|
|
|
|
echo "$current_hash" > "$HASH_FILE"
|