删掉 TurnAlreadyRunning:入队化后没有并发撞单的生产点
- 删 DirectTurnError::TurnAlreadyRunning 变体,以及 is_reportable / Display 的对应分支与专属用例 - 前端 directTurnEnqueueFailureNotice 删掉 turnAlreadyRunning 分支;ts-rs 绑定同步删除该变体 - 结构化载荷用例的样本换成 queueFull,本地说明用例的文案样本换成队列已满提示
This commit is contained in:
@@ -390,13 +390,6 @@ pub(crate) enum DirectTurnError {
|
||||
ClientTurnIdMissing,
|
||||
/// `clientTurnId` 形状非法:长度与字符集由宿主定,界面按同一份约束生成。
|
||||
ClientTurnIdMalformed { min_chars: usize, max_chars: usize },
|
||||
/// 同一项目已有另一条回合在跑(或同一 `clientTurnId` 并发复用)。
|
||||
///
|
||||
/// 两个身份都要带上,因为"撞的是哪一轮"决定界面该不该动当前回合。
|
||||
TurnAlreadyRunning {
|
||||
existing_invocation_id: String,
|
||||
incoming_invocation_id: String,
|
||||
},
|
||||
/// 待发消息队列已满:这一条没进队,等前面几条发完再发。
|
||||
///
|
||||
/// 上限只落在宿主这一处(`MAX_PENDING_DIRECT_TURNS`),随载荷带出去,界面不自己数一份。
|
||||
@@ -487,7 +480,6 @@ impl DirectTurnError {
|
||||
Self::ClientTurnIdMissing
|
||||
| Self::ClientTurnIdMalformed { .. }
|
||||
| Self::QueueFull { .. }
|
||||
| Self::TurnAlreadyRunning { .. }
|
||||
// 项目目录锚不定(符号链接 / 权限 / 目录被删)与目录不存在同类:都是用户能自己修好的
|
||||
// 文件系统事实,诊断文案不该顶替那句"无法锚定 Direct 调用项目目录:{cause}"。
|
||||
| Self::ProjectRootUnanchored { .. }
|
||||
@@ -653,23 +645,6 @@ impl fmt::Display for DirectTurnError {
|
||||
formatter,
|
||||
"待发消息已达上限(最多 {limit} 条),请等前面几条发完再发送"
|
||||
),
|
||||
Self::TurnAlreadyRunning {
|
||||
existing_invocation_id,
|
||||
incoming_invocation_id,
|
||||
} => {
|
||||
// 两条文案按身份是否相同分岔,但**不再有机器前缀**:界面按 `error.type` 与其
|
||||
// 两个身份字段分流,不解析文案(前缀曾经是协议约定,现在只是噪声)。
|
||||
if existing_invocation_id == incoming_invocation_id {
|
||||
formatter.write_str(
|
||||
"同一轮消息仍在处理中,已拒绝并发复用同一 clientTurnId;请等它结束或点「终止」后再发送",
|
||||
)
|
||||
} else {
|
||||
write!(
|
||||
formatter,
|
||||
"当前项目已有另一条 Direct 客户端回合正在运行,已拒绝混用付费生成身份;可在输入盒点「终止」结束它,或等它结束后再发送"
|
||||
)
|
||||
}
|
||||
}
|
||||
Self::ProjectRootUnanchored { cause } => {
|
||||
write!(formatter, "无法锚定 Direct 调用项目目录:{cause}")
|
||||
}
|
||||
@@ -1022,11 +997,6 @@ mod tests {
|
||||
policy_detail: "项目权限策略拒绝执行:conversation.write".into(),
|
||||
}
|
||||
.is_reportable());
|
||||
assert!(!DirectTurnError::TurnAlreadyRunning {
|
||||
existing_invocation_id: "turn-1".into(),
|
||||
incoming_invocation_id: "turn-2".into(),
|
||||
}
|
||||
.is_reportable());
|
||||
// 回合级失败在上游已经写过诊断。
|
||||
assert!(
|
||||
!DirectTurnError::turn_failed(DirectCodexFailureStage::CodeGeneration, "模型失败")
|
||||
@@ -1034,26 +1004,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// 并发拒绝的两条文案按身份是否相同分岔,身份必须原样带出来。
|
||||
#[test]
|
||||
fn concurrent_rejection_keeps_both_invocations_and_splits_the_copy() {
|
||||
let same = DirectTurnError::TurnAlreadyRunning {
|
||||
existing_invocation_id: "turn-1".into(),
|
||||
incoming_invocation_id: "turn-1".into(),
|
||||
};
|
||||
assert!(same.to_string().contains("同一轮消息仍在处理中"));
|
||||
let different = DirectTurnError::TurnAlreadyRunning {
|
||||
existing_invocation_id: "turn-1".into(),
|
||||
incoming_invocation_id: "turn-2".into(),
|
||||
};
|
||||
assert!(different
|
||||
.to_string()
|
||||
.contains("另一条 Direct 客户端回合正在运行"));
|
||||
assert!(!different
|
||||
.to_string()
|
||||
.starts_with("direct-codex-turn-already-running:"));
|
||||
}
|
||||
|
||||
/// 边界序列化:字符串只由 Display 生成,且与改造前的可见文本一致。
|
||||
#[test]
|
||||
fn boundary_serialization_uses_display() {
|
||||
|
||||
-1
@@ -102,7 +102,6 @@ export function directTurnEnqueueFailureNotice(
|
||||
case 'clientTurnIdMissing':
|
||||
case 'clientTurnIdMalformed':
|
||||
case 'queueFull':
|
||||
case 'turnAlreadyRunning':
|
||||
case 'projectRootUnanchored':
|
||||
case 'projectRootUnusable':
|
||||
case 'permissionRejected':
|
||||
|
||||
-5
@@ -9,11 +9,6 @@ import type { DirectTurnDeadline } from './DirectTurnDeadline';
|
||||
export type DirectTurnError =
|
||||
| { type: 'clientTurnIdMissing' }
|
||||
| { type: 'clientTurnIdMalformed'; minChars: number; maxChars: number }
|
||||
| {
|
||||
type: 'turnAlreadyRunning';
|
||||
existingInvocationId: string;
|
||||
incomingInvocationId: string;
|
||||
}
|
||||
| { type: 'queueFull'; limit: number }
|
||||
| { type: 'projectRootUnanchored'; cause: string }
|
||||
| { type: 'projectRootUnusable' }
|
||||
|
||||
@@ -23,19 +23,15 @@ import {
|
||||
export function registerProjectConversationTests() {
|
||||
it('splits structured rejections by variant and never by copy', () => {
|
||||
// 入队失败是**结构化**载荷:分流只看 `error.type`,文案不参与任何判断。
|
||||
const concurrent = {
|
||||
error: {
|
||||
type: 'turnAlreadyRunning' as const,
|
||||
existingInvocationId: 'turn-1',
|
||||
incomingInvocationId: 'turn-1',
|
||||
},
|
||||
message: '同一轮消息仍在处理中',
|
||||
const queueFull = {
|
||||
error: { type: 'queueFull' as const, limit: 5 },
|
||||
message: '待发消息已达上限(最多 5 条),请等前面几条发完再发送',
|
||||
};
|
||||
expect(readDirectTurnEnqueueFailure(concurrent)?.error.type).toBe(
|
||||
'turnAlreadyRunning',
|
||||
expect(readDirectTurnEnqueueFailure(queueFull)?.error.type).toBe(
|
||||
'queueFull',
|
||||
);
|
||||
expect(directTurnEnqueueFailureNotice(concurrent)).toBe(
|
||||
'同一轮消息仍在处理中',
|
||||
expect(directTurnEnqueueFailureNotice(queueFull)).toBe(
|
||||
'待发消息已达上限(最多 5 条),请等前面几条发完再发送',
|
||||
);
|
||||
// 认得的参数 / 前置条件类都给同级提示。
|
||||
expect(
|
||||
|
||||
@@ -356,7 +356,7 @@ describe('DirectProject 聊天分区', () => {
|
||||
entries: [userEntry('u1'), assistantEntry('a1', '上一轮答复')],
|
||||
localMessages: [
|
||||
localNotice(
|
||||
'同一轮消息仍在处理中',
|
||||
'待发消息已达上限(最多 5 条),请等前面几条发完再发送',
|
||||
'direct-codex:turn-9:user:rejected',
|
||||
),
|
||||
],
|
||||
@@ -370,7 +370,7 @@ describe('DirectProject 聊天分区', () => {
|
||||
expect(turns[1]?.users).toEqual([]);
|
||||
expect(turns[1]?.startedAt).toBe(0);
|
||||
expect(turns[1]?.finals.map((block) => block.text)).toEqual([
|
||||
'同一轮消息仍在处理中',
|
||||
'待发消息已达上限(最多 5 条),请等前面几条发完再发送',
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user