dlinkclaw-openclaw 2026.10.23 → 2026.10.25

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/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{emptyPluginConfigSchema as e}from"openclaw/plugin-sdk/core";import{channelMeta as s,dlChannel as n,LOG_TAG as r}from"./src/channel.js";import{setDLLogger as t,setDLRuntime as o,isRunStopped as i}from"./src/runtime.js";import{loadPersonaBlock as a,currentPersonaAgent as l}from"./src/persona.js";const p=s;p.isDebug&&(process.env.NODE_TLS_REJECT_UNAUTHORIZED="0",process.env.NODE_ENV="dev");const c={id:p.id,name:p.label,description:p.blurb,configSchema:e(),register(e){o(e.runtime),t(e.logger,r),e.registerChannel({plugin:n}),e.on("before_tool_call",(e,s)=>{const n=p.id;if(s.channelId===n)return e.runId&&i(e.runId)?{block:!0,blockReason:"用户已点击停止,任务终止执行"}:void 0}),e.on("before_prompt_build",async(s,n)=>{const r=p.id;if(n.channel!==r&&n.channelId!==r)return;const t=["[AGENT CODE OF CONDUCT - ALWAYS FOLLOW]","1. ASK BEFORE GUESSING: If the task is incomplete or has multiple reasonable interpretations, send a one-line confirmation first. Never guess.",'2. STOP ON FAILURE: If the same problem fails 2-3 times in a row, stop and report: "I am stuck: <reason>. Please advise." Never retry endlessly.','3. ALWAYS REPORT COMPLETION: Every task must end with a completion signal. After sending media/files, always follow up with a short text confirmation (e.g. "✅ Sent 3 files: a.png / b.pdf / c.txt"). The client shows a spinner until it receives the final text reply.',"4. LANGUAGE: write your reply AND any intermediate/progress text (tool narration, status lines, preamble) in the same language as the user's latest message. Never default to English when the user writes in another language."],o=l(n.sessionKey);!o&&String(n.sessionKey??"").includes(":group:")&&e.logger.warn(`persona group scope missing: session=${n.sessionKey}`);const i=await a(n.sessionKey,o);return i&&t.push(i),{prependSystemContext:t.join("\n\n")}})}};export default c;
1
+ import{emptyPluginConfigSchema as e}from"openclaw/plugin-sdk/core";import{channelMeta as n,dlChannel as s,LOG_TAG as r}from"./src/channel.js";import{setDLLogger as t,setDLRuntime as o,isRunStopped as i}from"./src/runtime.js";import{loadPersonaBlock as a,currentPersonaAgent as l}from"./src/persona.js";const p=n;p.isDebug&&(process.env.NODE_TLS_REJECT_UNAUTHORIZED="0",process.env.NODE_ENV="dev");const c={id:p.id,name:p.label,description:p.blurb,configSchema:e(),register(e){o(e.runtime),t(e.logger,r),e.registerChannel({plugin:s}),e.on("before_tool_call",(e,n)=>{const s=p.id;if(n.channelId===s)return e.runId&&i(e.runId)?{block:!0,blockReason:"用户已点击停止,任务终止执行"}:void 0}),e.on("before_prompt_build",async(n,s)=>{const r=p.id;if(s.channel!==r&&s.channelId!==r)return;const t=["[AGENT CODE OF CONDUCT - ALWAYS FOLLOW]","1. ASK BEFORE GUESSING: If the task is incomplete or has multiple reasonable interpretations, send a one-line confirmation first. Never guess.",'2. STOP ON FAILURE: If the same problem fails 2-3 times in a row, stop and report: "I am stuck: <reason>. Please advise." Never retry endlessly.','3. ALWAYS REPORT COMPLETION: Every task must end with a completion signal. After sending media/files, always follow up with a short text confirmation (e.g. "✅ Sent 3 files: a.png / b.pdf / c.txt"). The client shows a spinner until it receives the final text reply.',"4. LANGUAGE: write your reply, any intermediate/progress text (tool narration, status lines, preamble), AND your reasoning/thinking in the same language as the user's latest message. Never default to English when the user writes in another language."],o=l(s.sessionKey);!o&&String(s.sessionKey??"").includes(":group:")&&e.logger.warn(`persona group scope missing: session=${s.sessionKey}`);const i=await a(s.sessionKey,o);return i&&t.push(i),{prependSystemContext:t.join("\n\n")}})}};export default c;
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dlinkclaw-openclaw",
3
- "version": "2026.10.23",
3
+ "version": "2026.10.25",
4
4
  "description": "DLinkClaw OpenClaw channel plugin — 朵联 OpenClaw 接入通道",
5
5
  "keywords": [
6
6
  "DLinkClaw",
@@ -946,16 +946,57 @@ export async function routeAndDispatchMessage(ctx) {
946
946
  kind: 'in_process'
947
947
  });
948
948
  }
949
+ // 2026-10-27 terminal guard: hoisted so the finally block can always settle the stream
950
+ const mediaFiles = [];
951
+ // 2026-10-27 terminal guard: normal end, empty reply, abort and error must all send finish=true exactly once.
952
+ // Otherwise client runState.status.finish stays false and the UI spins forever.
953
+ let terminalSent = false;
954
+ let scopeEnded = false;
955
+ let personaScopeKey = '';
956
+ const endScopeOnce = () => {
957
+ if (scopeEnded) {
958
+ return;
959
+ }
960
+ scopeEnded = true;
961
+ if (personaAgentId) {
962
+ endPersonaScope(personaScopeKey, personaAgentId);
963
+ }
964
+ };
965
+ // send terminal state once; tag_status_completed is parsed as a state message, not a chat bubble
966
+ const settleOnce = async (content, kind = 'block') => {
967
+ if (terminalSent) {
968
+ return;
969
+ }
970
+ terminalSent = true;
971
+ try {
972
+ await client.publishProcessingState({
973
+ streamId,
974
+ content: '[tag_status_completed]',
975
+ status: 'read',
976
+ kind: 'in_process'
977
+ });
978
+ }
979
+ catch (e) {
980
+ logger.warn?.(`account[${accountId}] publish terminal status failed: ${String(e)}`);
981
+ }
982
+ try {
983
+ if (messageAccumulators.has(streamId)) {
984
+ await client.publishToServer({ streamId, content, finish: true, kind, mediaFiles }, chatOpts);
985
+ }
986
+ }
987
+ catch (e) {
988
+ logger.error(`account[${accountId}] publish terminal reply failed: ${String(e)}`);
989
+ }
990
+ };
949
991
  try {
950
992
  const ctx = await ctxPayload;
951
993
  // 2026-10-26 群聊人设作用域:分发期间绑定被 @ 的 agent,整轮(含工具循环)都能读到正确值
952
994
  // 2026-10-26 OpenClaw 的消息上下文字段名是 SessionKey(大写),兼容两种写法
953
- const personaScopeKey = ((ctx.SessionKey ?? ctx.sessionKey) || '');
995
+ personaScopeKey = ((ctx.SessionKey ?? ctx.sessionKey) || '');
954
996
  logger.debug?.(`persona scope: session=${personaScopeKey} agent=${personaAgentId ?? '-'}`);
955
997
  if (personaAgentId) {
956
998
  beginPersonaScope(personaScopeKey, personaAgentId);
957
999
  }
958
- const mediaFiles = [];
959
1000
  await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
960
1001
  ctx,
961
1002
  cfg,
@@ -997,9 +1038,7 @@ export async function routeAndDispatchMessage(ctx) {
997
1038
  }
998
1039
  },
999
1040
  onSettled: async () => {
1000
- if (personaAgentId) {
1001
- endPersonaScope(personaScopeKey, personaAgentId);
1002
- }
1041
+ endScopeOnce();
1003
1042
  if ((accStatus.accumulatedPayload && accStatus.accumulatedPayload.length > 0) || mediaFiles.length > 0) {
1004
1043
  //发送结束状态
1005
1044
  await client.publishProcessingState({
@@ -1009,6 +1048,7 @@ export async function routeAndDispatchMessage(ctx) {
1009
1048
  kind: 'in_process'
1010
1049
  });
1011
1050
  //处理完成整体回复
1051
+ terminalSent = true;
1012
1052
  await client.publishToServer({
1013
1053
  streamId,
1014
1054
  content: accStatus.accumulatedPayload,
@@ -1017,11 +1057,13 @@ export async function routeAndDispatchMessage(ctx) {
1017
1057
  mediaFiles
1018
1058
  }, chatOpts);
1019
1059
  }
1060
+ else {
1061
+ // 2026-10-27 空回复(含被中止、模型未产出正文)也必须收尾,否则客户端一直转圈
1062
+ await settleOnce('[tag_status_completed]');
1063
+ }
1020
1064
  },
1021
1065
  onError: async (err, info) => {
1022
- if (personaAgentId) {
1023
- endPersonaScope(personaScopeKey, personaAgentId);
1024
- }
1066
+ endScopeOnce();
1025
1067
  const errorStr = String(err);
1026
1068
  await client.publishProcessingState({
1027
1069
  streamId,
@@ -1029,7 +1071,10 @@ export async function routeAndDispatchMessage(ctx) {
1029
1071
  status: 'read',
1030
1072
  kind: 'in_process'
1031
1073
  });
1032
- client.publishToServer({
1074
+ if (info.kind === 'final') {
1075
+ terminalSent = true;
1076
+ }
1077
+ await client.publishToServer({
1033
1078
  streamId,
1034
1079
  content: `[tag_error_pross]\n${errorStr}`,
1035
1080
  finish: info.kind === 'final',
@@ -1227,4 +1272,11 @@ export async function routeAndDispatchMessage(ctx) {
1227
1272
  kind: 'block'
1228
1273
  }, chatOpts);
1229
1274
  }
1275
+ finally {
1276
+ // 2026-10-27 abort 兜底:dispatch 正常返回但没走 onSettled,或异常被上层吞掉,都补发一次终态
1277
+ endScopeOnce();
1278
+ if (!terminalSent) {
1279
+ await settleOnce('[tag_status_completed]');
1280
+ }
1281
+ }
1230
1282
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dlinkclaw-openclaw",
3
- "version": "2026.10.23",
3
+ "version": "2026.10.25",
4
4
  "description": "DLinkClaw OpenClaw channel plugin — 朵联 OpenClaw 接入通道",
5
5
  "keywords": [
6
6
  "DLinkClaw",