Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/aiskillstore/marketplace --skill workflow-stop-design명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SKILL.md 표시 중
Maintain a portable task-state ledger for long, multi-step work. Use when a task spans many files, produces large logs, needs a reliable handoff, or requires traceable evidence without repeatedly loading full outputs. Creates concise state records and private evidence references with explicit limits, redaction checks, and retention guidance.
【收纳储物必看】装修前不会规划收纳,入住半年家变仓库?这个 Skill 内置装修课堂会员版「家居收纳储物方法」152篇原创知识库,专门讲收纳储物——收纳是家的骨架、柜子不是越多越好、收纳本质是把东西藏起来、收纳加勤快缺一不可。问玄关鞋柜怎么装、问厨房9个收纳位置、问衣柜衣帽间怎么做、问小户型怎么榨干每1平米、问收纳避坑和鸡肋神器,全部覆盖。适合正在装修、准备收纳规划、家里东西多总是乱、想做满墙柜/通顶柜/800库的业主。
【儿童房装修必看】家里有小孩、正准备要孩子、或想给儿童房做环保安全装修?这个 Skill 内置装修课堂知识库,专门讲"适童化"——儿童是最易受甲醛伤害的人群,儿童房必须实木/ENF/控总量。问儿童房怎么装环保、问儿童房墙面地面用什么、问儿童家具选实木还是人造板、问孩子学习/游戏专区怎么规划、问有娃家庭怎么防磕碰防污染,全部覆盖。适合家里有娃、备孕婚房、想装出健康儿童房的业主。
SOC 직업 분류 기준
| name | workflow-stop-design |
| description | 工作流暂停逻辑设计方案 |
Redis Key 结构:
// Key 格式: agent_runtime_stopping:{appId}:{chatId}
const WORKFLOW_STATUS_PREFIX = 'agent_runtime_stopping';
type WorkflowStatusKey = `${typeof WORKFLOW_STATUS_PREFIX}:${string}:${string}`;
// 示例: agent_runtime_stopping:app_123456:chat_789012
状态值设计:
参数类型定义:
type WorkflowStatusParams = {
appId: string;
chatId: string;
};
状态转换流程:
正常运行(无键) → 停止中(键存在) → 完成(删除键)
TTL 设置:
1. setAgentRuntimeStop
{ appId, chatId }SETEX 命令,设置键值为 1,TTL 60 秒2. shouldWorkflowStop
{ appId, chatId }Promise<boolean> - true=应该停止, false=继续运行3. delAgentRuntimeStopSign
{ appId, chatId }4. waitForWorkflowComplete
{ appId, chatId, timeout?, pollInterval? }1. Redis 操作失败
.catch() 错误处理shouldWorkflowStop: 出错时返回 false (认为不需要停止,继续运行)delAgentRuntimeStopSign: 出错时记录错误日志,但不影响主流程2. TTL 自动清理
3. stop 接口等待超时
waitForWorkflowComplete 在 5 秒内轮询检查停止标志是否被删除4. 并发停止请求
setAgentRuntimeStop 是安全的,Redis SETEX 是幂等操作位置: packages/service/core/workflow/dispatch/workflowStatus.ts
import { addLog } from '../../../common/system/log';
import { getGlobalRedisConnection } from '../../../common/redis/index';
import { delay } from '@fastgpt/global/common/system/utils';
const WORKFLOW_STATUS_PREFIX = 'agent_runtime_stopping';
const TTL = 60; // 60秒
export const StopStatus = 'STOPPING';
export type WorkflowStatusParams = {
appId: string;
chatId: string;
};
// 获取工作流状态键
export const getRuntimeStatusKey = (params: WorkflowStatusParams): string => {
return `${WORKFLOW_STATUS_PREFIX}:${params.appId}:${params.chatId}`;
};
// 设置停止标志
export const setAgentRuntimeStop = async (params: WorkflowStatusParams): Promise<void> => {
const redis = getGlobalRedisConnection();
const key = getRuntimeStatusKey(params);
redis.(key, , );
};
delAgentRuntimeStopSign = (: ): <> => {
redis = ();
key = (params);
redis.(key).( {
addLog.(, err);
});
};
shouldWorkflowStop = (: ): <> => {
redis = ();
key = (params);
redis
.(key)
.( !!res)
.( );
};
= () => {
startTime = .();
(.() - startTime < timeout) {
sign = ({ appId, chatId });
(!sign) {
;
}
(pollInterval);
}
;
};
测试用例位置: test/cases/service/core/app/workflow/workflowStatus.test.ts
import { describe, test, expect, beforeEach } from 'vitest';
import {
setAgentRuntimeStop,
delAgentRuntimeStopSign,
shouldWorkflowStop,
waitForWorkflowComplete
} from '@fastgpt/service/core/workflow/dispatch/workflowStatus';
describe('Workflow Status Redis Functions', () => {
const testAppId = 'test_app_123';
const testChatId = 'test_chat_456';
beforeEach(async () => {
// 清理测试数据
await delAgentRuntimeStopSign({ appId: testAppId, chatId: testChatId });
});
test('should set stopping sign', async () => {
await setAgentRuntimeStop({ appId: testAppId, chatId: testChatId });
const shouldStop = await shouldWorkflowStop({ appId: testAppId, chatId: testChatId });
expect(shouldStop).toBe(true);
});
test('should return false for non-existent status', async () => {
const shouldStop = await shouldWorkflowStop({ appId: testAppId, chatId: testChatId });
expect(shouldStop).toBe();
});
(, () => {
({ : testAppId, : testChatId });
({ : testAppId, : testChatId });
shouldStop = ({ : testAppId, : testChatId });
(shouldStop).();
});
(, () => {
({ : testAppId, : testChatId });
( () => {
({ : testAppId, : testChatId });
}, );
({
: testAppId,
: testChatId,
:
});
shouldStop = ({ : testAppId, : testChatId });
(shouldStop).();
});
(, () => {
({ : testAppId, : testChatId });
({
: testAppId,
: testChatId,
:
});
shouldStop = ({ : testAppId, : testChatId });
(shouldStop).();
});
(, () => {
.([
({ : testAppId, : testChatId }),
({ : testAppId, : testChatId })
]);
shouldStop = ({ : testAppId, : testChatId });
(shouldStop).();
});
});
文件: packages/service/core/workflow/dispatch/index.ts
改造点 1: 停止检测逻辑 (行 196-216)
使用内存变量 + 定时轮询 Redis 的方式:
import { delAgentRuntimeStopSign, shouldWorkflowStop } from './workflowStatus';
// 初始化停止检测
let stopping = false;
const checkIsStopping = (): boolean => {
if (apiVersion === 'v2') {
return stopping;
}
if (apiVersion === 'v1') {
if (!res) return false;
return res.closed || !!res.errored;
}
return false;
};
// v2 版本: 启动定时器定期检查 Redis
const checkStoppingTimer =
apiVersion === 'v2'
? setInterval(async () => {
stopping = await shouldWorkflowStop({
appId: runningAppInfo.id,
chatId
});
}, 100)
: undefined;
设计要点:
stopping + 100ms 定时器轮询 Redisres.closed/res.errored 检测改造点 2: 工作流完成后清理 (行 232-249)
return runWorkflow({
...data,
checkIsStopping, // 传递检测函数
query,
histories,
// ... 其他参数
}).finally(async () => {
// 清理定时器
if (streamCheckTimer) {
clearInterval(streamCheckTimer);
}
if (checkStoppingTimer) {
clearInterval(checkStoppingTimer);
}
// Close mcpClient connections
Object.values(mcpClientMemory).forEach((client) => {
client.closeConnection();
});
// 工作流完成后删除 Redis 记录
await delAgentRuntimeStopSign({
appId: runningAppInfo.id,
chatId
});
});
位置: packages/service/core/workflow/dispatch/index.ts:861-868
在 checkNodeCanRun 方法中,每个节点执行前检查:
private async checkNodeCanRun(
node: RuntimeNodeItemType,
skippedNodeIdList = new Set<string>()
) {
// ... 其他检查逻辑 ...
// Check queue status
if (data.maxRunTimes <= 0) {
addLog.error('Max run times is 0', {
appId: data.runningAppInfo.id
});
return;
}
// 停止检测
if (checkIsStopping()) {
addLog.warn('Workflow stopped', {
appId: data.runningAppInfo.id,
nodeId: node.nodeId,
nodeName: node.name
});
return;
}
// ... 执行节点逻辑 ...
}
说明:
checkIsStopping() 同步方法stopping接口路径: /api/v2/chat/stop
Schema 位置: packages/global/openapi/core/chat/api.ts
接口文档位置: packages/global/openapi/core/chat/index.ts
请求方法: POST
请求参数:
// packages/global/openapi/core/chat/api.ts
export const StopV2ChatSchema = z
.object({
appId: ObjectIdSchema.describe('应用ID'),
chatId: z.string().min(1).describe('对话ID'),
outLinkAuthData: OutLinkChatAuthSchema.optional().describe('外链鉴权数据')
});
export type StopV2ChatParams = z.infer<typeof StopV2ChatSchema>;
响应格式:
export const StopV2ChatResponseSchema = z
.object({
success: z.boolean().describe('是否成功停止')
});
export type StopV2ChatResponse = z.infer<typeof StopV2ChatResponseSchema>;
文件位置: projects/app/src/pages/api/v2/chat/stop.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { NextAPI } from '@/service/middleware/entry';
import { authChatCrud } from '@/service/support/permission/auth/chat';
import {
setAgentRuntimeStop,
waitForWorkflowComplete
} from '@fastgpt/service/core/workflow/dispatch/workflowStatus';
import { StopV2ChatSchema, type StopV2ChatResponse } from '@fastgpt/global/openapi/core/chat/controler/api';
async function handler(req: NextApiRequest, res: NextApiResponse): Promise<StopV2ChatResponse> {
const { appId, chatId, outLinkAuthData } = StopV2ChatSchema.parse(req.body);
// 鉴权 (复用聊天 CRUD 鉴权)
await authChatCrud({
req,
authToken: true,
authApiKey: true,
appId,
chatId,
...outLinkAuthData
});
// 设置停止标志
await setAgentRuntimeStop({
appId,
chatId
});
// 等待工作流完成 (最多等待 5 秒)
({ appId, chatId, : });
{
:
};
}
(handler);
接口文档 (packages/global/openapi/core/chat/index.ts):
export const ChatPath: OpenAPIPath = {
// ... 其他路径
'/v2/chat/stop': {
post: {
summary: '停止 Agent 运行',
description: `优雅停止正在运行的 Agent, 会尝试等待当前节点结束后返回,最长 5s,超过 5s 仍未结束,则会返回成功。
LLM 节点,流输出时会同时被终止,但 HTTP 请求节点这种可能长时间运行的,不会被终止。`,
tags: [TagsMap.chatPage],
requestBody: {
content: {
'application/json': {
schema: StopV2ChatSchema
}
}
},
responses: {
200: {
description: '成功停止工作流',
content: {
'application/json': {
schema: StopV2ChatResponseSchema
}
}
}
}
}
}
};
说明:
authChatCrud 进行鉴权,支持 Token 和 API Keysuccess: true由于当前代码已经能够正常工作,且 v2 版本的后端已经实现了基于 Redis 的停止机制,前端可以保持现有的简单实现:
保持现有实现的原因:
abort() 后,后端会在下个检测周期(100ms内)发现停止标志可选的增强方案:
如果需要在前端显示更详细的停止状态,可以添加 API 客户端函数:
文件位置: projects/app/src/web/core/chat/api.ts
import { POST } from '@/web/common/api/request';
import type { StopV2ChatParams, StopV2ChatResponse } from '@fastgpt/global/openapi/core/chat/controler/api';
/**
* 停止 v2 版本工作流运行
*/
export const stopV2Chat = (data: StopV2ChatParams) =>
POST<StopV2ChatResponse>('/api/v2/chat/stop', data);
增强的 abortRequest 函数:
/* Abort chat completions, questionGuide */
const abortRequest = useMemoizedFn(async (reason: string = 'stop') => {
// 先调用 abort 中断连接
chatController.current?.abort(new Error(reason));
questionGuideController.current?.abort(new Error(reason));
pluginController.current?.abort(new Error(reason));
// v2 版本: 可选地通知后端优雅停止
if (chatBoxData?.app?.version === 'v2' && appId && chatId) {
try {
await stopV2Chat({
appId,
chatId,
outLinkAuthData
});
} catch (error) {
// 静默失败,不影响用户体验
console.warn('Failed to notify backend to stop workflow', error);
}
}
});
建议:
用户点击停止按钮
↓
前端: abortRequest()
↓
前端: chatController.abort() [立即中断 HTTP 连接]
↓
[可选] 前端: POST /api/v2/chat/stop
↓
后端: setAgentRuntimeStop(appId, chatId) [设置停止标志]
↓
后端: 定时器检测到 Redis 停止标志,更新内存变量 stopping = true
↓
后端: 下个节点执行前 checkIsStopping() 返回 true
↓
后端: 停止处理新节点,记录日志
↓
后端: 工作流 finally 块删除 Redis 停止标志
↓
[可选] 后端: waitForWorkflowComplete() 检测到停止标志被删除
↓
[可选] 前端: 显示停止成功提示
[可选] 前端: POST /api/v2/chat/stop
↓
后端: setAgentRuntimeStop(appId, chatId)
↓
后端: waitForWorkflowComplete(timeout=5s)
↓
后端: 5秒后停止标志仍存在
↓
后端: 返回成功响应 (不区分超时)
↓
[可选] 前端: 显示成功提示
↓
后端: 工作流继续运行,最终完成后删除停止标志
工作流运行中
↓
所有节点执行完成
↓
dispatchWorkFlow.finally()
↓
删除 Redis 停止标志
↓
清理定时器
↓
60秒 TTL 确保即使删除失败也会自动清理
关键时间点:
响应时间:
Redis 工具函数测试:
setAgentRuntimeStop / shouldWorkflowStop 基本功能delAgentRuntimeStopSign 删除功能waitForWorkflowComplete 等待机制和超时文件位置: test/cases/service/core/app/workflow/workflowStatus.test.ts
测试用例:
describe('Workflow Status Redis Functions', () => {
test('should set stopping sign')
test('should return false for non-existent status')
test('should detect stopping status')
test('should return false after deleting stop sign')
test('should wait for workflow completion')
test('should timeout when waiting too long')
test('should delete workflow stop sign')
test('should handle concurrent stop sign operations')
});