Skip to content

Commit d598336

Browse files
authored
Merge pull request #1 from 302ai/dev
merge dev
2 parents f1cfab4 + a35ff41 commit d598336

10 files changed

Lines changed: 427 additions & 77 deletions

Dockerfile

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,61 +12,45 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
1212
&& curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
1313
&& apt-get install -y --no-install-recommends nodejs \
1414
&& npm install -g @anthropic-ai/claude-code@latest \
15-
&& npm install -g openclaw@2026.3.13 \
15+
&& npm install -g openclaw@2026.3.23-2 \
1616
&& npm install -g clawhub@latest \
1717
&& npm install -g @playwright/cli@latest \
1818
&& apt-get clean \
1919
&& rm -rf /var/lib/apt/lists/* \
2020
&& rm -rf /root/.npm \
2121
&& rm -rf /tmp/*
2222

23-
# 创建用户
24-
RUN groupadd -r user && useradd -r -g user -m -s /bin/bash user
25-
2623
# 创建目录并设置权限
27-
RUN mkdir -p /data /data/user /app /home/user/.claude/skills /home/user/.openclaw /home/user/db && \
28-
chown -R user:user /data /app /home/user && \
29-
chmod 755 /data /app /home/user && \
30-
chmod 775 /home/user/db /home/user/.claude /home/user/.claude/skills /home/user/.openclaw
24+
RUN mkdir -p /data /app /home/user/.claude/skills /home/user/.openclaw /home/user/db && \
25+
chmod 755 /home/user/.openclaw /home/user/.claude /home/user/.claude/skills && \
26+
chmod 755 /data /app /home/user
3127

3228
# 安装 Python 依赖
3329
WORKDIR /app
3430
COPY requirements.txt ./
3531
RUN pip install --no-cache-dir -r requirements.txt
3632

3733
# 复制代码
38-
USER user
39-
COPY --chown=user:user . /app
34+
COPY . /app
4035

4136
# 复制自定义 skills 到不会被挂载覆盖的目录
42-
COPY --chown=user:user skills/ /app/.skills-backup/
37+
COPY skills/ /app/.skills-backup/
4338

4439
ENV HOME=/home/user
4540

4641
# 安装插件
47-
RUN openclaw plugins install @openclaw-china/channels
42+
RUN openclaw plugins install @openclaw-china/channels@latest
43+
RUN openclaw plugins install @openclaw/acpx
44+
RUN #npx -y @tencent-weixin/openclaw-weixin-cli@latest install
4845

4946
# 把插件数据备份到不会被挂载覆盖的目录
5047
RUN mkdir -p /app/.openclaw-extensions-backup && \
5148
cp -a /home/user/.openclaw/extensions /app/.openclaw-extensions-backup/
5249

50+
# 复制启动脚本
51+
COPY entrypoint.sh /app/entrypoint.sh
52+
RUN chmod +x /app/entrypoint.sh
53+
5354
EXPOSE 8000 18789
5455

55-
# 启动时检查 channels 插件是否存在,不存在才恢复
56-
CMD ["sh", "-c", "\
57-
if [ ! -d \"/home/user/.openclaw/extensions/channels\" ]; then \
58-
mkdir -p /home/user/.openclaw/extensions && \
59-
cp -a /app/.openclaw-extensions-backup/extensions/* /home/user/.openclaw/extensions/ 2>/dev/null || true; \
60-
echo 'Restored openclaw extensions (channels plugin was missing)'; \
61-
else \
62-
echo 'channels plugin exists, skipping restore'; \
63-
fi && \
64-
mkdir -p /home/user/.claude/skills && \
65-
for p in /app/.skills-backup/*; do \
66-
name=\"$(basename \"$p\")\"; \
67-
cp -a \"$p\" /home/user/.claude/skills/ 2>/dev/null || true; \
68-
echo \"Restored skill entry (overwrite): $name\"; \
69-
done && \
70-
openclaw gateway run --port 18789 --bind lan & \
71-
uvicorn main:app --host 0.0.0.0 --port 8000\
72-
"]
56+
CMD ["/app/entrypoint.sh"]

app/api/routes_chat.py

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,6 @@ async def _run_openclaw_cmd():
768768

769769
if list_sessions_result.exit_code != 0:
770770
raise Exception(list_sessions_result.stderr)
771-
772771
try:
773772
data = json.loads(list_sessions_result.stdout)
774773
except json.decoder.JSONDecodeError:
@@ -846,39 +845,70 @@ async def _run_openclaw_cmd():
846845

847846
prefix = "\n\n".join(prefix_parts) + "\n\n"
848847
final_user_prompt = prefix + user_prompt
848+
collected_text_chunks: list[str] = []
849+
# 确保扩展名在路径最后一个 / 之后
850+
file_regex = re.compile(r"^- `?(\/\S+\/[^/\s]+\.[^/\s`]+)`?$", re.MULTILINE)
851+
849852
async for event in oc_chat_completions_sse(
850853
oc_session_key=oc_session_key,
851854
user_prompt=final_user_prompt,
852855
timeout=aiohttp.ClientTimeout(total=None, sock_read=None, connect=30),
853856
):
854857
# event 是 bytes,需要对应处理
855858
if event.strip() == b"data: [DONE]":
856-
857-
# check_cmd = """find . -maxdepth 4 \( -path "./claude" -o -path "./claude/*" -o -path "./node_modules" -o -path "./node_modules/*" -o -path "./.git" -o -path "./.git/*" -o -path "./venv" -o -path "./venv/*" -o -path "./.venv" -o -path "./.venv/*" -o -path "./env" -o -path "./env/*" -o -path "./__pycache__" -o -path "./__pycache__/*" \) -prune -o -type f \( -name "package.json" -o -name "pnpm-lock.yaml" -o -name "yarn.lock" -o -name "package-lock.json" -o -name "next.config.*" -o -name "vite.config.*" -o -name "vue.config.*" -o -name "nuxt.config.*" -o -name "svelte.config.*" -o -name "astro.config.*" -o -name "remix.config.*" -o -name "angular.json" -o -name "gatsby-config.*" -o -path "./index.html" -o -path "*/public/index.html" -o -path "./server.js" -o -path "./app.js" -o -path "./index.js" -o -path "./main.js" -o -path "./server.ts" -o -path "./app.ts" -o -path "./index.ts" -o -path "./main.ts" -o -path "./src/index.js" -o -path "./src/index.ts" -o -path "./src/index.jsx" -o -path "./src/index.tsx" -o -path "./src/main.js" -o -path "./src/main.ts" -o -path "./src/main.jsx" -o -path "./src/main.tsx" -o -path "./src/App.vue" -o -path "./src/app.js" -o -path "./src/app.ts" -o -path "./src/app.jsx" -o -path "./src/app.tsx" -o -path "./src/server.js" -o -path "./src/server.ts" -o \( -path "./src/*" -a \( -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" -o -name "*.vue" \) \) \)"""
859+
# 在 DONE 时,从之前累计的文本块里提取路径并插入一个额外 chunk
860+
combined = "".join(collected_text_chunks)
861+
matches = file_regex.findall(combined)
862+
if matches:
863+
# 校验路径是否存在,只保留存在的
864+
valid_matches = [m for m in matches if os.path.isfile(m)]
865+
diff_file_check_info = json.dumps({
866+
"type": "result",
867+
"is_error": False,
868+
"result": "\n".join(f"-`{m}`" for m in valid_matches) if valid_matches else "",
869+
}, ensure_ascii=False)
870+
else:
871+
diff_file_check_info = json.dumps({
872+
"type": "result",
873+
"is_error": False,
874+
"result": "",
875+
}, ensure_ascii=False)
876+
diff_file_chunk = gpt_stream_chunk(diff_file_check_info)
877+
yield f"data: {json.dumps(diff_file_chunk, ensure_ascii=False)}\n\n".encode("utf-8")
858878

859879
if payload.enable_pre_deploy_check:
860-
861880
pre_deploy_check_result = await runner.exec_json(command=check_cmd, cwd=workspace_path)
862881
if pre_deploy_check_result.exit_code == 0:
863-
if pre_deploy_check_result.stdout:
864-
deploy_check_info = json.dumps({
865-
"type": "pre_deploy_check",
866-
"success": True,
867-
"find_file": pre_deploy_check_result.stdout,
868-
})
869-
else:
870-
deploy_check_info = json.dumps({
871-
"type": "pre_deploy_check",
872-
"success": False,
873-
"find_file": pre_deploy_check_result.stdout,
874-
})
882+
deploy_check_info = json.dumps({
883+
"type": "pre_deploy_check",
884+
"success": bool(pre_deploy_check_result.stdout),
885+
"find_file": pre_deploy_check_result.stdout,
886+
}, ensure_ascii=False)
875887

876888
# 插入自定义文本 chunk
877-
chunk = gpt_stream_chunk(f"{json.dumps(deploy_check_info, ensure_ascii=False)}")
889+
chunk = gpt_stream_chunk(deploy_check_info)
878890
yield f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n".encode("utf-8")
891+
879892
# 放行 [DONE]
880893
yield event
881894
else:
895+
# 非 DONE:透传,同时尽量从 OpenAI chunk 中抽取文本累计
896+
try:
897+
line = event.decode("utf-8", errors="ignore").strip()
898+
if line.startswith("data: "):
899+
payload_json = line[6:]
900+
if payload_json and payload_json != "[DONE]":
901+
obj = json.loads(payload_json)
902+
content = (
903+
obj.get("choices", [{}])[0]
904+
.get("delta", {})
905+
.get("content")
906+
)
907+
if isinstance(content, str) and content:
908+
collected_text_chunks.append(content)
909+
except Exception:
910+
pass
911+
882912
yield event
883913

884914
# 处理messages

app/api/routes_session.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -222,28 +222,28 @@ async def init_project(payload: ProjectInitRequest, repo: SessionRepository = De
222222

223223
oc_agent_id = workspace_name
224224

225-
new_resp, list_sessions_result = await oc_new_session_and_list_active(
226-
oc_agent_id=oc_agent_id,
227-
runner=runner,
228-
active=3,
229-
)
230-
log_info(f"{new_resp}")
231-
232-
if list_sessions_result.exit_code != 0:
233-
return fail(list_sessions_result.stderr, status_code=400)
234-
try:
235-
data = json.loads(list_sessions_result.stdout)
236-
except json.decoder.JSONDecodeError:
237-
# fix openclaw 3.13 CLI --json没正确返回json格式
238-
data = await oc_load_sessions_json_as_list(oc_agent_name=oc_agent_id)
239-
sessions = data.get("sessions", [])
240-
241-
if not sessions:
242-
return fail("No active sessions found", status_code=400)
243-
244-
# 按 updatedAt 降序排序,取最新的一个
245-
latest_session = max(sessions, key=lambda s: s.get("updatedAt", 0))
246-
log_info(f"{latest_session}")
225+
new_resp, list_sessions_result = await oc_new_session_and_list_active(
226+
oc_agent_id=oc_agent_id,
227+
runner=runner,
228+
active=3,
229+
)
230+
log_info(f"{new_resp}")
231+
232+
if list_sessions_result.exit_code != 0:
233+
return fail(list_sessions_result.stderr, status_code=400)
234+
try:
235+
data = json.loads(list_sessions_result.stdout)
236+
except json.decoder.JSONDecodeError:
237+
# fix openclaw 3.13 CLI --json没正确返回json格式
238+
data = await oc_load_sessions_json_as_list(oc_agent_name=oc_agent_id)
239+
sessions = data.get("sessions", [])
240+
241+
if not sessions:
242+
return fail("No active sessions found", status_code=400)
243+
244+
# 按 updatedAt 降序排序,取最新的一个
245+
latest_session = max(sessions, key=lambda s: s.get("updatedAt", 0))
246+
log_info(f"{latest_session}")
247247

248248
await run_in_threadpool(lambda: repo.create_session(
249249
session_alias=payload.session_id,

0 commit comments

Comments
 (0)